@tanstack/react-router 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/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/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/router.ts +50 -7
|
@@ -359,69 +359,6 @@
|
|
|
359
359
|
}
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
exports.routerContext = /*#__PURE__*/React__namespace.createContext(null);
|
|
363
|
-
if (typeof document !== 'undefined') {
|
|
364
|
-
if (window.__TSR_ROUTER_CONTEXT__) {
|
|
365
|
-
exports.routerContext = window.__TSR_ROUTER_CONTEXT__;
|
|
366
|
-
} else {
|
|
367
|
-
window.__TSR_ROUTER_CONTEXT__ = exports.routerContext;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function useRouter(opts) {
|
|
372
|
-
const resolvedContext = typeof document !== 'undefined' ? window.__TSR_ROUTER_CONTEXT__ || exports.routerContext : exports.routerContext;
|
|
373
|
-
const value = React__namespace.useContext(resolvedContext);
|
|
374
|
-
warning(!((opts?.warn ?? true) && !value), 'useRouter must be used inside a <RouterProvider> component!');
|
|
375
|
-
return value;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function defer(_promise) {
|
|
379
|
-
const promise = _promise;
|
|
380
|
-
if (!promise.__deferredState) {
|
|
381
|
-
promise.__deferredState = {
|
|
382
|
-
uid: Math.random().toString(36).slice(2),
|
|
383
|
-
status: 'pending'
|
|
384
|
-
};
|
|
385
|
-
const state = promise.__deferredState;
|
|
386
|
-
promise.then(data => {
|
|
387
|
-
state.status = 'success';
|
|
388
|
-
state.data = data;
|
|
389
|
-
}).catch(error => {
|
|
390
|
-
state.status = 'error';
|
|
391
|
-
state.error = error;
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
return promise;
|
|
395
|
-
}
|
|
396
|
-
function isDehydratedDeferred(obj) {
|
|
397
|
-
return typeof obj === 'object' && obj !== null && !(obj instanceof Promise) && !obj.then && '__deferredState' in obj;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function useAwaited({
|
|
401
|
-
promise
|
|
402
|
-
}) {
|
|
403
|
-
const router = useRouter();
|
|
404
|
-
let state = promise.__deferredState;
|
|
405
|
-
const key = `__TSR__DEFERRED__${state.uid}`;
|
|
406
|
-
if (isDehydratedDeferred(promise)) {
|
|
407
|
-
state = router.hydrateData(key);
|
|
408
|
-
promise = Promise.resolve(state.data);
|
|
409
|
-
promise.__deferredState = state;
|
|
410
|
-
}
|
|
411
|
-
if (state.status === 'pending') {
|
|
412
|
-
throw promise;
|
|
413
|
-
}
|
|
414
|
-
if (state.status === 'error') {
|
|
415
|
-
throw state.error;
|
|
416
|
-
}
|
|
417
|
-
router.dehydrateData(key, state);
|
|
418
|
-
return [state.data];
|
|
419
|
-
}
|
|
420
|
-
function Await(props) {
|
|
421
|
-
const awaited = useAwaited(props);
|
|
422
|
-
return props.children(...awaited);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
362
|
function CatchBoundary(props) {
|
|
426
363
|
const errorComponent = props.errorComponent ?? ErrorComponent;
|
|
427
364
|
return /*#__PURE__*/React__namespace.createElement(CatchBoundaryImpl, {
|
|
@@ -779,6 +716,22 @@
|
|
|
779
716
|
return true;
|
|
780
717
|
}
|
|
781
718
|
|
|
719
|
+
exports.routerContext = /*#__PURE__*/React__namespace.createContext(null);
|
|
720
|
+
if (typeof document !== 'undefined') {
|
|
721
|
+
if (window.__TSR_ROUTER_CONTEXT__) {
|
|
722
|
+
exports.routerContext = window.__TSR_ROUTER_CONTEXT__;
|
|
723
|
+
} else {
|
|
724
|
+
window.__TSR_ROUTER_CONTEXT__ = exports.routerContext;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function useRouter(opts) {
|
|
729
|
+
const resolvedContext = typeof document !== 'undefined' ? window.__TSR_ROUTER_CONTEXT__ || exports.routerContext : exports.routerContext;
|
|
730
|
+
const value = React__namespace.useContext(resolvedContext);
|
|
731
|
+
warning(!((opts?.warn ?? true) && !value), 'useRouter must be used inside a <RouterProvider> component!');
|
|
732
|
+
return value;
|
|
733
|
+
}
|
|
734
|
+
|
|
782
735
|
function useRouterState(opts) {
|
|
783
736
|
const contextRouter = useRouter({
|
|
784
737
|
warn: opts?.router === undefined
|
|
@@ -1050,7 +1003,12 @@
|
|
|
1050
1003
|
select: s => pick(getRenderedMatches(s).find(d => d.id === matchId), ['status', 'error', 'showPending', 'loadPromise'])
|
|
1051
1004
|
});
|
|
1052
1005
|
if (match.status === 'error') {
|
|
1053
|
-
|
|
1006
|
+
if (isServerSideError(match.error)) {
|
|
1007
|
+
const deserializeError = router.options.errorSerializer?.deserialize ?? defaultDeserializeError;
|
|
1008
|
+
throw deserializeError(match.error.data);
|
|
1009
|
+
} else {
|
|
1010
|
+
throw match.error;
|
|
1011
|
+
}
|
|
1054
1012
|
}
|
|
1055
1013
|
if (match.status === 'pending') {
|
|
1056
1014
|
if (match.showPending) {
|
|
@@ -1171,114 +1129,353 @@
|
|
|
1171
1129
|
}
|
|
1172
1130
|
});
|
|
1173
1131
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1132
|
+
function isServerSideError(error) {
|
|
1133
|
+
if (!(typeof error === 'object' && error && 'data' in error)) return false;
|
|
1134
|
+
if (!('__isServerError' in error && error.__isServerError)) return false;
|
|
1135
|
+
if (!(typeof error.data === 'object' && error.data)) return false;
|
|
1136
|
+
return error.__isServerError === true;
|
|
1137
|
+
}
|
|
1138
|
+
function defaultDeserializeError(serializedData) {
|
|
1139
|
+
if ('name' in serializedData && 'message' in serializedData) {
|
|
1140
|
+
const error = new Error(serializedData.message);
|
|
1141
|
+
error.name = serializedData.name;
|
|
1142
|
+
return error;
|
|
1143
|
+
}
|
|
1144
|
+
return serializedData.data;
|
|
1184
1145
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1146
|
+
|
|
1147
|
+
// @ts-nocheck
|
|
1148
|
+
|
|
1149
|
+
// 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.
|
|
1150
|
+
|
|
1151
|
+
function encode(obj, pfx) {
|
|
1152
|
+
var k,
|
|
1153
|
+
i,
|
|
1154
|
+
tmp,
|
|
1155
|
+
str = '';
|
|
1156
|
+
for (k in obj) {
|
|
1157
|
+
if ((tmp = obj[k]) !== void 0) {
|
|
1158
|
+
if (Array.isArray(tmp)) {
|
|
1159
|
+
for (i = 0; i < tmp.length; i++) {
|
|
1160
|
+
str && (str += '&');
|
|
1161
|
+
str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp[i]);
|
|
1162
|
+
}
|
|
1163
|
+
} else {
|
|
1164
|
+
str && (str += '&');
|
|
1165
|
+
str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
return (pfx || '') + str;
|
|
1187
1170
|
}
|
|
1188
|
-
function
|
|
1189
|
-
|
|
1171
|
+
function toValue(mix) {
|
|
1172
|
+
if (!mix) return '';
|
|
1173
|
+
var str = decodeURIComponent(mix);
|
|
1174
|
+
if (str === 'false') return false;
|
|
1175
|
+
if (str === 'true') return true;
|
|
1176
|
+
return +str * 0 === 0 && +str + '' === str ? +str : str;
|
|
1190
1177
|
}
|
|
1191
|
-
function
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
} else if (index === toSegments.length - 1) {
|
|
1202
|
-
// Trailing Slash
|
|
1203
|
-
baseSegments.push(toSegment);
|
|
1204
|
-
} else ;
|
|
1205
|
-
} else if (toSegment.value === '..') {
|
|
1206
|
-
// Extra trailing slash? pop it off
|
|
1207
|
-
if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
|
|
1208
|
-
baseSegments.pop();
|
|
1209
|
-
}
|
|
1210
|
-
baseSegments.pop();
|
|
1211
|
-
} else if (toSegment.value === '.') {
|
|
1212
|
-
return;
|
|
1178
|
+
function decode(str) {
|
|
1179
|
+
var tmp,
|
|
1180
|
+
k,
|
|
1181
|
+
out = {},
|
|
1182
|
+
arr = str.split('&');
|
|
1183
|
+
while (tmp = arr.shift()) {
|
|
1184
|
+
tmp = tmp.split('=');
|
|
1185
|
+
k = tmp.shift();
|
|
1186
|
+
if (out[k] !== void 0) {
|
|
1187
|
+
out[k] = [].concat(out[k], toValue(tmp.shift()));
|
|
1213
1188
|
} else {
|
|
1214
|
-
|
|
1189
|
+
out[k] = toValue(tmp.shift());
|
|
1215
1190
|
}
|
|
1216
|
-
});
|
|
1217
|
-
const joined = joinPaths([basepath, ...baseSegments.map(d => d.value)]);
|
|
1218
|
-
return cleanPath(joined);
|
|
1219
|
-
}
|
|
1220
|
-
function parsePathname(pathname) {
|
|
1221
|
-
if (!pathname) {
|
|
1222
|
-
return [];
|
|
1223
|
-
}
|
|
1224
|
-
pathname = cleanPath(pathname);
|
|
1225
|
-
const segments = [];
|
|
1226
|
-
if (pathname.slice(0, 1) === '/') {
|
|
1227
|
-
pathname = pathname.substring(1);
|
|
1228
|
-
segments.push({
|
|
1229
|
-
type: 'pathname',
|
|
1230
|
-
value: '/'
|
|
1231
|
-
});
|
|
1232
|
-
}
|
|
1233
|
-
if (!pathname) {
|
|
1234
|
-
return segments;
|
|
1235
1191
|
}
|
|
1192
|
+
return out;
|
|
1193
|
+
}
|
|
1236
1194
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
value: part
|
|
1244
|
-
};
|
|
1195
|
+
const defaultParseSearch = parseSearchWith(JSON.parse);
|
|
1196
|
+
const defaultStringifySearch = stringifySearchWith(JSON.stringify, JSON.parse);
|
|
1197
|
+
function parseSearchWith(parser) {
|
|
1198
|
+
return searchStr => {
|
|
1199
|
+
if (searchStr.substring(0, 1) === '?') {
|
|
1200
|
+
searchStr = searchStr.substring(1);
|
|
1245
1201
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1202
|
+
let query = decode(searchStr);
|
|
1203
|
+
|
|
1204
|
+
// Try to parse any query params that might be json
|
|
1205
|
+
for (let key in query) {
|
|
1206
|
+
const value = query[key];
|
|
1207
|
+
if (typeof value === 'string') {
|
|
1208
|
+
try {
|
|
1209
|
+
query[key] = parser(value);
|
|
1210
|
+
} catch (err) {
|
|
1211
|
+
//
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1251
1214
|
}
|
|
1252
|
-
return
|
|
1253
|
-
|
|
1254
|
-
value: part
|
|
1255
|
-
};
|
|
1256
|
-
}));
|
|
1257
|
-
if (pathname.slice(-1) === '/') {
|
|
1258
|
-
pathname = pathname.substring(1);
|
|
1259
|
-
segments.push({
|
|
1260
|
-
type: 'pathname',
|
|
1261
|
-
value: '/'
|
|
1262
|
-
});
|
|
1263
|
-
}
|
|
1264
|
-
return segments;
|
|
1215
|
+
return query;
|
|
1216
|
+
};
|
|
1265
1217
|
}
|
|
1266
|
-
function
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
if (
|
|
1275
|
-
|
|
1218
|
+
function stringifySearchWith(stringify, parser) {
|
|
1219
|
+
function stringifyValue(val) {
|
|
1220
|
+
if (typeof val === 'object' && val !== null) {
|
|
1221
|
+
try {
|
|
1222
|
+
return stringify(val);
|
|
1223
|
+
} catch (err) {
|
|
1224
|
+
// silent
|
|
1225
|
+
}
|
|
1226
|
+
} else if (typeof val === 'string' && typeof parser === 'function') {
|
|
1227
|
+
try {
|
|
1228
|
+
// Check if it's a valid parseable string.
|
|
1229
|
+
// If it is, then stringify it again.
|
|
1230
|
+
parser(val);
|
|
1231
|
+
return stringify(val);
|
|
1232
|
+
} catch (err) {
|
|
1233
|
+
// silent
|
|
1234
|
+
}
|
|
1276
1235
|
}
|
|
1277
|
-
return
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1236
|
+
return val;
|
|
1237
|
+
}
|
|
1238
|
+
return search => {
|
|
1239
|
+
search = {
|
|
1240
|
+
...search
|
|
1241
|
+
};
|
|
1242
|
+
if (search) {
|
|
1243
|
+
Object.keys(search).forEach(key => {
|
|
1244
|
+
const val = search[key];
|
|
1245
|
+
if (typeof val === 'undefined' || val === undefined) {
|
|
1246
|
+
delete search[key];
|
|
1247
|
+
} else {
|
|
1248
|
+
search[key] = stringifyValue(val);
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
const searchStr = encode(search).toString();
|
|
1253
|
+
return searchStr ? `?${searchStr}` : '';
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
const useTransition = React__namespace.useTransition || (() => [false, cb => {
|
|
1258
|
+
cb();
|
|
1259
|
+
}]);
|
|
1260
|
+
function RouterProvider({
|
|
1261
|
+
router,
|
|
1262
|
+
...rest
|
|
1263
|
+
}) {
|
|
1264
|
+
// Allow the router to update options on the router instance
|
|
1265
|
+
router.update({
|
|
1266
|
+
...router.options,
|
|
1267
|
+
...rest,
|
|
1268
|
+
context: {
|
|
1269
|
+
...router.options.context,
|
|
1270
|
+
...rest?.context
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
const matches = router.options.InnerWrap ? /*#__PURE__*/React__namespace.createElement(router.options.InnerWrap, null, /*#__PURE__*/React__namespace.createElement(Matches, null)) : /*#__PURE__*/React__namespace.createElement(Matches, null);
|
|
1274
|
+
const provider = /*#__PURE__*/React__namespace.createElement(exports.routerContext.Provider, {
|
|
1275
|
+
value: router
|
|
1276
|
+
}, matches, /*#__PURE__*/React__namespace.createElement(Transitioner, null));
|
|
1277
|
+
if (router.options.Wrap) {
|
|
1278
|
+
return /*#__PURE__*/React__namespace.createElement(router.options.Wrap, null, provider);
|
|
1279
|
+
}
|
|
1280
|
+
return provider;
|
|
1281
|
+
}
|
|
1282
|
+
function Transitioner() {
|
|
1283
|
+
const mountLoadCount = React__namespace.useRef(0);
|
|
1284
|
+
const router = useRouter();
|
|
1285
|
+
const routerState = useRouterState({
|
|
1286
|
+
select: s => pick(s, ['isLoading', 'location', 'resolvedLocation', 'isTransitioning'])
|
|
1287
|
+
});
|
|
1288
|
+
const [isTransitioning, startReactTransition] = useTransition();
|
|
1289
|
+
router.startReactTransition = startReactTransition;
|
|
1290
|
+
React__namespace.useEffect(() => {
|
|
1291
|
+
if (isTransitioning) {
|
|
1292
|
+
router.__store.setState(s => ({
|
|
1293
|
+
...s,
|
|
1294
|
+
isTransitioning
|
|
1295
|
+
}));
|
|
1296
|
+
}
|
|
1297
|
+
}, [isTransitioning]);
|
|
1298
|
+
const tryLoad = () => {
|
|
1299
|
+
const apply = cb => {
|
|
1300
|
+
if (!routerState.isTransitioning) {
|
|
1301
|
+
startReactTransition(() => cb());
|
|
1302
|
+
} else {
|
|
1303
|
+
cb();
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
apply(() => {
|
|
1307
|
+
try {
|
|
1308
|
+
router.load();
|
|
1309
|
+
} catch (err) {
|
|
1310
|
+
console.error(err);
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
};
|
|
1314
|
+
useLayoutEffect$1(() => {
|
|
1315
|
+
const unsub = router.history.subscribe(() => {
|
|
1316
|
+
router.latestLocation = router.parseLocation(router.latestLocation);
|
|
1317
|
+
if (routerState.location !== router.latestLocation) {
|
|
1318
|
+
tryLoad();
|
|
1319
|
+
}
|
|
1320
|
+
});
|
|
1321
|
+
const nextLocation = router.buildLocation({
|
|
1322
|
+
search: true,
|
|
1323
|
+
params: true,
|
|
1324
|
+
hash: true,
|
|
1325
|
+
state: true
|
|
1326
|
+
});
|
|
1327
|
+
if (routerState.location.href !== nextLocation.href) {
|
|
1328
|
+
router.commitLocation({
|
|
1329
|
+
...nextLocation,
|
|
1330
|
+
replace: true
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
return () => {
|
|
1334
|
+
unsub();
|
|
1335
|
+
};
|
|
1336
|
+
}, [router.history]);
|
|
1337
|
+
useLayoutEffect$1(() => {
|
|
1338
|
+
if (React__namespace.useTransition ? routerState.isTransitioning && !isTransitioning : !routerState.isLoading && routerState.resolvedLocation !== routerState.location) {
|
|
1339
|
+
router.emit({
|
|
1340
|
+
type: 'onResolved',
|
|
1341
|
+
fromLocation: routerState.resolvedLocation,
|
|
1342
|
+
toLocation: routerState.location,
|
|
1343
|
+
pathChanged: routerState.location.href !== routerState.resolvedLocation?.href
|
|
1344
|
+
});
|
|
1345
|
+
if (document.querySelector) {
|
|
1346
|
+
if (routerState.location.hash !== '') {
|
|
1347
|
+
const el = document.getElementById(routerState.location.hash);
|
|
1348
|
+
if (el) {
|
|
1349
|
+
el.scrollIntoView();
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
router.__store.setState(s => ({
|
|
1354
|
+
...s,
|
|
1355
|
+
isTransitioning: false,
|
|
1356
|
+
resolvedLocation: s.location
|
|
1357
|
+
}));
|
|
1358
|
+
}
|
|
1359
|
+
}, [routerState.isTransitioning, isTransitioning, routerState.isLoading, routerState.resolvedLocation, routerState.location]);
|
|
1360
|
+
useLayoutEffect$1(() => {
|
|
1361
|
+
if (!window.__TSR_DEHYDRATED__ && !mountLoadCount.current) {
|
|
1362
|
+
mountLoadCount.current++;
|
|
1363
|
+
tryLoad();
|
|
1364
|
+
}
|
|
1365
|
+
}, []);
|
|
1366
|
+
return null;
|
|
1367
|
+
}
|
|
1368
|
+
function getRouteMatch(state, id) {
|
|
1369
|
+
return [...state.cachedMatches, ...(state.pendingMatches ?? []), ...state.matches].find(d => d.id === id);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
function joinPaths(paths) {
|
|
1373
|
+
return cleanPath(paths.filter(Boolean).join('/'));
|
|
1374
|
+
}
|
|
1375
|
+
function cleanPath(path) {
|
|
1376
|
+
// remove double slashes
|
|
1377
|
+
return path.replace(/\/{2,}/g, '/');
|
|
1378
|
+
}
|
|
1379
|
+
function trimPathLeft(path) {
|
|
1380
|
+
return path === '/' ? path : path.replace(/^\/{1,}/, '');
|
|
1381
|
+
}
|
|
1382
|
+
function trimPathRight(path) {
|
|
1383
|
+
return path === '/' ? path : path.replace(/\/{1,}$/, '');
|
|
1384
|
+
}
|
|
1385
|
+
function trimPath(path) {
|
|
1386
|
+
return trimPathRight(trimPathLeft(path));
|
|
1387
|
+
}
|
|
1388
|
+
function resolvePath(basepath, base, to) {
|
|
1389
|
+
base = base.replace(new RegExp(`^${basepath}`), '/');
|
|
1390
|
+
to = to.replace(new RegExp(`^${basepath}`), '/');
|
|
1391
|
+
let baseSegments = parsePathname(base);
|
|
1392
|
+
const toSegments = parsePathname(to);
|
|
1393
|
+
toSegments.forEach((toSegment, index) => {
|
|
1394
|
+
if (toSegment.value === '/') {
|
|
1395
|
+
if (!index) {
|
|
1396
|
+
// Leading slash
|
|
1397
|
+
baseSegments = [toSegment];
|
|
1398
|
+
} else if (index === toSegments.length - 1) {
|
|
1399
|
+
// Trailing Slash
|
|
1400
|
+
baseSegments.push(toSegment);
|
|
1401
|
+
} else ;
|
|
1402
|
+
} else if (toSegment.value === '..') {
|
|
1403
|
+
// Extra trailing slash? pop it off
|
|
1404
|
+
if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
|
|
1405
|
+
baseSegments.pop();
|
|
1406
|
+
}
|
|
1407
|
+
baseSegments.pop();
|
|
1408
|
+
} else if (toSegment.value === '.') {
|
|
1409
|
+
return;
|
|
1410
|
+
} else {
|
|
1411
|
+
baseSegments.push(toSegment);
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
const joined = joinPaths([basepath, ...baseSegments.map(d => d.value)]);
|
|
1415
|
+
return cleanPath(joined);
|
|
1416
|
+
}
|
|
1417
|
+
function parsePathname(pathname) {
|
|
1418
|
+
if (!pathname) {
|
|
1419
|
+
return [];
|
|
1420
|
+
}
|
|
1421
|
+
pathname = cleanPath(pathname);
|
|
1422
|
+
const segments = [];
|
|
1423
|
+
if (pathname.slice(0, 1) === '/') {
|
|
1424
|
+
pathname = pathname.substring(1);
|
|
1425
|
+
segments.push({
|
|
1426
|
+
type: 'pathname',
|
|
1427
|
+
value: '/'
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
if (!pathname) {
|
|
1431
|
+
return segments;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// Remove empty segments and '.' segments
|
|
1435
|
+
const split = pathname.split('/').filter(Boolean);
|
|
1436
|
+
segments.push(...split.map(part => {
|
|
1437
|
+
if (part === '$' || part === '*') {
|
|
1438
|
+
return {
|
|
1439
|
+
type: 'wildcard',
|
|
1440
|
+
value: part
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
if (part.charAt(0) === '$') {
|
|
1444
|
+
return {
|
|
1445
|
+
type: 'param',
|
|
1446
|
+
value: part
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
return {
|
|
1450
|
+
type: 'pathname',
|
|
1451
|
+
value: part
|
|
1452
|
+
};
|
|
1453
|
+
}));
|
|
1454
|
+
if (pathname.slice(-1) === '/') {
|
|
1455
|
+
pathname = pathname.substring(1);
|
|
1456
|
+
segments.push({
|
|
1457
|
+
type: 'pathname',
|
|
1458
|
+
value: '/'
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
return segments;
|
|
1462
|
+
}
|
|
1463
|
+
function interpolatePath(path, params, leaveWildcards = false) {
|
|
1464
|
+
const interpolatedPathSegments = parsePathname(path);
|
|
1465
|
+
return joinPaths(interpolatedPathSegments.map(segment => {
|
|
1466
|
+
if (segment.type === 'wildcard') {
|
|
1467
|
+
const value = params[segment.value];
|
|
1468
|
+
if (leaveWildcards) return `${segment.value}${value ?? ''}`;
|
|
1469
|
+
return value;
|
|
1470
|
+
}
|
|
1471
|
+
if (segment.type === 'param') {
|
|
1472
|
+
return params[segment.value.substring(1)] ?? 'undefined';
|
|
1473
|
+
}
|
|
1474
|
+
return segment.value;
|
|
1475
|
+
}));
|
|
1476
|
+
}
|
|
1477
|
+
function matchPathname(basepath, currentPathname, matchLocation) {
|
|
1478
|
+
const pathParams = matchByPath(basepath, currentPathname, matchLocation);
|
|
1282
1479
|
// const searchMatched = matchBySearch(location.search, matchLocation)
|
|
1283
1480
|
|
|
1284
1481
|
if (matchLocation.to && !pathParams) {
|
|
@@ -1363,1827 +1560,1684 @@
|
|
|
1363
1560
|
return isMatch ? params : undefined;
|
|
1364
1561
|
}
|
|
1365
1562
|
|
|
1366
|
-
|
|
1367
|
-
return useRouterState({
|
|
1368
|
-
select: state => {
|
|
1369
|
-
const params = last(getRenderedMatches(state))?.params;
|
|
1370
|
-
return opts?.select ? opts.select(params) : params;
|
|
1371
|
-
}
|
|
1372
|
-
});
|
|
1373
|
-
}
|
|
1563
|
+
// Detect if we're in the DOM
|
|
1374
1564
|
|
|
1375
|
-
function
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1565
|
+
function redirect(opts) {
|
|
1566
|
+
opts.isRedirect = true;
|
|
1567
|
+
if (opts.throw) {
|
|
1568
|
+
throw opts;
|
|
1569
|
+
}
|
|
1570
|
+
return opts;
|
|
1571
|
+
}
|
|
1572
|
+
function isRedirect(obj) {
|
|
1573
|
+
return !!obj?.isRedirect;
|
|
1382
1574
|
}
|
|
1383
1575
|
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
// The parse type here allows a zod schema to be passed directly to the validator
|
|
1387
|
-
|
|
1388
|
-
// TODO: This is part of a future APi to move away from classes and
|
|
1389
|
-
// towards a more functional API. It's not ready yet.
|
|
1390
|
-
|
|
1391
|
-
// type RouteApiInstance<
|
|
1392
|
-
// TId extends RouteIds<RegisteredRouter['routeTree']>,
|
|
1393
|
-
// TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
|
|
1394
|
-
// TFullSearchSchema extends Record<
|
|
1395
|
-
// string,
|
|
1396
|
-
// any
|
|
1397
|
-
// > = TRoute['types']['fullSearchSchema'],
|
|
1398
|
-
// TAllParams extends AnyPathParams = TRoute['types']['allParams'],
|
|
1399
|
-
// TAllContext extends Record<string, any> = TRoute['types']['allContext'],
|
|
1400
|
-
// TLoaderDeps extends Record<string, any> = TRoute['types']['loaderDeps'],
|
|
1401
|
-
// TLoaderData extends any = TRoute['types']['loaderData'],
|
|
1402
|
-
// > = {
|
|
1403
|
-
// id: TId
|
|
1404
|
-
// useMatch: <TSelected = TAllContext>(opts?: {
|
|
1405
|
-
// select?: (s: TAllContext) => TSelected
|
|
1406
|
-
// }) => TSelected
|
|
1407
|
-
|
|
1408
|
-
// useRouteContext: <TSelected = TAllContext>(opts?: {
|
|
1409
|
-
// select?: (s: TAllContext) => TSelected
|
|
1410
|
-
// }) => TSelected
|
|
1411
|
-
|
|
1412
|
-
// useSearch: <TSelected = TFullSearchSchema>(opts?: {
|
|
1413
|
-
// select?: (s: TFullSearchSchema) => TSelected
|
|
1414
|
-
// }) => TSelected
|
|
1415
|
-
|
|
1416
|
-
// useParams: <TSelected = TAllParams>(opts?: {
|
|
1417
|
-
// select?: (s: TAllParams) => TSelected
|
|
1418
|
-
// }) => TSelected
|
|
1419
|
-
|
|
1420
|
-
// useLoaderDeps: <TSelected = TLoaderDeps>(opts?: {
|
|
1421
|
-
// select?: (s: TLoaderDeps) => TSelected
|
|
1422
|
-
// }) => TSelected
|
|
1423
|
-
|
|
1424
|
-
// useLoaderData: <TSelected = TLoaderData>(opts?: {
|
|
1425
|
-
// select?: (s: TLoaderData) => TSelected
|
|
1426
|
-
// }) => TSelected
|
|
1427
|
-
// }
|
|
1428
|
-
|
|
1429
|
-
// export function RouteApi_v2<
|
|
1430
|
-
// TId extends RouteIds<RegisteredRouter['routeTree']>,
|
|
1431
|
-
// TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
|
|
1432
|
-
// TFullSearchSchema extends Record<
|
|
1433
|
-
// string,
|
|
1434
|
-
// any
|
|
1435
|
-
// > = TRoute['types']['fullSearchSchema'],
|
|
1436
|
-
// TAllParams extends AnyPathParams = TRoute['types']['allParams'],
|
|
1437
|
-
// TAllContext extends Record<string, any> = TRoute['types']['allContext'],
|
|
1438
|
-
// TLoaderDeps extends Record<string, any> = TRoute['types']['loaderDeps'],
|
|
1439
|
-
// TLoaderData extends any = TRoute['types']['loaderData'],
|
|
1440
|
-
// >({
|
|
1441
|
-
// id,
|
|
1442
|
-
// }: {
|
|
1443
|
-
// id: TId
|
|
1444
|
-
// }): RouteApiInstance<
|
|
1445
|
-
// TId,
|
|
1446
|
-
// TRoute,
|
|
1447
|
-
// TFullSearchSchema,
|
|
1448
|
-
// TAllParams,
|
|
1449
|
-
// TAllContext,
|
|
1450
|
-
// TLoaderDeps,
|
|
1451
|
-
// TLoaderData
|
|
1452
|
-
// > {
|
|
1453
|
-
// return {
|
|
1454
|
-
// id,
|
|
1455
|
-
|
|
1456
|
-
// useMatch: (opts) => {
|
|
1457
|
-
// return useMatch({ ...opts, from: id })
|
|
1458
|
-
// },
|
|
1459
|
-
|
|
1460
|
-
// useRouteContext: (opts) => {
|
|
1461
|
-
// return useMatch({
|
|
1462
|
-
// ...opts,
|
|
1463
|
-
// from: id,
|
|
1464
|
-
// select: (d: any) => (opts?.select ? opts.select(d.context) : d.context),
|
|
1465
|
-
// } as any)
|
|
1466
|
-
// },
|
|
1467
|
-
|
|
1468
|
-
// useSearch: (opts) => {
|
|
1469
|
-
// return useSearch({ ...opts, from: id } as any)
|
|
1470
|
-
// },
|
|
1471
|
-
|
|
1472
|
-
// useParams: (opts) => {
|
|
1473
|
-
// return useParams({ ...opts, from: id } as any)
|
|
1474
|
-
// },
|
|
1475
|
-
|
|
1476
|
-
// useLoaderDeps: (opts) => {
|
|
1477
|
-
// return useLoaderDeps({ ...opts, from: id } as any) as any
|
|
1478
|
-
// },
|
|
1479
|
-
|
|
1480
|
-
// useLoaderData: (opts) => {
|
|
1481
|
-
// return useLoaderData({ ...opts, from: id } as any) as any
|
|
1482
|
-
// },
|
|
1483
|
-
// }
|
|
1484
|
-
// }
|
|
1576
|
+
// import warning from 'tiny-warning'
|
|
1485
1577
|
|
|
1486
|
-
|
|
1487
|
-
constructor({
|
|
1488
|
-
id
|
|
1489
|
-
}) {
|
|
1490
|
-
this.id = id;
|
|
1491
|
-
}
|
|
1492
|
-
useMatch = opts => {
|
|
1493
|
-
return useMatch({
|
|
1494
|
-
select: opts?.select,
|
|
1495
|
-
from: this.id
|
|
1496
|
-
});
|
|
1497
|
-
};
|
|
1498
|
-
useRouteContext = opts => {
|
|
1499
|
-
return useMatch({
|
|
1500
|
-
from: this.id,
|
|
1501
|
-
select: d => opts?.select ? opts.select(d.context) : d.context
|
|
1502
|
-
});
|
|
1503
|
-
};
|
|
1504
|
-
useSearch = opts => {
|
|
1505
|
-
return useSearch({
|
|
1506
|
-
...opts,
|
|
1507
|
-
from: this.id
|
|
1508
|
-
});
|
|
1509
|
-
};
|
|
1510
|
-
useParams = opts => {
|
|
1511
|
-
return useParams({
|
|
1512
|
-
...opts,
|
|
1513
|
-
from: this.id
|
|
1514
|
-
});
|
|
1515
|
-
};
|
|
1516
|
-
useLoaderDeps = opts => {
|
|
1517
|
-
return useLoaderDeps({
|
|
1518
|
-
...opts,
|
|
1519
|
-
from: this.id
|
|
1520
|
-
});
|
|
1521
|
-
};
|
|
1522
|
-
useLoaderData = opts => {
|
|
1523
|
-
return useLoaderData({
|
|
1524
|
-
...opts,
|
|
1525
|
-
from: this.id
|
|
1526
|
-
});
|
|
1527
|
-
};
|
|
1528
|
-
}
|
|
1529
|
-
class Route {
|
|
1530
|
-
// Set up in this.init()
|
|
1578
|
+
//
|
|
1531
1579
|
|
|
1532
|
-
|
|
1580
|
+
const componentTypes = ['component', 'errorComponent', 'pendingComponent'];
|
|
1581
|
+
class Router {
|
|
1582
|
+
// Option-independent properties
|
|
1583
|
+
tempLocationKey = `${Math.round(Math.random() * 10000000)}`;
|
|
1584
|
+
resetNextScroll = true;
|
|
1585
|
+
navigateTimeout = null;
|
|
1586
|
+
latestLoadPromise = Promise.resolve();
|
|
1587
|
+
subscribers = new Set();
|
|
1588
|
+
injectedHtml = [];
|
|
1533
1589
|
|
|
1534
|
-
//
|
|
1590
|
+
// Must build in constructor
|
|
1535
1591
|
|
|
1536
1592
|
constructor(options) {
|
|
1537
|
-
this.
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1593
|
+
this.update({
|
|
1594
|
+
defaultPreloadDelay: 50,
|
|
1595
|
+
defaultPendingMs: 1000,
|
|
1596
|
+
defaultPendingMinMs: 500,
|
|
1597
|
+
context: undefined,
|
|
1598
|
+
...options,
|
|
1599
|
+
stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
|
|
1600
|
+
parseSearch: options?.parseSearch ?? defaultParseSearch
|
|
1601
|
+
});
|
|
1541
1602
|
}
|
|
1542
|
-
init = opts => {
|
|
1543
|
-
this.originalIndex = opts.originalIndex;
|
|
1544
|
-
const options = this.options;
|
|
1545
|
-
const isRoot = !options?.path && !options?.id;
|
|
1546
|
-
this.parentRoute = this.options?.getParentRoute?.();
|
|
1547
|
-
if (isRoot) {
|
|
1548
|
-
this.path = rootRouteId;
|
|
1549
|
-
} else {
|
|
1550
|
-
invariant(this.parentRoute, `Child Route instances must pass a 'getParentRoute: () => ParentRoute' option that returns a Route instance.`);
|
|
1551
|
-
}
|
|
1552
|
-
let path = isRoot ? rootRouteId : options.path;
|
|
1553
1603
|
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1604
|
+
// These are default implementations that can optionally be overridden
|
|
1605
|
+
// by the router provider once rendered. We provide these so that the
|
|
1606
|
+
// router can be used in a non-react environment if necessary
|
|
1607
|
+
startReactTransition = fn => fn();
|
|
1608
|
+
update = newOptions => {
|
|
1609
|
+
const previousOptions = this.options;
|
|
1610
|
+
this.options = {
|
|
1611
|
+
...this.options,
|
|
1612
|
+
...newOptions
|
|
1613
|
+
};
|
|
1614
|
+
if (!this.basepath || newOptions.basepath && newOptions.basepath !== previousOptions.basepath) {
|
|
1615
|
+
if (newOptions.basepath === undefined || newOptions.basepath === '' || newOptions.basepath === '/') {
|
|
1616
|
+
this.basepath = '/';
|
|
1617
|
+
} else {
|
|
1618
|
+
this.basepath = `/${trimPath(newOptions.basepath)}`;
|
|
1619
|
+
}
|
|
1557
1620
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
path = '/';
|
|
1621
|
+
if (!this.history || this.options.history && this.options.history !== this.history) {
|
|
1622
|
+
this.history = this.options.history ?? (typeof document !== 'undefined' ? createBrowserHistory() : createMemoryHistory({
|
|
1623
|
+
initialEntries: [this.options.basepath || '/']
|
|
1624
|
+
}));
|
|
1625
|
+
this.latestLocation = this.parseLocation();
|
|
1564
1626
|
}
|
|
1565
|
-
if (
|
|
1566
|
-
|
|
1627
|
+
if (this.options.routeTree !== this.routeTree) {
|
|
1628
|
+
this.routeTree = this.options.routeTree;
|
|
1629
|
+
this.buildRouteTree();
|
|
1630
|
+
}
|
|
1631
|
+
if (!this.__store) {
|
|
1632
|
+
this.__store = new Store(getInitialRouterState(this.latestLocation), {
|
|
1633
|
+
onUpdate: () => {
|
|
1634
|
+
this.__store.state = {
|
|
1635
|
+
...this.state,
|
|
1636
|
+
status: this.state.isTransitioning || this.state.isLoading ? 'pending' : 'idle'
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1567
1640
|
}
|
|
1568
|
-
const fullPath = id === rootRouteId ? '/' : joinPaths([this.parentRoute.fullPath, path]);
|
|
1569
|
-
this.path = path;
|
|
1570
|
-
this.id = id;
|
|
1571
|
-
// this.customId = customId as TCustomId
|
|
1572
|
-
this.fullPath = fullPath;
|
|
1573
|
-
this.to = fullPath;
|
|
1574
|
-
};
|
|
1575
|
-
addChildren = children => {
|
|
1576
|
-
this.children = children;
|
|
1577
|
-
return this;
|
|
1578
|
-
};
|
|
1579
|
-
updateLoader = options => {
|
|
1580
|
-
Object.assign(this.options, options);
|
|
1581
|
-
return this;
|
|
1582
1641
|
};
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1642
|
+
get state() {
|
|
1643
|
+
return this.__store.state;
|
|
1644
|
+
}
|
|
1645
|
+
buildRouteTree = () => {
|
|
1646
|
+
this.routesById = {};
|
|
1647
|
+
this.routesByPath = {};
|
|
1648
|
+
const notFoundRoute = this.options.notFoundRoute;
|
|
1649
|
+
if (notFoundRoute) {
|
|
1650
|
+
notFoundRoute.init({
|
|
1651
|
+
originalIndex: 99999999999
|
|
1652
|
+
});
|
|
1653
|
+
this.routesById[notFoundRoute.id] = notFoundRoute;
|
|
1654
|
+
}
|
|
1655
|
+
const recurseRoutes = childRoutes => {
|
|
1656
|
+
childRoutes.forEach((childRoute, i) => {
|
|
1657
|
+
childRoute.init({
|
|
1658
|
+
originalIndex: i
|
|
1659
|
+
});
|
|
1660
|
+
const existingRoute = this.routesById[childRoute.id];
|
|
1661
|
+
invariant(!existingRoute, `Duplicate routes found with id: ${String(childRoute.id)}`);
|
|
1662
|
+
this.routesById[childRoute.id] = childRoute;
|
|
1663
|
+
if (!childRoute.isRoot && childRoute.path) {
|
|
1664
|
+
const trimmedFullPath = trimPathRight(childRoute.fullPath);
|
|
1665
|
+
if (!this.routesByPath[trimmedFullPath] || childRoute.fullPath.endsWith('/')) {
|
|
1666
|
+
this.routesByPath[trimmedFullPath] = childRoute;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
const children = childRoute.children;
|
|
1670
|
+
if (children?.length) {
|
|
1671
|
+
recurseRoutes(children);
|
|
1672
|
+
}
|
|
1673
|
+
});
|
|
1674
|
+
};
|
|
1675
|
+
recurseRoutes([this.routeTree]);
|
|
1676
|
+
const scoredRoutes = [];
|
|
1677
|
+
Object.values(this.routesById).forEach((d, i) => {
|
|
1678
|
+
if (d.isRoot || !d.path) {
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
const trimmed = trimPathLeft(d.fullPath);
|
|
1682
|
+
const parsed = parsePathname(trimmed);
|
|
1683
|
+
while (parsed.length > 1 && parsed[0]?.value === '/') {
|
|
1684
|
+
parsed.shift();
|
|
1685
|
+
}
|
|
1686
|
+
const scores = parsed.map(d => {
|
|
1687
|
+
if (d.value === '/') {
|
|
1688
|
+
return 0.75;
|
|
1689
|
+
}
|
|
1690
|
+
if (d.type === 'param') {
|
|
1691
|
+
return 0.5;
|
|
1692
|
+
}
|
|
1693
|
+
if (d.type === 'wildcard') {
|
|
1694
|
+
return 0.25;
|
|
1695
|
+
}
|
|
1696
|
+
return 1;
|
|
1697
|
+
});
|
|
1698
|
+
scoredRoutes.push({
|
|
1699
|
+
child: d,
|
|
1700
|
+
trimmed,
|
|
1701
|
+
parsed,
|
|
1702
|
+
index: i,
|
|
1703
|
+
scores
|
|
1704
|
+
});
|
|
1591
1705
|
});
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1706
|
+
this.flatRoutes = scoredRoutes.sort((a, b) => {
|
|
1707
|
+
const minLength = Math.min(a.scores.length, b.scores.length);
|
|
1708
|
+
|
|
1709
|
+
// Sort by min available score
|
|
1710
|
+
for (let i = 0; i < minLength; i++) {
|
|
1711
|
+
if (a.scores[i] !== b.scores[i]) {
|
|
1712
|
+
return b.scores[i] - a.scores[i];
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// Sort by length of score
|
|
1717
|
+
if (a.scores.length !== b.scores.length) {
|
|
1718
|
+
return b.scores.length - a.scores.length;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// Sort by min available parsed value
|
|
1722
|
+
for (let i = 0; i < minLength; i++) {
|
|
1723
|
+
if (a.parsed[i].value !== b.parsed[i].value) {
|
|
1724
|
+
return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
// Sort by original index
|
|
1729
|
+
return a.index - b.index;
|
|
1730
|
+
}).map((d, i) => {
|
|
1731
|
+
d.child.rank = i;
|
|
1732
|
+
return d.child;
|
|
1598
1733
|
});
|
|
1599
1734
|
};
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
}
|
|
1735
|
+
subscribe = (eventType, fn) => {
|
|
1736
|
+
const listener = {
|
|
1737
|
+
eventType,
|
|
1738
|
+
fn
|
|
1739
|
+
};
|
|
1740
|
+
this.subscribers.add(listener);
|
|
1741
|
+
return () => {
|
|
1742
|
+
this.subscribers.delete(listener);
|
|
1743
|
+
};
|
|
1605
1744
|
};
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1745
|
+
emit = routerEvent => {
|
|
1746
|
+
this.subscribers.forEach(listener => {
|
|
1747
|
+
if (listener.eventType === routerEvent.type) {
|
|
1748
|
+
listener.fn(routerEvent);
|
|
1749
|
+
}
|
|
1610
1750
|
});
|
|
1611
1751
|
};
|
|
1612
|
-
|
|
1613
|
-
return
|
|
1614
|
-
...opts,
|
|
1615
|
-
from: this.id
|
|
1616
|
-
});
|
|
1752
|
+
checkLatest = promise => {
|
|
1753
|
+
return this.latestLoadPromise !== promise ? this.latestLoadPromise : undefined;
|
|
1617
1754
|
};
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1755
|
+
parseLocation = previousLocation => {
|
|
1756
|
+
const parse = ({
|
|
1757
|
+
pathname,
|
|
1758
|
+
search,
|
|
1759
|
+
hash,
|
|
1760
|
+
state
|
|
1761
|
+
}) => {
|
|
1762
|
+
const parsedSearch = this.options.parseSearch(search);
|
|
1763
|
+
return {
|
|
1764
|
+
pathname: pathname,
|
|
1765
|
+
searchStr: search,
|
|
1766
|
+
search: replaceEqualDeep(previousLocation?.search, parsedSearch),
|
|
1767
|
+
hash: hash.split('#').reverse()[0] ?? '',
|
|
1768
|
+
href: `${pathname}${search}${hash}`,
|
|
1769
|
+
state: replaceEqualDeep(previousLocation?.state, state)
|
|
1770
|
+
};
|
|
1771
|
+
};
|
|
1772
|
+
const location = parse(this.history.location);
|
|
1773
|
+
let {
|
|
1774
|
+
__tempLocation,
|
|
1775
|
+
__tempKey
|
|
1776
|
+
} = location.state;
|
|
1777
|
+
if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
|
|
1778
|
+
// Sync up the location keys
|
|
1779
|
+
const parsedTempLocation = parse(__tempLocation);
|
|
1780
|
+
parsedTempLocation.state.key = location.state.key;
|
|
1781
|
+
delete parsedTempLocation.state.__tempLocation;
|
|
1782
|
+
return {
|
|
1783
|
+
...parsedTempLocation,
|
|
1784
|
+
maskedLocation: location
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
return location;
|
|
1623
1788
|
};
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
return options => {
|
|
1627
|
-
return new RootRoute(options);
|
|
1789
|
+
resolvePathWithBase = (from, path) => {
|
|
1790
|
+
return resolvePath(this.basepath, from, cleanPath(path));
|
|
1628
1791
|
};
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
constructor(options) {
|
|
1632
|
-
super(options);
|
|
1792
|
+
get looseRoutesById() {
|
|
1793
|
+
return this.routesById;
|
|
1633
1794
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1795
|
+
matchRoutes = (pathname, locationSearch, opts) => {
|
|
1796
|
+
let routeParams = {};
|
|
1797
|
+
let foundRoute = this.flatRoutes.find(route => {
|
|
1798
|
+
const matchedParams = matchPathname(this.basepath, trimPathRight(pathname), {
|
|
1799
|
+
to: route.fullPath,
|
|
1800
|
+
caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive,
|
|
1801
|
+
fuzzy: true
|
|
1802
|
+
});
|
|
1803
|
+
if (matchedParams) {
|
|
1804
|
+
routeParams = matchedParams;
|
|
1805
|
+
return true;
|
|
1806
|
+
}
|
|
1807
|
+
return false;
|
|
1646
1808
|
});
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
class FileRoute {
|
|
1651
|
-
constructor(path) {
|
|
1652
|
-
this.path = path;
|
|
1653
|
-
}
|
|
1654
|
-
createRoute = options => {
|
|
1655
|
-
const route = new Route(options);
|
|
1656
|
-
route.isRoot = false;
|
|
1657
|
-
return route;
|
|
1658
|
-
};
|
|
1659
|
-
}
|
|
1660
|
-
function FileRouteLoader(_path) {
|
|
1661
|
-
return loaderFn => loaderFn;
|
|
1662
|
-
}
|
|
1809
|
+
let routeCursor = foundRoute || this.routesById['__root__'];
|
|
1810
|
+
let matchedRoutes = [routeCursor];
|
|
1663
1811
|
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1812
|
+
// Check to see if the route needs a 404 entry
|
|
1813
|
+
if (
|
|
1814
|
+
// If we found a route, and it's not an index route and we have left over path
|
|
1815
|
+
(foundRoute ? foundRoute.path !== '/' && routeParams['**'] :
|
|
1816
|
+
// Or if we didn't find a route and we have left over path
|
|
1817
|
+
trimPathRight(pathname)) &&
|
|
1818
|
+
// And we have a 404 route configured
|
|
1819
|
+
this.options.notFoundRoute) {
|
|
1820
|
+
matchedRoutes.push(this.options.notFoundRoute);
|
|
1821
|
+
}
|
|
1822
|
+
while (routeCursor?.parentRoute) {
|
|
1823
|
+
routeCursor = routeCursor.parentRoute;
|
|
1824
|
+
if (routeCursor) matchedRoutes.unshift(routeCursor);
|
|
1669
1825
|
}
|
|
1670
|
-
return loadPromise;
|
|
1671
|
-
};
|
|
1672
|
-
const lazyComp = /*#__PURE__*/React__namespace.lazy(async () => {
|
|
1673
|
-
const moduleExports = await load();
|
|
1674
|
-
const comp = moduleExports[exportName ?? 'default'];
|
|
1675
|
-
return {
|
|
1676
|
-
default: comp
|
|
1677
|
-
};
|
|
1678
|
-
});
|
|
1679
|
-
lazyComp.preload = load;
|
|
1680
|
-
return lazyComp;
|
|
1681
|
-
}
|
|
1682
1826
|
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1827
|
+
// Existing matches are matches that are already loaded along with
|
|
1828
|
+
// pending matches that are still loading
|
|
1829
|
+
|
|
1830
|
+
const parseErrors = matchedRoutes.map(route => {
|
|
1831
|
+
let parsedParamsError;
|
|
1832
|
+
if (route.options.parseParams) {
|
|
1833
|
+
try {
|
|
1834
|
+
const parsedParams = route.options.parseParams(routeParams);
|
|
1835
|
+
// Add the parsed params to the accumulated params bag
|
|
1836
|
+
Object.assign(routeParams, parsedParams);
|
|
1837
|
+
} catch (err) {
|
|
1838
|
+
parsedParamsError = new PathParamError(err.message, {
|
|
1839
|
+
cause: err
|
|
1840
|
+
});
|
|
1841
|
+
if (opts?.throwOnError) {
|
|
1842
|
+
throw parsedParamsError;
|
|
1843
|
+
}
|
|
1844
|
+
return parsedParamsError;
|
|
1690
1845
|
}
|
|
1691
1846
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
strict: false,
|
|
1703
|
-
select: s => s.pathname
|
|
1704
|
-
});
|
|
1705
|
-
const {
|
|
1706
|
-
// custom props
|
|
1707
|
-
children,
|
|
1708
|
-
target,
|
|
1709
|
-
activeProps = () => ({
|
|
1710
|
-
className: 'active'
|
|
1711
|
-
}),
|
|
1712
|
-
inactiveProps = () => ({}),
|
|
1713
|
-
activeOptions,
|
|
1714
|
-
disabled,
|
|
1715
|
-
hash,
|
|
1716
|
-
search,
|
|
1717
|
-
params,
|
|
1718
|
-
to,
|
|
1719
|
-
state,
|
|
1720
|
-
mask,
|
|
1721
|
-
preload: userPreload,
|
|
1722
|
-
preloadDelay: userPreloadDelay,
|
|
1723
|
-
replace,
|
|
1724
|
-
startTransition,
|
|
1725
|
-
resetScroll,
|
|
1726
|
-
// element props
|
|
1727
|
-
style,
|
|
1728
|
-
className,
|
|
1729
|
-
onClick,
|
|
1730
|
-
onFocus,
|
|
1731
|
-
onMouseEnter,
|
|
1732
|
-
onMouseLeave,
|
|
1733
|
-
onTouchStart,
|
|
1734
|
-
...rest
|
|
1735
|
-
} = options;
|
|
1736
|
-
|
|
1737
|
-
// If this link simply reloads the current route,
|
|
1738
|
-
// make sure it has a new key so it will trigger a data refresh
|
|
1847
|
+
return;
|
|
1848
|
+
});
|
|
1849
|
+
const matches = [];
|
|
1850
|
+
matchedRoutes.forEach((route, index) => {
|
|
1851
|
+
// Take each matched route and resolve + validate its search params
|
|
1852
|
+
// This has to happen serially because each route's search params
|
|
1853
|
+
// can depend on the parent route's search params
|
|
1854
|
+
// It must also happen before we create the match so that we can
|
|
1855
|
+
// pass the search params to the route's potential key function
|
|
1856
|
+
// which is used to uniquely identify the route match in state
|
|
1739
1857
|
|
|
1740
|
-
|
|
1741
|
-
|
|
1858
|
+
const parentMatch = matches[index - 1];
|
|
1859
|
+
const [preMatchSearch, searchError] = (() => {
|
|
1860
|
+
// Validate the search params and stabilize them
|
|
1861
|
+
const parentSearch = parentMatch?.search ?? locationSearch;
|
|
1862
|
+
try {
|
|
1863
|
+
const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
|
|
1864
|
+
let search = validator?.(parentSearch) ?? {};
|
|
1865
|
+
return [{
|
|
1866
|
+
...parentSearch,
|
|
1867
|
+
...search
|
|
1868
|
+
}, undefined];
|
|
1869
|
+
} catch (err) {
|
|
1870
|
+
const searchError = new SearchParamError(err.message, {
|
|
1871
|
+
cause: err
|
|
1872
|
+
});
|
|
1873
|
+
if (opts?.throwOnError) {
|
|
1874
|
+
throw searchError;
|
|
1875
|
+
}
|
|
1876
|
+
return [parentSearch, searchError];
|
|
1877
|
+
}
|
|
1878
|
+
})();
|
|
1742
1879
|
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
let type = 'internal';
|
|
1748
|
-
try {
|
|
1749
|
-
new URL(`${to}`);
|
|
1750
|
-
type = 'external';
|
|
1751
|
-
} catch {}
|
|
1752
|
-
if (type === 'external') {
|
|
1753
|
-
return {
|
|
1754
|
-
href: to
|
|
1755
|
-
};
|
|
1756
|
-
}
|
|
1757
|
-
const next = router.buildLocation(dest);
|
|
1758
|
-
const preload = userPreload ?? router.options.defaultPreload;
|
|
1759
|
-
const preloadDelay = userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0;
|
|
1760
|
-
const isActive = useRouterState({
|
|
1761
|
-
select: s => {
|
|
1762
|
-
// Compare path/hash for matches
|
|
1763
|
-
const currentPathSplit = s.location.pathname.split('/');
|
|
1764
|
-
const nextPathSplit = next.pathname.split('/');
|
|
1765
|
-
const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
|
|
1766
|
-
// Combine the matches based on user router.options
|
|
1767
|
-
const pathTest = activeOptions?.exact ? s.location.pathname === next.pathname : pathIsFuzzyEqual;
|
|
1768
|
-
const hashTest = activeOptions?.includeHash ? s.location.hash === next.hash : true;
|
|
1769
|
-
const searchTest = activeOptions?.includeSearch ?? true ? deepEqual(s.location.search, next.search, !activeOptions?.exact) : true;
|
|
1880
|
+
// This is where we need to call route.options.loaderDeps() to get any additional
|
|
1881
|
+
// deps that the route's loader function might need to run. We need to do this
|
|
1882
|
+
// before we create the match so that we can pass the deps to the route's
|
|
1883
|
+
// potential key function which is used to uniquely identify the route match in state
|
|
1770
1884
|
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1885
|
+
const loaderDeps = route.options.loaderDeps?.({
|
|
1886
|
+
search: preMatchSearch
|
|
1887
|
+
}) ?? '';
|
|
1888
|
+
const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : '';
|
|
1889
|
+
const interpolatedPath = interpolatePath(route.fullPath, routeParams);
|
|
1890
|
+
const matchId = interpolatePath(route.id, routeParams, true) + loaderDepsHash;
|
|
1775
1891
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1892
|
+
// Waste not, want not. If we already have a match for this route,
|
|
1893
|
+
// reuse it. This is important for layout routes, which might stick
|
|
1894
|
+
// around between navigation actions that only change leaf routes.
|
|
1895
|
+
const existingMatch = getRouteMatch(this.state, matchId);
|
|
1896
|
+
const cause = this.state.matches.find(d => d.id === matchId) ? 'stay' : 'enter';
|
|
1780
1897
|
|
|
1781
|
-
//
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1898
|
+
// Create a fresh route match
|
|
1899
|
+
const hasLoaders = !!(route.options.loader || componentTypes.some(d => route.options[d]?.preload));
|
|
1900
|
+
const match = existingMatch ? {
|
|
1901
|
+
...existingMatch,
|
|
1902
|
+
cause
|
|
1903
|
+
} : {
|
|
1904
|
+
id: matchId,
|
|
1905
|
+
routeId: route.id,
|
|
1906
|
+
params: routeParams,
|
|
1907
|
+
pathname: joinPaths([this.basepath, interpolatedPath]),
|
|
1908
|
+
updatedAt: Date.now(),
|
|
1909
|
+
search: {},
|
|
1910
|
+
searchError: undefined,
|
|
1911
|
+
status: hasLoaders ? 'pending' : 'success',
|
|
1912
|
+
showPending: false,
|
|
1913
|
+
isFetching: false,
|
|
1914
|
+
error: undefined,
|
|
1915
|
+
paramsError: parseErrors[index],
|
|
1916
|
+
loadPromise: Promise.resolve(),
|
|
1917
|
+
routeContext: undefined,
|
|
1918
|
+
context: undefined,
|
|
1919
|
+
abortController: new AbortController(),
|
|
1920
|
+
fetchCount: 0,
|
|
1921
|
+
cause,
|
|
1922
|
+
loaderDeps,
|
|
1923
|
+
invalid: false,
|
|
1924
|
+
preload: false
|
|
1925
|
+
};
|
|
1790
1926
|
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
};
|
|
1800
|
-
const handleTouchStart = e => {
|
|
1801
|
-
if (preload) {
|
|
1802
|
-
router.preloadRoute(dest).catch(err => {
|
|
1803
|
-
console.warn(err);
|
|
1804
|
-
console.warn(preloadWarning);
|
|
1805
|
-
});
|
|
1806
|
-
}
|
|
1807
|
-
};
|
|
1808
|
-
const handleEnter = e => {
|
|
1809
|
-
const target = e.target || {};
|
|
1810
|
-
if (preload) {
|
|
1811
|
-
if (target.preloadTimeout) {
|
|
1812
|
-
return;
|
|
1813
|
-
}
|
|
1814
|
-
target.preloadTimeout = setTimeout(() => {
|
|
1815
|
-
target.preloadTimeout = null;
|
|
1816
|
-
router.preloadRoute(dest).catch(err => {
|
|
1817
|
-
console.warn(err);
|
|
1818
|
-
console.warn(preloadWarning);
|
|
1819
|
-
});
|
|
1820
|
-
}, preloadDelay);
|
|
1821
|
-
}
|
|
1927
|
+
// Regardless of whether we're reusing an existing match or creating
|
|
1928
|
+
// a new one, we need to update the match's search params
|
|
1929
|
+
match.search = replaceEqualDeep(match.search, preMatchSearch);
|
|
1930
|
+
// And also update the searchError if there is one
|
|
1931
|
+
match.searchError = searchError;
|
|
1932
|
+
matches.push(match);
|
|
1933
|
+
});
|
|
1934
|
+
return matches;
|
|
1822
1935
|
};
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
if (target.preloadTimeout) {
|
|
1826
|
-
clearTimeout(target.preloadTimeout);
|
|
1827
|
-
target.preloadTimeout = null;
|
|
1828
|
-
}
|
|
1936
|
+
cancelMatch = id => {
|
|
1937
|
+
getRouteMatch(this.state, id)?.abortController?.abort();
|
|
1829
1938
|
};
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
if (e.defaultPrevented) return;
|
|
1834
|
-
handler(e);
|
|
1939
|
+
cancelMatches = () => {
|
|
1940
|
+
this.state.pendingMatches?.forEach(match => {
|
|
1941
|
+
this.cancelMatch(match.id);
|
|
1835
1942
|
});
|
|
1836
1943
|
};
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
...style,
|
|
1856
|
-
...resolvedActiveProps.style,
|
|
1857
|
-
...resolvedInactiveProps.style
|
|
1858
|
-
},
|
|
1859
|
-
className: [className, resolvedActiveProps.className, resolvedInactiveProps.className].filter(Boolean).join(' ') || undefined,
|
|
1860
|
-
...(disabled ? {
|
|
1861
|
-
role: 'link',
|
|
1862
|
-
'aria-disabled': true
|
|
1863
|
-
} : undefined),
|
|
1864
|
-
['data-status']: isActive ? 'active' : undefined
|
|
1865
|
-
};
|
|
1866
|
-
}
|
|
1867
|
-
const Link = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
|
|
1868
|
-
const linkProps = useLinkProps(props);
|
|
1869
|
-
return /*#__PURE__*/React__namespace.createElement("a", _extends({
|
|
1870
|
-
ref: ref
|
|
1871
|
-
}, linkProps, {
|
|
1872
|
-
children: typeof props.children === 'function' ? props.children({
|
|
1873
|
-
isActive: linkProps['data-status'] === 'active'
|
|
1874
|
-
}) : props.children
|
|
1875
|
-
}));
|
|
1876
|
-
});
|
|
1877
|
-
function isCtrlEvent(e) {
|
|
1878
|
-
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
// @ts-nocheck
|
|
1882
|
-
|
|
1883
|
-
// 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.
|
|
1884
|
-
|
|
1885
|
-
function encode(obj, pfx) {
|
|
1886
|
-
var k,
|
|
1887
|
-
i,
|
|
1888
|
-
tmp,
|
|
1889
|
-
str = '';
|
|
1890
|
-
for (k in obj) {
|
|
1891
|
-
if ((tmp = obj[k]) !== void 0) {
|
|
1892
|
-
if (Array.isArray(tmp)) {
|
|
1893
|
-
for (i = 0; i < tmp.length; i++) {
|
|
1894
|
-
str && (str += '&');
|
|
1895
|
-
str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp[i]);
|
|
1896
|
-
}
|
|
1897
|
-
} else {
|
|
1898
|
-
str && (str += '&');
|
|
1899
|
-
str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp);
|
|
1944
|
+
buildLocation = opts => {
|
|
1945
|
+
const build = (dest = {}, matches) => {
|
|
1946
|
+
const relevantMatches = this.state.pendingMatches || this.state.matches;
|
|
1947
|
+
const fromSearch = relevantMatches[relevantMatches.length - 1]?.search || this.latestLocation.search;
|
|
1948
|
+
let pathname = this.resolvePathWithBase(dest.from ?? this.latestLocation.pathname, `${dest.to ?? ''}`);
|
|
1949
|
+
const fromMatches = this.matchRoutes(this.latestLocation.pathname, fromSearch);
|
|
1950
|
+
const stayingMatches = matches?.filter(d => fromMatches?.find(e => e.routeId === d.routeId));
|
|
1951
|
+
const prevParams = {
|
|
1952
|
+
...last(fromMatches)?.params
|
|
1953
|
+
};
|
|
1954
|
+
let nextParams = (dest.params ?? true) === true ? prevParams : functionalUpdate(dest.params, prevParams);
|
|
1955
|
+
if (nextParams) {
|
|
1956
|
+
matches?.map(d => this.looseRoutesById[d.routeId].options.stringifyParams).filter(Boolean).forEach(fn => {
|
|
1957
|
+
nextParams = {
|
|
1958
|
+
...nextParams,
|
|
1959
|
+
...fn(nextParams)
|
|
1960
|
+
};
|
|
1961
|
+
});
|
|
1900
1962
|
}
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
}
|
|
1905
|
-
function toValue(mix) {
|
|
1906
|
-
if (!mix) return '';
|
|
1907
|
-
var str = decodeURIComponent(mix);
|
|
1908
|
-
if (str === 'false') return false;
|
|
1909
|
-
if (str === 'true') return true;
|
|
1910
|
-
return +str * 0 === 0 && +str + '' === str ? +str : str;
|
|
1911
|
-
}
|
|
1912
|
-
function decode(str) {
|
|
1913
|
-
var tmp,
|
|
1914
|
-
k,
|
|
1915
|
-
out = {},
|
|
1916
|
-
arr = str.split('&');
|
|
1917
|
-
while (tmp = arr.shift()) {
|
|
1918
|
-
tmp = tmp.split('=');
|
|
1919
|
-
k = tmp.shift();
|
|
1920
|
-
if (out[k] !== void 0) {
|
|
1921
|
-
out[k] = [].concat(out[k], toValue(tmp.shift()));
|
|
1922
|
-
} else {
|
|
1923
|
-
out[k] = toValue(tmp.shift());
|
|
1924
|
-
}
|
|
1925
|
-
}
|
|
1926
|
-
return out;
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
// Detect if we're in the DOM
|
|
1963
|
+
pathname = interpolatePath(pathname, nextParams ?? {});
|
|
1964
|
+
const preSearchFilters = stayingMatches?.map(match => this.looseRoutesById[match.routeId].options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
|
|
1965
|
+
const postSearchFilters = stayingMatches?.map(match => this.looseRoutesById[match.routeId].options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
|
|
1930
1966
|
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
if (opts.throw) {
|
|
1934
|
-
throw opts;
|
|
1935
|
-
}
|
|
1936
|
-
return opts;
|
|
1937
|
-
}
|
|
1938
|
-
function isRedirect(obj) {
|
|
1939
|
-
return !!obj?.isRedirect;
|
|
1940
|
-
}
|
|
1967
|
+
// Pre filters first
|
|
1968
|
+
const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), fromSearch) : fromSearch;
|
|
1941
1969
|
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
searchStr = searchStr.substring(1);
|
|
1948
|
-
}
|
|
1949
|
-
let query = decode(searchStr);
|
|
1970
|
+
// Then the link/navigate function
|
|
1971
|
+
const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
|
|
1972
|
+
: dest.search ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1973
|
+
: preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
|
|
1974
|
+
: {};
|
|
1950
1975
|
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
const
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1976
|
+
// Then post filters
|
|
1977
|
+
const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
|
|
1978
|
+
const search = replaceEqualDeep(fromSearch, postFilteredSearch);
|
|
1979
|
+
const searchStr = this.options.stringifySearch(search);
|
|
1980
|
+
const hash = dest.hash === true ? this.latestLocation.hash : dest.hash ? functionalUpdate(dest.hash, this.latestLocation.hash) : undefined;
|
|
1981
|
+
const hashStr = hash ? `#${hash}` : '';
|
|
1982
|
+
let nextState = dest.state === true ? this.latestLocation.state : dest.state ? functionalUpdate(dest.state, this.latestLocation.state) : this.latestLocation.state;
|
|
1983
|
+
nextState = replaceEqualDeep(this.latestLocation.state, nextState);
|
|
1984
|
+
return {
|
|
1985
|
+
pathname,
|
|
1986
|
+
search,
|
|
1987
|
+
searchStr,
|
|
1988
|
+
state: nextState,
|
|
1989
|
+
hash: hash ?? '',
|
|
1990
|
+
href: `${pathname}${searchStr}${hashStr}`,
|
|
1991
|
+
unmaskOnReload: dest.unmaskOnReload
|
|
1992
|
+
};
|
|
1993
|
+
};
|
|
1994
|
+
const buildWithMatches = (dest = {}, maskedDest) => {
|
|
1995
|
+
let next = build(dest);
|
|
1996
|
+
let maskedNext = maskedDest ? build(maskedDest) : undefined;
|
|
1997
|
+
if (!maskedNext) {
|
|
1998
|
+
let params = {};
|
|
1999
|
+
let foundMask = this.options.routeMasks?.find(d => {
|
|
2000
|
+
const match = matchPathname(this.basepath, next.pathname, {
|
|
2001
|
+
to: d.from,
|
|
2002
|
+
caseSensitive: false,
|
|
2003
|
+
fuzzy: false
|
|
2004
|
+
});
|
|
2005
|
+
if (match) {
|
|
2006
|
+
params = match;
|
|
2007
|
+
return true;
|
|
2008
|
+
}
|
|
2009
|
+
return false;
|
|
2010
|
+
});
|
|
2011
|
+
if (foundMask) {
|
|
2012
|
+
maskedDest = {
|
|
2013
|
+
...pick(opts, ['from']),
|
|
2014
|
+
...foundMask,
|
|
2015
|
+
params
|
|
2016
|
+
};
|
|
2017
|
+
maskedNext = build(maskedDest);
|
|
1959
2018
|
}
|
|
1960
2019
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
if (typeof val === 'object' && val !== null) {
|
|
1968
|
-
try {
|
|
1969
|
-
return stringify(val);
|
|
1970
|
-
} catch (err) {
|
|
1971
|
-
// silent
|
|
1972
|
-
}
|
|
1973
|
-
} else if (typeof val === 'string' && typeof parser === 'function') {
|
|
1974
|
-
try {
|
|
1975
|
-
// Check if it's a valid parseable string.
|
|
1976
|
-
// If it is, then stringify it again.
|
|
1977
|
-
parser(val);
|
|
1978
|
-
return stringify(val);
|
|
1979
|
-
} catch (err) {
|
|
1980
|
-
// silent
|
|
2020
|
+
const nextMatches = this.matchRoutes(next.pathname, next.search);
|
|
2021
|
+
const maskedMatches = maskedNext ? this.matchRoutes(maskedNext.pathname, maskedNext.search) : undefined;
|
|
2022
|
+
const maskedFinal = maskedNext ? build(maskedDest, maskedMatches) : undefined;
|
|
2023
|
+
const final = build(dest, nextMatches);
|
|
2024
|
+
if (maskedFinal) {
|
|
2025
|
+
final.maskedLocation = maskedFinal;
|
|
1981
2026
|
}
|
|
1982
|
-
|
|
1983
|
-
return val;
|
|
1984
|
-
}
|
|
1985
|
-
return search => {
|
|
1986
|
-
search = {
|
|
1987
|
-
...search
|
|
2027
|
+
return final;
|
|
1988
2028
|
};
|
|
1989
|
-
if (
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
delete search[key];
|
|
1994
|
-
} else {
|
|
1995
|
-
search[key] = stringifyValue(val);
|
|
1996
|
-
}
|
|
2029
|
+
if (opts.mask) {
|
|
2030
|
+
return buildWithMatches(opts, {
|
|
2031
|
+
...pick(opts, ['from']),
|
|
2032
|
+
...opts.mask
|
|
1997
2033
|
});
|
|
1998
2034
|
}
|
|
1999
|
-
|
|
2000
|
-
return searchStr ? `?${searchStr}` : '';
|
|
2035
|
+
return buildWithMatches(opts);
|
|
2001
2036
|
};
|
|
2002
|
-
|
|
2037
|
+
commitLocation = async ({
|
|
2038
|
+
startTransition,
|
|
2039
|
+
...next
|
|
2040
|
+
}) => {
|
|
2041
|
+
if (this.navigateTimeout) clearTimeout(this.navigateTimeout);
|
|
2042
|
+
const isSameUrl = this.latestLocation.href === next.href;
|
|
2003
2043
|
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
const routerState = useRouterState({
|
|
2033
|
-
select: s => pick(s, ['isLoading', 'location', 'resolvedLocation', 'isTransitioning'])
|
|
2034
|
-
});
|
|
2035
|
-
const [isTransitioning, startReactTransition] = useTransition();
|
|
2036
|
-
router.startReactTransition = startReactTransition;
|
|
2037
|
-
React__namespace.useEffect(() => {
|
|
2038
|
-
if (isTransitioning) {
|
|
2039
|
-
router.__store.setState(s => ({
|
|
2040
|
-
...s,
|
|
2041
|
-
isTransitioning
|
|
2042
|
-
}));
|
|
2043
|
-
}
|
|
2044
|
-
}, [isTransitioning]);
|
|
2045
|
-
const tryLoad = () => {
|
|
2046
|
-
const apply = cb => {
|
|
2047
|
-
if (!routerState.isTransitioning) {
|
|
2048
|
-
startReactTransition(() => cb());
|
|
2049
|
-
} else {
|
|
2050
|
-
cb();
|
|
2044
|
+
// If the next urls are the same and we're not replacing,
|
|
2045
|
+
// do nothing
|
|
2046
|
+
if (!isSameUrl || !next.replace) {
|
|
2047
|
+
let {
|
|
2048
|
+
maskedLocation,
|
|
2049
|
+
...nextHistory
|
|
2050
|
+
} = next;
|
|
2051
|
+
if (maskedLocation) {
|
|
2052
|
+
nextHistory = {
|
|
2053
|
+
...maskedLocation,
|
|
2054
|
+
state: {
|
|
2055
|
+
...maskedLocation.state,
|
|
2056
|
+
__tempKey: undefined,
|
|
2057
|
+
__tempLocation: {
|
|
2058
|
+
...nextHistory,
|
|
2059
|
+
search: nextHistory.searchStr,
|
|
2060
|
+
state: {
|
|
2061
|
+
...nextHistory.state,
|
|
2062
|
+
__tempKey: undefined,
|
|
2063
|
+
__tempLocation: undefined,
|
|
2064
|
+
key: undefined
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
};
|
|
2069
|
+
if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
|
|
2070
|
+
nextHistory.state.__tempKey = this.tempLocationKey;
|
|
2071
|
+
}
|
|
2051
2072
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2073
|
+
const apply = () => {
|
|
2074
|
+
this.history[next.replace ? 'replace' : 'push'](nextHistory.href, nextHistory.state);
|
|
2075
|
+
};
|
|
2076
|
+
if (startTransition ?? true) {
|
|
2077
|
+
this.startReactTransition(apply);
|
|
2078
|
+
} else {
|
|
2079
|
+
apply();
|
|
2058
2080
|
}
|
|
2059
|
-
}
|
|
2081
|
+
}
|
|
2082
|
+
this.resetNextScroll = next.resetScroll ?? true;
|
|
2083
|
+
return this.latestLoadPromise;
|
|
2060
2084
|
};
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2085
|
+
buildAndCommitLocation = ({
|
|
2086
|
+
replace,
|
|
2087
|
+
resetScroll,
|
|
2088
|
+
startTransition,
|
|
2089
|
+
...rest
|
|
2090
|
+
} = {}) => {
|
|
2091
|
+
const location = this.buildLocation(rest);
|
|
2092
|
+
return this.commitLocation({
|
|
2093
|
+
...location,
|
|
2094
|
+
startTransition,
|
|
2095
|
+
replace,
|
|
2096
|
+
resetScroll
|
|
2067
2097
|
});
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2098
|
+
};
|
|
2099
|
+
navigate = ({
|
|
2100
|
+
from,
|
|
2101
|
+
to,
|
|
2102
|
+
...rest
|
|
2103
|
+
}) => {
|
|
2104
|
+
// If this link simply reloads the current route,
|
|
2105
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
2106
|
+
|
|
2107
|
+
// If this `to` is a valid external URL, return
|
|
2108
|
+
// null for LinkUtils
|
|
2109
|
+
const toString = String(to);
|
|
2110
|
+
// const fromString = from !== undefined ? String(from) : from
|
|
2111
|
+
let isExternal;
|
|
2112
|
+
try {
|
|
2113
|
+
new URL(`${toString}`);
|
|
2114
|
+
isExternal = true;
|
|
2115
|
+
} catch (e) {}
|
|
2116
|
+
invariant(!isExternal, 'Attempting to navigate to external url with this.navigate!');
|
|
2117
|
+
return this.buildAndCommitLocation({
|
|
2118
|
+
...rest,
|
|
2119
|
+
from,
|
|
2120
|
+
to
|
|
2121
|
+
// to: toString,
|
|
2073
2122
|
});
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2123
|
+
};
|
|
2124
|
+
loadMatches = async ({
|
|
2125
|
+
checkLatest,
|
|
2126
|
+
matches,
|
|
2127
|
+
preload
|
|
2128
|
+
}) => {
|
|
2129
|
+
let latestPromise;
|
|
2130
|
+
let firstBadMatchIndex;
|
|
2131
|
+
const updateMatch = match => {
|
|
2132
|
+
// const isPreload = this.state.cachedMatches.find((d) => d.id === match.id)
|
|
2133
|
+
const isPending = this.state.pendingMatches?.find(d => d.id === match.id);
|
|
2134
|
+
const isMatched = this.state.matches.find(d => d.id === match.id);
|
|
2135
|
+
const matchesKey = isPending ? 'pendingMatches' : isMatched ? 'matches' : 'cachedMatches';
|
|
2136
|
+
this.__store.setState(s => ({
|
|
2137
|
+
...s,
|
|
2138
|
+
[matchesKey]: s[matchesKey]?.map(d => d.id === match.id ? match : d)
|
|
2139
|
+
}));
|
|
2082
2140
|
};
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2141
|
+
|
|
2142
|
+
// Check each match middleware to see if the route can be accessed
|
|
2143
|
+
try {
|
|
2144
|
+
for (let [index, match] of matches.entries()) {
|
|
2145
|
+
const parentMatch = matches[index - 1];
|
|
2146
|
+
const route = this.looseRoutesById[match.routeId];
|
|
2147
|
+
const abortController = new AbortController();
|
|
2148
|
+
const handleErrorAndRedirect = (err, code) => {
|
|
2149
|
+
err.routerCode = code;
|
|
2150
|
+
firstBadMatchIndex = firstBadMatchIndex ?? index;
|
|
2151
|
+
if (isRedirect(err)) {
|
|
2152
|
+
throw err;
|
|
2153
|
+
}
|
|
2154
|
+
try {
|
|
2155
|
+
route.options.onError?.(err);
|
|
2156
|
+
} catch (errorHandlerErr) {
|
|
2157
|
+
err = errorHandlerErr;
|
|
2158
|
+
if (isRedirect(errorHandlerErr)) {
|
|
2159
|
+
throw errorHandlerErr;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
matches[index] = match = {
|
|
2163
|
+
...match,
|
|
2164
|
+
error: err,
|
|
2165
|
+
status: 'error',
|
|
2166
|
+
updatedAt: Date.now(),
|
|
2167
|
+
abortController: new AbortController()
|
|
2168
|
+
};
|
|
2169
|
+
};
|
|
2170
|
+
try {
|
|
2171
|
+
if (match.paramsError) {
|
|
2172
|
+
handleErrorAndRedirect(match.paramsError, 'PARSE_PARAMS');
|
|
2173
|
+
}
|
|
2174
|
+
if (match.searchError) {
|
|
2175
|
+
handleErrorAndRedirect(match.searchError, 'VALIDATE_SEARCH');
|
|
2176
|
+
}
|
|
2177
|
+
const parentContext = parentMatch?.context ?? this.options.context ?? {};
|
|
2178
|
+
const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
|
|
2179
|
+
const pendingPromise = typeof pendingMs === 'number' && pendingMs <= 0 ? Promise.resolve() : new Promise(r => setTimeout(r, pendingMs));
|
|
2180
|
+
const beforeLoadContext = (await route.options.beforeLoad?.({
|
|
2181
|
+
search: match.search,
|
|
2182
|
+
abortController,
|
|
2183
|
+
params: match.params,
|
|
2184
|
+
preload: !!preload,
|
|
2185
|
+
context: parentContext,
|
|
2186
|
+
location: this.state.location,
|
|
2187
|
+
// TOOD: just expose state and router, etc
|
|
2188
|
+
navigate: opts => this.navigate({
|
|
2189
|
+
...opts,
|
|
2190
|
+
from: match.pathname
|
|
2191
|
+
}),
|
|
2192
|
+
buildLocation: this.buildLocation,
|
|
2193
|
+
cause: preload ? 'preload' : match.cause
|
|
2194
|
+
})) ?? {};
|
|
2195
|
+
if (isRedirect(beforeLoadContext)) {
|
|
2196
|
+
throw beforeLoadContext;
|
|
2097
2197
|
}
|
|
2198
|
+
const context = {
|
|
2199
|
+
...parentContext,
|
|
2200
|
+
...beforeLoadContext
|
|
2201
|
+
};
|
|
2202
|
+
matches[index] = match = {
|
|
2203
|
+
...match,
|
|
2204
|
+
routeContext: replaceEqualDeep(match.routeContext, beforeLoadContext),
|
|
2205
|
+
context: replaceEqualDeep(match.context, context),
|
|
2206
|
+
abortController,
|
|
2207
|
+
pendingPromise
|
|
2208
|
+
};
|
|
2209
|
+
} catch (err) {
|
|
2210
|
+
handleErrorAndRedirect(err, 'BEFORE_LOAD');
|
|
2211
|
+
break;
|
|
2098
2212
|
}
|
|
2099
2213
|
}
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
}, [routerState.isTransitioning, isTransitioning, routerState.isLoading, routerState.resolvedLocation, routerState.location]);
|
|
2107
|
-
useLayoutEffect$1(() => {
|
|
2108
|
-
if (!window.__TSR_DEHYDRATED__ && !mountLoadCount.current) {
|
|
2109
|
-
mountLoadCount.current++;
|
|
2110
|
-
tryLoad();
|
|
2214
|
+
} catch (err) {
|
|
2215
|
+
if (isRedirect(err)) {
|
|
2216
|
+
if (!preload) this.navigate(err);
|
|
2217
|
+
return matches;
|
|
2218
|
+
}
|
|
2219
|
+
throw err;
|
|
2111
2220
|
}
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2221
|
+
const validResolvedMatches = matches.slice(0, firstBadMatchIndex);
|
|
2222
|
+
const matchPromises = [];
|
|
2223
|
+
validResolvedMatches.forEach((match, index) => {
|
|
2224
|
+
matchPromises.push(new Promise(async resolve => {
|
|
2225
|
+
const parentMatchPromise = matchPromises[index - 1];
|
|
2226
|
+
const route = this.looseRoutesById[match.routeId];
|
|
2227
|
+
const handleErrorAndRedirect = err => {
|
|
2228
|
+
if (isRedirect(err)) {
|
|
2229
|
+
if (!preload) {
|
|
2230
|
+
this.navigate(err);
|
|
2231
|
+
}
|
|
2232
|
+
return true;
|
|
2233
|
+
}
|
|
2234
|
+
return false;
|
|
2235
|
+
};
|
|
2236
|
+
let loadPromise;
|
|
2237
|
+
matches[index] = match = {
|
|
2238
|
+
...match,
|
|
2239
|
+
showPending: false
|
|
2240
|
+
};
|
|
2241
|
+
let didShowPending = false;
|
|
2242
|
+
const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
|
|
2243
|
+
const pendingMinMs = route.options.pendingMinMs ?? this.options.defaultPendingMinMs;
|
|
2244
|
+
const shouldPending = !preload && typeof pendingMs === 'number' && (route.options.pendingComponent ?? this.options.defaultPendingComponent);
|
|
2245
|
+
const loaderContext = {
|
|
2246
|
+
params: match.params,
|
|
2247
|
+
deps: match.loaderDeps,
|
|
2248
|
+
preload: !!preload,
|
|
2249
|
+
parentMatchPromise,
|
|
2250
|
+
abortController: match.abortController,
|
|
2251
|
+
context: match.context,
|
|
2252
|
+
location: this.state.location,
|
|
2253
|
+
navigate: opts => this.navigate({
|
|
2254
|
+
...opts,
|
|
2255
|
+
from: match.pathname
|
|
2256
|
+
}),
|
|
2257
|
+
cause: preload ? 'preload' : match.cause
|
|
2258
|
+
};
|
|
2259
|
+
const fetch = async () => {
|
|
2260
|
+
if (match.isFetching) {
|
|
2261
|
+
loadPromise = getRouteMatch(this.state, match.id)?.loadPromise;
|
|
2262
|
+
} else {
|
|
2263
|
+
// If the user doesn't want the route to reload, just
|
|
2264
|
+
// resolve with the existing loader data
|
|
2134
2265
|
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
defaultPendingMs: 1000,
|
|
2139
|
-
defaultPendingMinMs: 500,
|
|
2140
|
-
context: undefined,
|
|
2141
|
-
...options,
|
|
2142
|
-
stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
|
|
2143
|
-
parseSearch: options?.parseSearch ?? defaultParseSearch
|
|
2144
|
-
});
|
|
2145
|
-
}
|
|
2266
|
+
if (match.fetchCount && match.status === 'success') {
|
|
2267
|
+
resolve();
|
|
2268
|
+
}
|
|
2146
2269
|
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
this.history = this.options.history ?? (typeof document !== 'undefined' ? createBrowserHistory() : createMemoryHistory({
|
|
2166
|
-
initialEntries: [this.options.basepath || '/']
|
|
2167
|
-
}));
|
|
2168
|
-
this.latestLocation = this.parseLocation();
|
|
2169
|
-
}
|
|
2170
|
-
if (this.options.routeTree !== this.routeTree) {
|
|
2171
|
-
this.routeTree = this.options.routeTree;
|
|
2172
|
-
this.buildRouteTree();
|
|
2173
|
-
}
|
|
2174
|
-
if (!this.__store) {
|
|
2175
|
-
this.__store = new Store(getInitialRouterState(this.latestLocation), {
|
|
2176
|
-
onUpdate: () => {
|
|
2177
|
-
this.__store.state = {
|
|
2178
|
-
...this.state,
|
|
2179
|
-
status: this.state.isTransitioning || this.state.isLoading ? 'pending' : 'idle'
|
|
2270
|
+
// Otherwise, load the route
|
|
2271
|
+
matches[index] = match = {
|
|
2272
|
+
...match,
|
|
2273
|
+
isFetching: true,
|
|
2274
|
+
fetchCount: match.fetchCount + 1
|
|
2275
|
+
};
|
|
2276
|
+
const componentsPromise = Promise.all(componentTypes.map(async type => {
|
|
2277
|
+
const component = route.options[type];
|
|
2278
|
+
if (component?.preload) {
|
|
2279
|
+
await component.preload();
|
|
2280
|
+
}
|
|
2281
|
+
}));
|
|
2282
|
+
const loaderPromise = route.options.loader?.(loaderContext);
|
|
2283
|
+
loadPromise = Promise.all([componentsPromise, loaderPromise]).then(d => d[1]);
|
|
2284
|
+
}
|
|
2285
|
+
matches[index] = match = {
|
|
2286
|
+
...match,
|
|
2287
|
+
loadPromise
|
|
2180
2288
|
};
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2289
|
+
updateMatch(match);
|
|
2290
|
+
try {
|
|
2291
|
+
const loaderData = await loadPromise;
|
|
2292
|
+
if (latestPromise = checkLatest()) return await latestPromise;
|
|
2293
|
+
if (isRedirect(loaderData)) {
|
|
2294
|
+
if (handleErrorAndRedirect(loaderData)) return;
|
|
2295
|
+
}
|
|
2296
|
+
if (didShowPending && pendingMinMs) {
|
|
2297
|
+
await new Promise(r => setTimeout(r, pendingMinMs));
|
|
2298
|
+
}
|
|
2299
|
+
if (latestPromise = checkLatest()) return await latestPromise;
|
|
2300
|
+
matches[index] = match = {
|
|
2301
|
+
...match,
|
|
2302
|
+
error: undefined,
|
|
2303
|
+
status: 'success',
|
|
2304
|
+
isFetching: false,
|
|
2305
|
+
updatedAt: Date.now(),
|
|
2306
|
+
loaderData,
|
|
2307
|
+
loadPromise: undefined
|
|
2308
|
+
};
|
|
2309
|
+
} catch (error) {
|
|
2310
|
+
if (latestPromise = checkLatest()) return await latestPromise;
|
|
2311
|
+
if (handleErrorAndRedirect(error)) return;
|
|
2312
|
+
try {
|
|
2313
|
+
route.options.onError?.(error);
|
|
2314
|
+
} catch (onErrorError) {
|
|
2315
|
+
error = onErrorError;
|
|
2316
|
+
if (handleErrorAndRedirect(onErrorError)) return;
|
|
2317
|
+
}
|
|
2318
|
+
matches[index] = match = {
|
|
2319
|
+
...match,
|
|
2320
|
+
error,
|
|
2321
|
+
status: 'error',
|
|
2322
|
+
isFetching: false
|
|
2323
|
+
};
|
|
2210
2324
|
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
if (children?.length) {
|
|
2214
|
-
recurseRoutes(children);
|
|
2215
|
-
}
|
|
2216
|
-
});
|
|
2217
|
-
};
|
|
2218
|
-
recurseRoutes([this.routeTree]);
|
|
2219
|
-
const scoredRoutes = [];
|
|
2220
|
-
Object.values(this.routesById).forEach((d, i) => {
|
|
2221
|
-
if (d.isRoot || !d.path) {
|
|
2222
|
-
return;
|
|
2223
|
-
}
|
|
2224
|
-
const trimmed = trimPathLeft(d.fullPath);
|
|
2225
|
-
const parsed = parsePathname(trimmed);
|
|
2226
|
-
while (parsed.length > 1 && parsed[0]?.value === '/') {
|
|
2227
|
-
parsed.shift();
|
|
2228
|
-
}
|
|
2229
|
-
const scores = parsed.map(d => {
|
|
2230
|
-
if (d.value === '/') {
|
|
2231
|
-
return 0.75;
|
|
2232
|
-
}
|
|
2233
|
-
if (d.type === 'param') {
|
|
2234
|
-
return 0.5;
|
|
2235
|
-
}
|
|
2236
|
-
if (d.type === 'wildcard') {
|
|
2237
|
-
return 0.25;
|
|
2238
|
-
}
|
|
2239
|
-
return 1;
|
|
2240
|
-
});
|
|
2241
|
-
scoredRoutes.push({
|
|
2242
|
-
child: d,
|
|
2243
|
-
trimmed,
|
|
2244
|
-
parsed,
|
|
2245
|
-
index: i,
|
|
2246
|
-
scores
|
|
2247
|
-
});
|
|
2248
|
-
});
|
|
2249
|
-
this.flatRoutes = scoredRoutes.sort((a, b) => {
|
|
2250
|
-
const minLength = Math.min(a.scores.length, b.scores.length);
|
|
2251
|
-
|
|
2252
|
-
// Sort by min available score
|
|
2253
|
-
for (let i = 0; i < minLength; i++) {
|
|
2254
|
-
if (a.scores[i] !== b.scores[i]) {
|
|
2255
|
-
return b.scores[i] - a.scores[i];
|
|
2256
|
-
}
|
|
2257
|
-
}
|
|
2325
|
+
updateMatch(match);
|
|
2326
|
+
};
|
|
2258
2327
|
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2328
|
+
// This is where all of the stale-while-revalidate magic happens
|
|
2329
|
+
const age = Date.now() - match.updatedAt;
|
|
2330
|
+
let staleAge = preload ? route.options.preloadStaleTime ?? this.options.defaultPreloadStaleTime ?? 30_000 // 30 seconds for preloads by default
|
|
2331
|
+
: route.options.staleTime ?? this.options.defaultStaleTime ?? 0;
|
|
2263
2332
|
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2333
|
+
// Default to reloading the route all the time
|
|
2334
|
+
let shouldReload;
|
|
2335
|
+
const shouldReloadOption = route.options.shouldReload;
|
|
2270
2336
|
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2337
|
+
// Allow shouldReload to get the last say,
|
|
2338
|
+
// if provided.
|
|
2339
|
+
shouldReload = typeof shouldReloadOption === 'function' ? shouldReloadOption(loaderContext) : shouldReloadOption;
|
|
2340
|
+
matches[index] = match = {
|
|
2341
|
+
...match,
|
|
2342
|
+
preload: !!preload && !this.state.matches.find(d => d.id === match.id)
|
|
2343
|
+
};
|
|
2344
|
+
if (match.status !== 'success') {
|
|
2345
|
+
// If we need to potentially show the pending component,
|
|
2346
|
+
// start a timer to show it after the pendingMs
|
|
2347
|
+
if (shouldPending) {
|
|
2348
|
+
match.pendingPromise?.then(async () => {
|
|
2349
|
+
if (latestPromise = checkLatest()) return latestPromise;
|
|
2350
|
+
didShowPending = true;
|
|
2351
|
+
matches[index] = match = {
|
|
2352
|
+
...match,
|
|
2353
|
+
showPending: true
|
|
2354
|
+
};
|
|
2355
|
+
updateMatch(match);
|
|
2356
|
+
resolve();
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
// Critical Fetching, we need to await
|
|
2361
|
+
await fetch();
|
|
2362
|
+
} else if (match.invalid || (shouldReload ?? age > staleAge)) {
|
|
2363
|
+
// Background Fetching, no need to wait
|
|
2364
|
+
fetch();
|
|
2365
|
+
}
|
|
2366
|
+
resolve();
|
|
2367
|
+
}));
|
|
2293
2368
|
});
|
|
2369
|
+
await Promise.all(matchPromises);
|
|
2370
|
+
return matches;
|
|
2294
2371
|
};
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
const parse = ({
|
|
2300
|
-
pathname,
|
|
2301
|
-
search,
|
|
2302
|
-
hash,
|
|
2303
|
-
state
|
|
2304
|
-
}) => {
|
|
2305
|
-
const parsedSearch = this.options.parseSearch(search);
|
|
2306
|
-
return {
|
|
2307
|
-
pathname: pathname,
|
|
2308
|
-
searchStr: search,
|
|
2309
|
-
search: replaceEqualDeep(previousLocation?.search, parsedSearch),
|
|
2310
|
-
hash: hash.split('#').reverse()[0] ?? '',
|
|
2311
|
-
href: `${pathname}${search}${hash}`,
|
|
2312
|
-
state: replaceEqualDeep(previousLocation?.state, state)
|
|
2313
|
-
};
|
|
2314
|
-
};
|
|
2315
|
-
const location = parse(this.history.location);
|
|
2316
|
-
let {
|
|
2317
|
-
__tempLocation,
|
|
2318
|
-
__tempKey
|
|
2319
|
-
} = location.state;
|
|
2320
|
-
if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
|
|
2321
|
-
// Sync up the location keys
|
|
2322
|
-
const parsedTempLocation = parse(__tempLocation);
|
|
2323
|
-
parsedTempLocation.state.key = location.state.key;
|
|
2324
|
-
delete parsedTempLocation.state.__tempLocation;
|
|
2325
|
-
return {
|
|
2326
|
-
...parsedTempLocation,
|
|
2327
|
-
maskedLocation: location
|
|
2328
|
-
};
|
|
2329
|
-
}
|
|
2330
|
-
return location;
|
|
2331
|
-
};
|
|
2332
|
-
resolvePathWithBase = (from, path) => {
|
|
2333
|
-
return resolvePath(this.basepath, from, cleanPath(path));
|
|
2334
|
-
};
|
|
2335
|
-
get looseRoutesById() {
|
|
2336
|
-
return this.routesById;
|
|
2337
|
-
}
|
|
2338
|
-
matchRoutes = (pathname, locationSearch, opts) => {
|
|
2339
|
-
let routeParams = {};
|
|
2340
|
-
let foundRoute = this.flatRoutes.find(route => {
|
|
2341
|
-
const matchedParams = matchPathname(this.basepath, trimPathRight(pathname), {
|
|
2342
|
-
to: route.fullPath,
|
|
2343
|
-
caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive,
|
|
2344
|
-
fuzzy: true
|
|
2345
|
-
});
|
|
2346
|
-
if (matchedParams) {
|
|
2347
|
-
routeParams = matchedParams;
|
|
2348
|
-
return true;
|
|
2349
|
-
}
|
|
2350
|
-
return false;
|
|
2372
|
+
invalidate = () => {
|
|
2373
|
+
const invalidate = d => ({
|
|
2374
|
+
...d,
|
|
2375
|
+
invalid: true
|
|
2351
2376
|
});
|
|
2352
|
-
|
|
2353
|
-
|
|
2377
|
+
this.__store.setState(s => ({
|
|
2378
|
+
...s,
|
|
2379
|
+
matches: s.matches.map(invalidate),
|
|
2380
|
+
cachedMatches: s.cachedMatches.map(invalidate),
|
|
2381
|
+
pendingMatches: s.pendingMatches?.map(invalidate)
|
|
2382
|
+
}));
|
|
2383
|
+
this.load();
|
|
2384
|
+
};
|
|
2385
|
+
load = async () => {
|
|
2386
|
+
const promise = new Promise(async (resolve, reject) => {
|
|
2387
|
+
const next = this.latestLocation;
|
|
2388
|
+
const prevLocation = this.state.resolvedLocation;
|
|
2389
|
+
const pathDidChange = prevLocation.href !== next.href;
|
|
2390
|
+
let latestPromise;
|
|
2354
2391
|
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
if (routeCursor) matchedRoutes.unshift(routeCursor);
|
|
2368
|
-
}
|
|
2392
|
+
// Cancel any pending matches
|
|
2393
|
+
this.cancelMatches();
|
|
2394
|
+
this.emit({
|
|
2395
|
+
type: 'onBeforeLoad',
|
|
2396
|
+
fromLocation: prevLocation,
|
|
2397
|
+
toLocation: next,
|
|
2398
|
+
pathChanged: pathDidChange
|
|
2399
|
+
});
|
|
2400
|
+
let pendingMatches;
|
|
2401
|
+
const previousMatches = this.state.matches;
|
|
2402
|
+
this.__store.batch(() => {
|
|
2403
|
+
this.cleanCache();
|
|
2369
2404
|
|
|
2370
|
-
|
|
2371
|
-
|
|
2405
|
+
// Match the routes
|
|
2406
|
+
pendingMatches = this.matchRoutes(next.pathname, next.search, {
|
|
2407
|
+
debug: true
|
|
2408
|
+
});
|
|
2372
2409
|
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2410
|
+
// Ingest the new matches
|
|
2411
|
+
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
2412
|
+
this.__store.setState(s => ({
|
|
2413
|
+
...s,
|
|
2414
|
+
isLoading: true,
|
|
2415
|
+
location: next,
|
|
2416
|
+
pendingMatches,
|
|
2417
|
+
cachedMatches: s.cachedMatches.filter(d => {
|
|
2418
|
+
return !pendingMatches.find(e => e.id === d.id);
|
|
2419
|
+
})
|
|
2420
|
+
}));
|
|
2421
|
+
});
|
|
2422
|
+
try {
|
|
2376
2423
|
try {
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
parsedParamsError = new PathParamError(err.message, {
|
|
2382
|
-
cause: err
|
|
2424
|
+
// Load the matches
|
|
2425
|
+
await this.loadMatches({
|
|
2426
|
+
matches: pendingMatches,
|
|
2427
|
+
checkLatest: () => this.checkLatest(promise)
|
|
2383
2428
|
});
|
|
2384
|
-
if (opts?.throwOnError) {
|
|
2385
|
-
throw parsedParamsError;
|
|
2386
|
-
}
|
|
2387
|
-
return parsedParamsError;
|
|
2388
|
-
}
|
|
2389
|
-
}
|
|
2390
|
-
return;
|
|
2391
|
-
});
|
|
2392
|
-
const matches = [];
|
|
2393
|
-
matchedRoutes.forEach((route, index) => {
|
|
2394
|
-
// Take each matched route and resolve + validate its search params
|
|
2395
|
-
// This has to happen serially because each route's search params
|
|
2396
|
-
// can depend on the parent route's search params
|
|
2397
|
-
// It must also happen before we create the match so that we can
|
|
2398
|
-
// pass the search params to the route's potential key function
|
|
2399
|
-
// which is used to uniquely identify the route match in state
|
|
2400
|
-
|
|
2401
|
-
const parentMatch = matches[index - 1];
|
|
2402
|
-
const [preMatchSearch, searchError] = (() => {
|
|
2403
|
-
// Validate the search params and stabilize them
|
|
2404
|
-
const parentSearch = parentMatch?.search ?? locationSearch;
|
|
2405
|
-
try {
|
|
2406
|
-
const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
|
|
2407
|
-
let search = validator?.(parentSearch) ?? {};
|
|
2408
|
-
return [{
|
|
2409
|
-
...parentSearch,
|
|
2410
|
-
...search
|
|
2411
|
-
}, undefined];
|
|
2412
2429
|
} catch (err) {
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
});
|
|
2416
|
-
if (opts?.throwOnError) {
|
|
2417
|
-
throw searchError;
|
|
2418
|
-
}
|
|
2419
|
-
return [parentSearch, searchError];
|
|
2430
|
+
// swallow this error, since we'll display the
|
|
2431
|
+
// errors on the route components
|
|
2420
2432
|
}
|
|
2421
|
-
})();
|
|
2422
|
-
|
|
2423
|
-
// This is where we need to call route.options.loaderDeps() to get any additional
|
|
2424
|
-
// deps that the route's loader function might need to run. We need to do this
|
|
2425
|
-
// before we create the match so that we can pass the deps to the route's
|
|
2426
|
-
// potential key function which is used to uniquely identify the route match in state
|
|
2427
2433
|
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
+
// Only apply the latest transition
|
|
2435
|
+
if (latestPromise = this.checkLatest(promise)) {
|
|
2436
|
+
return latestPromise;
|
|
2437
|
+
}
|
|
2438
|
+
const exitingMatches = previousMatches.filter(match => !pendingMatches.find(d => d.id === match.id));
|
|
2439
|
+
const enteringMatches = pendingMatches.filter(match => !previousMatches.find(d => d.id === match.id));
|
|
2440
|
+
const stayingMatches = previousMatches.filter(match => pendingMatches.find(d => d.id === match.id));
|
|
2434
2441
|
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2442
|
+
// Commit the pending matches. If a previous match was
|
|
2443
|
+
// removed, place it in the cachedMatches
|
|
2444
|
+
this.__store.batch(() => {
|
|
2445
|
+
this.__store.setState(s => ({
|
|
2446
|
+
...s,
|
|
2447
|
+
isLoading: false,
|
|
2448
|
+
matches: s.pendingMatches,
|
|
2449
|
+
pendingMatches: undefined,
|
|
2450
|
+
cachedMatches: [...s.cachedMatches, ...exitingMatches.filter(d => d.status !== 'error')]
|
|
2451
|
+
}));
|
|
2452
|
+
this.cleanCache();
|
|
2453
|
+
})
|
|
2440
2454
|
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
} : {
|
|
2447
|
-
id: matchId,
|
|
2448
|
-
routeId: route.id,
|
|
2449
|
-
params: routeParams,
|
|
2450
|
-
pathname: joinPaths([this.basepath, interpolatedPath]),
|
|
2451
|
-
updatedAt: Date.now(),
|
|
2452
|
-
search: {},
|
|
2453
|
-
searchError: undefined,
|
|
2454
|
-
status: hasLoaders ? 'pending' : 'success',
|
|
2455
|
-
showPending: false,
|
|
2456
|
-
isFetching: false,
|
|
2457
|
-
error: undefined,
|
|
2458
|
-
paramsError: parseErrors[index],
|
|
2459
|
-
loadPromise: Promise.resolve(),
|
|
2460
|
-
routeContext: undefined,
|
|
2461
|
-
context: undefined,
|
|
2462
|
-
abortController: new AbortController(),
|
|
2463
|
-
fetchCount: 0,
|
|
2464
|
-
cause,
|
|
2465
|
-
loaderDeps,
|
|
2466
|
-
invalid: false,
|
|
2467
|
-
preload: false
|
|
2468
|
-
};
|
|
2469
|
-
|
|
2470
|
-
// Regardless of whether we're reusing an existing match or creating
|
|
2471
|
-
// a new one, we need to update the match's search params
|
|
2472
|
-
match.search = replaceEqualDeep(match.search, preMatchSearch);
|
|
2473
|
-
// And also update the searchError if there is one
|
|
2474
|
-
match.searchError = searchError;
|
|
2475
|
-
matches.push(match);
|
|
2476
|
-
});
|
|
2477
|
-
return matches;
|
|
2478
|
-
};
|
|
2479
|
-
cancelMatch = id => {
|
|
2480
|
-
getRouteMatch(this.state, id)?.abortController?.abort();
|
|
2481
|
-
};
|
|
2482
|
-
cancelMatches = () => {
|
|
2483
|
-
this.state.pendingMatches?.forEach(match => {
|
|
2484
|
-
this.cancelMatch(match.id);
|
|
2485
|
-
});
|
|
2486
|
-
};
|
|
2487
|
-
buildLocation = opts => {
|
|
2488
|
-
const build = (dest = {}, matches) => {
|
|
2489
|
-
const relevantMatches = this.state.pendingMatches || this.state.matches;
|
|
2490
|
-
const fromSearch = relevantMatches[relevantMatches.length - 1]?.search || this.latestLocation.search;
|
|
2491
|
-
let pathname = this.resolvePathWithBase(dest.from ?? this.latestLocation.pathname, `${dest.to ?? ''}`);
|
|
2492
|
-
const fromMatches = this.matchRoutes(this.latestLocation.pathname, fromSearch);
|
|
2493
|
-
const stayingMatches = matches?.filter(d => fromMatches?.find(e => e.routeId === d.routeId));
|
|
2494
|
-
const prevParams = {
|
|
2495
|
-
...last(fromMatches)?.params
|
|
2496
|
-
};
|
|
2497
|
-
let nextParams = (dest.params ?? true) === true ? prevParams : functionalUpdate(dest.params, prevParams);
|
|
2498
|
-
if (nextParams) {
|
|
2499
|
-
matches?.map(d => this.looseRoutesById[d.routeId].options.stringifyParams).filter(Boolean).forEach(fn => {
|
|
2500
|
-
nextParams = {
|
|
2501
|
-
...nextParams,
|
|
2502
|
-
...fn(nextParams)
|
|
2503
|
-
};
|
|
2504
|
-
});
|
|
2505
|
-
}
|
|
2506
|
-
pathname = interpolatePath(pathname, nextParams ?? {});
|
|
2507
|
-
const preSearchFilters = stayingMatches?.map(match => this.looseRoutesById[match.routeId].options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
|
|
2508
|
-
const postSearchFilters = stayingMatches?.map(match => this.looseRoutesById[match.routeId].options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
|
|
2509
|
-
|
|
2510
|
-
// Pre filters first
|
|
2511
|
-
const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), fromSearch) : fromSearch;
|
|
2512
|
-
|
|
2513
|
-
// Then the link/navigate function
|
|
2514
|
-
const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
|
|
2515
|
-
: dest.search ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
2516
|
-
: preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
|
|
2517
|
-
: {};
|
|
2518
|
-
|
|
2519
|
-
// Then post filters
|
|
2520
|
-
const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
|
|
2521
|
-
const search = replaceEqualDeep(fromSearch, postFilteredSearch);
|
|
2522
|
-
const searchStr = this.options.stringifySearch(search);
|
|
2523
|
-
const hash = dest.hash === true ? this.latestLocation.hash : dest.hash ? functionalUpdate(dest.hash, this.latestLocation.hash) : undefined;
|
|
2524
|
-
const hashStr = hash ? `#${hash}` : '';
|
|
2525
|
-
let nextState = dest.state === true ? this.latestLocation.state : dest.state ? functionalUpdate(dest.state, this.latestLocation.state) : this.latestLocation.state;
|
|
2526
|
-
nextState = replaceEqualDeep(this.latestLocation.state, nextState);
|
|
2527
|
-
return {
|
|
2528
|
-
pathname,
|
|
2529
|
-
search,
|
|
2530
|
-
searchStr,
|
|
2531
|
-
state: nextState,
|
|
2532
|
-
hash: hash ?? '',
|
|
2533
|
-
href: `${pathname}${searchStr}${hashStr}`,
|
|
2534
|
-
unmaskOnReload: dest.unmaskOnReload
|
|
2535
|
-
};
|
|
2536
|
-
};
|
|
2537
|
-
const buildWithMatches = (dest = {}, maskedDest) => {
|
|
2538
|
-
let next = build(dest);
|
|
2539
|
-
let maskedNext = maskedDest ? build(maskedDest) : undefined;
|
|
2540
|
-
if (!maskedNext) {
|
|
2541
|
-
let params = {};
|
|
2542
|
-
let foundMask = this.options.routeMasks?.find(d => {
|
|
2543
|
-
const match = matchPathname(this.basepath, next.pathname, {
|
|
2544
|
-
to: d.from,
|
|
2545
|
-
caseSensitive: false,
|
|
2546
|
-
fuzzy: false
|
|
2455
|
+
//
|
|
2456
|
+
;
|
|
2457
|
+
[[exitingMatches, 'onLeave'], [enteringMatches, 'onEnter'], [stayingMatches, 'onStay']].forEach(([matches, hook]) => {
|
|
2458
|
+
matches.forEach(match => {
|
|
2459
|
+
this.looseRoutesById[match.routeId].options[hook]?.(match);
|
|
2547
2460
|
});
|
|
2548
|
-
if (match) {
|
|
2549
|
-
params = match;
|
|
2550
|
-
return true;
|
|
2551
|
-
}
|
|
2552
|
-
return false;
|
|
2553
2461
|
});
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2462
|
+
this.emit({
|
|
2463
|
+
type: 'onLoad',
|
|
2464
|
+
fromLocation: prevLocation,
|
|
2465
|
+
toLocation: next,
|
|
2466
|
+
pathChanged: pathDidChange
|
|
2467
|
+
});
|
|
2468
|
+
resolve();
|
|
2469
|
+
} catch (err) {
|
|
2470
|
+
// Only apply the latest transition
|
|
2471
|
+
if (latestPromise = this.checkLatest(promise)) {
|
|
2472
|
+
return latestPromise;
|
|
2561
2473
|
}
|
|
2474
|
+
reject(err);
|
|
2562
2475
|
}
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
const final = build(dest, nextMatches);
|
|
2567
|
-
if (maskedFinal) {
|
|
2568
|
-
final.maskedLocation = maskedFinal;
|
|
2569
|
-
}
|
|
2570
|
-
return final;
|
|
2571
|
-
};
|
|
2572
|
-
if (opts.mask) {
|
|
2573
|
-
return buildWithMatches(opts, {
|
|
2574
|
-
...pick(opts, ['from']),
|
|
2575
|
-
...opts.mask
|
|
2576
|
-
});
|
|
2577
|
-
}
|
|
2578
|
-
return buildWithMatches(opts);
|
|
2476
|
+
});
|
|
2477
|
+
this.latestLoadPromise = promise;
|
|
2478
|
+
return this.latestLoadPromise;
|
|
2579
2479
|
};
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
if (!isSameUrl || !next.replace) {
|
|
2590
|
-
let {
|
|
2591
|
-
maskedLocation,
|
|
2592
|
-
...nextHistory
|
|
2593
|
-
} = next;
|
|
2594
|
-
if (maskedLocation) {
|
|
2595
|
-
nextHistory = {
|
|
2596
|
-
...maskedLocation,
|
|
2597
|
-
state: {
|
|
2598
|
-
...maskedLocation.state,
|
|
2599
|
-
__tempKey: undefined,
|
|
2600
|
-
__tempLocation: {
|
|
2601
|
-
...nextHistory,
|
|
2602
|
-
search: nextHistory.searchStr,
|
|
2603
|
-
state: {
|
|
2604
|
-
...nextHistory.state,
|
|
2605
|
-
__tempKey: undefined,
|
|
2606
|
-
__tempLocation: undefined,
|
|
2607
|
-
key: undefined
|
|
2608
|
-
}
|
|
2609
|
-
}
|
|
2480
|
+
cleanCache = () => {
|
|
2481
|
+
// This is where all of the garbage collection magic happens
|
|
2482
|
+
this.__store.setState(s => {
|
|
2483
|
+
return {
|
|
2484
|
+
...s,
|
|
2485
|
+
cachedMatches: s.cachedMatches.filter(d => {
|
|
2486
|
+
const route = this.looseRoutesById[d.routeId];
|
|
2487
|
+
if (!route.options.loader) {
|
|
2488
|
+
return false;
|
|
2610
2489
|
}
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
this.history[next.replace ? 'replace' : 'push'](nextHistory.href, nextHistory.state);
|
|
2490
|
+
|
|
2491
|
+
// If the route was preloaded, use the preloadGcTime
|
|
2492
|
+
// otherwise, use the gcTime
|
|
2493
|
+
const gcTime = (d.preload ? route.options.preloadGcTime ?? this.options.defaultPreloadGcTime : route.options.gcTime ?? this.options.defaultGcTime) ?? 5 * 60 * 1000;
|
|
2494
|
+
return d.status !== 'error' && Date.now() - d.updatedAt < gcTime;
|
|
2495
|
+
})
|
|
2618
2496
|
};
|
|
2619
|
-
if (startTransition ?? true) {
|
|
2620
|
-
this.startReactTransition(apply);
|
|
2621
|
-
} else {
|
|
2622
|
-
apply();
|
|
2623
|
-
}
|
|
2624
|
-
}
|
|
2625
|
-
this.resetNextScroll = next.resetScroll ?? true;
|
|
2626
|
-
return this.latestLoadPromise;
|
|
2627
|
-
};
|
|
2628
|
-
buildAndCommitLocation = ({
|
|
2629
|
-
replace,
|
|
2630
|
-
resetScroll,
|
|
2631
|
-
startTransition,
|
|
2632
|
-
...rest
|
|
2633
|
-
} = {}) => {
|
|
2634
|
-
const location = this.buildLocation(rest);
|
|
2635
|
-
return this.commitLocation({
|
|
2636
|
-
...location,
|
|
2637
|
-
startTransition,
|
|
2638
|
-
replace,
|
|
2639
|
-
resetScroll
|
|
2640
2497
|
});
|
|
2641
2498
|
};
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
}) => {
|
|
2647
|
-
// If this link simply reloads the current route,
|
|
2648
|
-
// make sure it has a new key so it will trigger a data refresh
|
|
2649
|
-
|
|
2650
|
-
// If this `to` is a valid external URL, return
|
|
2651
|
-
// null for LinkUtils
|
|
2652
|
-
const toString = String(to);
|
|
2653
|
-
// const fromString = from !== undefined ? String(from) : from
|
|
2654
|
-
let isExternal;
|
|
2655
|
-
try {
|
|
2656
|
-
new URL(`${toString}`);
|
|
2657
|
-
isExternal = true;
|
|
2658
|
-
} catch (e) {}
|
|
2659
|
-
invariant(!isExternal, 'Attempting to navigate to external url with this.navigate!');
|
|
2660
|
-
return this.buildAndCommitLocation({
|
|
2661
|
-
...rest,
|
|
2662
|
-
from,
|
|
2663
|
-
to
|
|
2664
|
-
// to: toString,
|
|
2499
|
+
preloadRoute = async (navigateOpts = this.state.location) => {
|
|
2500
|
+
let next = this.buildLocation(navigateOpts);
|
|
2501
|
+
let matches = this.matchRoutes(next.pathname, next.search, {
|
|
2502
|
+
throwOnError: true
|
|
2665
2503
|
});
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
const updateMatch = match => {
|
|
2675
|
-
// const isPreload = this.state.cachedMatches.find((d) => d.id === match.id)
|
|
2676
|
-
const isPending = this.state.pendingMatches?.find(d => d.id === match.id);
|
|
2677
|
-
const isMatched = this.state.matches.find(d => d.id === match.id);
|
|
2678
|
-
const matchesKey = isPending ? 'pendingMatches' : isMatched ? 'matches' : 'cachedMatches';
|
|
2679
|
-
this.__store.setState(s => ({
|
|
2680
|
-
...s,
|
|
2681
|
-
[matchesKey]: s[matchesKey]?.map(d => d.id === match.id ? match : d)
|
|
2682
|
-
}));
|
|
2683
|
-
};
|
|
2684
|
-
|
|
2685
|
-
// Check each match middleware to see if the route can be accessed
|
|
2686
|
-
try {
|
|
2687
|
-
for (let [index, match] of matches.entries()) {
|
|
2688
|
-
const parentMatch = matches[index - 1];
|
|
2689
|
-
const route = this.looseRoutesById[match.routeId];
|
|
2690
|
-
const abortController = new AbortController();
|
|
2691
|
-
const handleErrorAndRedirect = (err, code) => {
|
|
2692
|
-
err.routerCode = code;
|
|
2693
|
-
firstBadMatchIndex = firstBadMatchIndex ?? index;
|
|
2694
|
-
if (isRedirect(err)) {
|
|
2695
|
-
throw err;
|
|
2696
|
-
}
|
|
2697
|
-
try {
|
|
2698
|
-
route.options.onError?.(err);
|
|
2699
|
-
} catch (errorHandlerErr) {
|
|
2700
|
-
err = errorHandlerErr;
|
|
2701
|
-
if (isRedirect(errorHandlerErr)) {
|
|
2702
|
-
throw errorHandlerErr;
|
|
2703
|
-
}
|
|
2704
|
-
}
|
|
2705
|
-
matches[index] = match = {
|
|
2706
|
-
...match,
|
|
2707
|
-
error: err,
|
|
2708
|
-
status: 'error',
|
|
2709
|
-
updatedAt: Date.now(),
|
|
2710
|
-
abortController: new AbortController()
|
|
2711
|
-
};
|
|
2712
|
-
};
|
|
2713
|
-
try {
|
|
2714
|
-
if (match.paramsError) {
|
|
2715
|
-
handleErrorAndRedirect(match.paramsError, 'PARSE_PARAMS');
|
|
2716
|
-
}
|
|
2717
|
-
if (match.searchError) {
|
|
2718
|
-
handleErrorAndRedirect(match.searchError, 'VALIDATE_SEARCH');
|
|
2719
|
-
}
|
|
2720
|
-
const parentContext = parentMatch?.context ?? this.options.context ?? {};
|
|
2721
|
-
const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
|
|
2722
|
-
const pendingPromise = typeof pendingMs === 'number' && pendingMs <= 0 ? Promise.resolve() : new Promise(r => setTimeout(r, pendingMs));
|
|
2723
|
-
const beforeLoadContext = (await route.options.beforeLoad?.({
|
|
2724
|
-
search: match.search,
|
|
2725
|
-
abortController,
|
|
2726
|
-
params: match.params,
|
|
2727
|
-
preload: !!preload,
|
|
2728
|
-
context: parentContext,
|
|
2729
|
-
location: this.state.location,
|
|
2730
|
-
// TOOD: just expose state and router, etc
|
|
2731
|
-
navigate: opts => this.navigate({
|
|
2732
|
-
...opts,
|
|
2733
|
-
from: match.pathname
|
|
2734
|
-
}),
|
|
2735
|
-
buildLocation: this.buildLocation,
|
|
2736
|
-
cause: preload ? 'preload' : match.cause
|
|
2737
|
-
})) ?? {};
|
|
2738
|
-
if (isRedirect(beforeLoadContext)) {
|
|
2739
|
-
throw beforeLoadContext;
|
|
2740
|
-
}
|
|
2741
|
-
const context = {
|
|
2742
|
-
...parentContext,
|
|
2743
|
-
...beforeLoadContext
|
|
2744
|
-
};
|
|
2745
|
-
matches[index] = match = {
|
|
2746
|
-
...match,
|
|
2747
|
-
routeContext: replaceEqualDeep(match.routeContext, beforeLoadContext),
|
|
2748
|
-
context: replaceEqualDeep(match.context, context),
|
|
2749
|
-
abortController,
|
|
2750
|
-
pendingPromise
|
|
2751
|
-
};
|
|
2752
|
-
} catch (err) {
|
|
2753
|
-
handleErrorAndRedirect(err, 'BEFORE_LOAD');
|
|
2754
|
-
break;
|
|
2504
|
+
const loadedMatchIds = Object.fromEntries([...this.state.matches, ...(this.state.pendingMatches ?? []), ...this.state.cachedMatches]?.map(d => [d.id, true]));
|
|
2505
|
+
this.__store.batch(() => {
|
|
2506
|
+
matches.forEach(match => {
|
|
2507
|
+
if (!loadedMatchIds[match.id]) {
|
|
2508
|
+
this.__store.setState(s => ({
|
|
2509
|
+
...s,
|
|
2510
|
+
cachedMatches: [...s.cachedMatches, match]
|
|
2511
|
+
}));
|
|
2755
2512
|
}
|
|
2513
|
+
});
|
|
2514
|
+
});
|
|
2515
|
+
matches = await this.loadMatches({
|
|
2516
|
+
matches,
|
|
2517
|
+
preload: true,
|
|
2518
|
+
checkLatest: () => undefined
|
|
2519
|
+
});
|
|
2520
|
+
return matches;
|
|
2521
|
+
};
|
|
2522
|
+
matchRoute = (location, opts) => {
|
|
2523
|
+
location = {
|
|
2524
|
+
...location,
|
|
2525
|
+
to: location.to ? this.resolvePathWithBase(location.from || '', location.to) : undefined
|
|
2526
|
+
};
|
|
2527
|
+
const next = this.buildLocation(location);
|
|
2528
|
+
if (opts?.pending && this.state.status !== 'pending') {
|
|
2529
|
+
return false;
|
|
2530
|
+
}
|
|
2531
|
+
const baseLocation = opts?.pending ? this.latestLocation : this.state.resolvedLocation;
|
|
2532
|
+
if (!baseLocation) {
|
|
2533
|
+
return false;
|
|
2534
|
+
}
|
|
2535
|
+
const match = matchPathname(this.basepath, baseLocation.pathname, {
|
|
2536
|
+
...opts,
|
|
2537
|
+
to: next.pathname
|
|
2538
|
+
});
|
|
2539
|
+
if (!match) {
|
|
2540
|
+
return false;
|
|
2541
|
+
}
|
|
2542
|
+
if (match && (opts?.includeSearch ?? true)) {
|
|
2543
|
+
return deepEqual(baseLocation.search, next.search, true) ? match : false;
|
|
2544
|
+
}
|
|
2545
|
+
return match;
|
|
2546
|
+
};
|
|
2547
|
+
injectHtml = async html => {
|
|
2548
|
+
this.injectedHtml.push(html);
|
|
2549
|
+
};
|
|
2550
|
+
dehydrateData = (key, getData) => {
|
|
2551
|
+
if (typeof document === 'undefined') {
|
|
2552
|
+
const strKey = typeof key === 'string' ? key : JSON.stringify(key);
|
|
2553
|
+
this.injectHtml(async () => {
|
|
2554
|
+
const id = `__TSR_DEHYDRATED__${strKey}`;
|
|
2555
|
+
const data = typeof getData === 'function' ? await getData() : getData;
|
|
2556
|
+
return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(strKey)}"] = ${JSON.stringify(data)}
|
|
2557
|
+
;(() => {
|
|
2558
|
+
var el = document.getElementById('${id}')
|
|
2559
|
+
el.parentElement.removeChild(el)
|
|
2560
|
+
})()
|
|
2561
|
+
</script>`;
|
|
2562
|
+
});
|
|
2563
|
+
return () => this.hydrateData(key);
|
|
2564
|
+
}
|
|
2565
|
+
return () => undefined;
|
|
2566
|
+
};
|
|
2567
|
+
hydrateData = key => {
|
|
2568
|
+
if (typeof document !== 'undefined') {
|
|
2569
|
+
const strKey = typeof key === 'string' ? key : JSON.stringify(key);
|
|
2570
|
+
return window[`__TSR_DEHYDRATED__${strKey}`];
|
|
2571
|
+
}
|
|
2572
|
+
return undefined;
|
|
2573
|
+
};
|
|
2574
|
+
dehydrate = () => {
|
|
2575
|
+
const pickError = this.options.errorSerializer?.serialize ?? defaultSerializeError;
|
|
2576
|
+
return {
|
|
2577
|
+
state: {
|
|
2578
|
+
dehydratedMatches: this.state.matches.map(d => ({
|
|
2579
|
+
...pick(d, ['id', 'status', 'updatedAt', 'loaderData']),
|
|
2580
|
+
// If an error occurs server-side during SSRing,
|
|
2581
|
+
// send a small subset of the error to the client
|
|
2582
|
+
error: d.error ? {
|
|
2583
|
+
data: pickError(d.error),
|
|
2584
|
+
__isServerError: true
|
|
2585
|
+
} : undefined
|
|
2586
|
+
}))
|
|
2756
2587
|
}
|
|
2757
|
-
}
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2588
|
+
};
|
|
2589
|
+
};
|
|
2590
|
+
hydrate = async __do_not_use_server_ctx => {
|
|
2591
|
+
let _ctx = __do_not_use_server_ctx;
|
|
2592
|
+
// Client hydrates from window
|
|
2593
|
+
if (typeof document !== 'undefined') {
|
|
2594
|
+
_ctx = window.__TSR_DEHYDRATED__;
|
|
2763
2595
|
}
|
|
2764
|
-
|
|
2765
|
-
const
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
}
|
|
2775
|
-
return true;
|
|
2776
|
-
}
|
|
2777
|
-
return false;
|
|
2778
|
-
};
|
|
2779
|
-
let loadPromise;
|
|
2780
|
-
matches[index] = match = {
|
|
2596
|
+
invariant(_ctx, 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?');
|
|
2597
|
+
const ctx = _ctx;
|
|
2598
|
+
this.dehydratedData = ctx.payload;
|
|
2599
|
+
this.options.hydrate?.(ctx.payload);
|
|
2600
|
+
const dehydratedState = ctx.router.state;
|
|
2601
|
+
let matches = this.matchRoutes(this.state.location.pathname, this.state.location.search).map(match => {
|
|
2602
|
+
const dehydratedMatch = dehydratedState.dehydratedMatches.find(d => d.id === match.id);
|
|
2603
|
+
invariant(dehydratedMatch, `Could not find a client-side match for dehydrated match with id: ${match.id}!`);
|
|
2604
|
+
if (dehydratedMatch) {
|
|
2605
|
+
return {
|
|
2781
2606
|
...match,
|
|
2782
|
-
|
|
2783
|
-
};
|
|
2784
|
-
let didShowPending = false;
|
|
2785
|
-
const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
|
|
2786
|
-
const pendingMinMs = route.options.pendingMinMs ?? this.options.defaultPendingMinMs;
|
|
2787
|
-
const shouldPending = !preload && typeof pendingMs === 'number' && (route.options.pendingComponent ?? this.options.defaultPendingComponent);
|
|
2788
|
-
const loaderContext = {
|
|
2789
|
-
params: match.params,
|
|
2790
|
-
deps: match.loaderDeps,
|
|
2791
|
-
preload: !!preload,
|
|
2792
|
-
parentMatchPromise,
|
|
2793
|
-
abortController: match.abortController,
|
|
2794
|
-
context: match.context,
|
|
2795
|
-
location: this.state.location,
|
|
2796
|
-
navigate: opts => this.navigate({
|
|
2797
|
-
...opts,
|
|
2798
|
-
from: match.pathname
|
|
2799
|
-
}),
|
|
2800
|
-
cause: preload ? 'preload' : match.cause
|
|
2607
|
+
...dehydratedMatch
|
|
2801
2608
|
};
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2609
|
+
}
|
|
2610
|
+
return match;
|
|
2611
|
+
});
|
|
2612
|
+
this.__store.setState(s => {
|
|
2613
|
+
return {
|
|
2614
|
+
...s,
|
|
2615
|
+
matches: matches
|
|
2616
|
+
};
|
|
2617
|
+
});
|
|
2618
|
+
};
|
|
2808
2619
|
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2620
|
+
// resolveMatchPromise = (matchId: string, key: string, value: any) => {
|
|
2621
|
+
// state.matches
|
|
2622
|
+
// .find((d) => d.id === matchId)
|
|
2623
|
+
// ?.__promisesByKey[key]?.resolve(value)
|
|
2624
|
+
// }
|
|
2625
|
+
}
|
|
2812
2626
|
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
updatedAt: Date.now(),
|
|
2849
|
-
loaderData,
|
|
2850
|
-
loadPromise: undefined
|
|
2851
|
-
};
|
|
2852
|
-
} catch (error) {
|
|
2853
|
-
if (latestPromise = checkLatest()) return await latestPromise;
|
|
2854
|
-
if (handleErrorAndRedirect(error)) return;
|
|
2855
|
-
try {
|
|
2856
|
-
route.options.onError?.(error);
|
|
2857
|
-
} catch (onErrorError) {
|
|
2858
|
-
error = onErrorError;
|
|
2859
|
-
if (handleErrorAndRedirect(onErrorError)) return;
|
|
2860
|
-
}
|
|
2861
|
-
matches[index] = match = {
|
|
2862
|
-
...match,
|
|
2863
|
-
error,
|
|
2864
|
-
status: 'error',
|
|
2865
|
-
isFetching: false
|
|
2866
|
-
};
|
|
2867
|
-
}
|
|
2868
|
-
updateMatch(match);
|
|
2869
|
-
};
|
|
2627
|
+
// A function that takes an import() argument which is a function and returns a new function that will
|
|
2628
|
+
// proxy arguments from the caller to the imported function, retaining all type
|
|
2629
|
+
// information along the way
|
|
2630
|
+
function lazyFn(fn, key) {
|
|
2631
|
+
return async (...args) => {
|
|
2632
|
+
const imported = await fn();
|
|
2633
|
+
return imported[key || 'default'](...args);
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
2636
|
+
class SearchParamError extends Error {}
|
|
2637
|
+
class PathParamError extends Error {}
|
|
2638
|
+
function getInitialRouterState(location) {
|
|
2639
|
+
return {
|
|
2640
|
+
isLoading: false,
|
|
2641
|
+
isTransitioning: false,
|
|
2642
|
+
status: 'idle',
|
|
2643
|
+
resolvedLocation: {
|
|
2644
|
+
...location
|
|
2645
|
+
},
|
|
2646
|
+
location,
|
|
2647
|
+
matches: [],
|
|
2648
|
+
pendingMatches: [],
|
|
2649
|
+
cachedMatches: [],
|
|
2650
|
+
lastUpdated: Date.now()
|
|
2651
|
+
};
|
|
2652
|
+
}
|
|
2653
|
+
function defaultSerializeError(err) {
|
|
2654
|
+
if (err instanceof Error) return {
|
|
2655
|
+
name: err.name,
|
|
2656
|
+
message: err.message
|
|
2657
|
+
};
|
|
2658
|
+
return {
|
|
2659
|
+
data: err
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2870
2662
|
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2663
|
+
function defer(_promise, options) {
|
|
2664
|
+
const promise = _promise;
|
|
2665
|
+
if (!promise.__deferredState) {
|
|
2666
|
+
promise.__deferredState = {
|
|
2667
|
+
uid: Math.random().toString(36).slice(2),
|
|
2668
|
+
status: 'pending'
|
|
2669
|
+
};
|
|
2670
|
+
const state = promise.__deferredState;
|
|
2671
|
+
promise.then(data => {
|
|
2672
|
+
state.status = 'success';
|
|
2673
|
+
state.data = data;
|
|
2674
|
+
}).catch(error => {
|
|
2675
|
+
state.status = 'error';
|
|
2676
|
+
state.error = {
|
|
2677
|
+
data: (options?.serializeError ?? defaultSerializeError)(error),
|
|
2678
|
+
__isServerError: true
|
|
2679
|
+
};
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
return promise;
|
|
2683
|
+
}
|
|
2684
|
+
function isDehydratedDeferred(obj) {
|
|
2685
|
+
return typeof obj === 'object' && obj !== null && !(obj instanceof Promise) && !obj.then && '__deferredState' in obj;
|
|
2686
|
+
}
|
|
2875
2687
|
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2688
|
+
function useAwaited({
|
|
2689
|
+
promise
|
|
2690
|
+
}) {
|
|
2691
|
+
const router = useRouter();
|
|
2692
|
+
let state = promise.__deferredState;
|
|
2693
|
+
const key = `__TSR__DEFERRED__${state.uid}`;
|
|
2694
|
+
if (isDehydratedDeferred(promise)) {
|
|
2695
|
+
state = router.hydrateData(key);
|
|
2696
|
+
if (!state) throw new Error('Could not find dehydrated data');
|
|
2697
|
+
promise = Promise.resolve(state.data);
|
|
2698
|
+
promise.__deferredState = state;
|
|
2699
|
+
}
|
|
2700
|
+
if (state.status === 'pending') {
|
|
2701
|
+
throw promise;
|
|
2702
|
+
}
|
|
2703
|
+
if (state.status === 'error') {
|
|
2704
|
+
if (typeof document !== 'undefined') {
|
|
2705
|
+
if (isServerSideError(state.error)) {
|
|
2706
|
+
throw (router.options.errorSerializer?.deserialize ?? defaultDeserializeError)(state.error.data);
|
|
2707
|
+
} else {
|
|
2708
|
+
warning(false, "Encountered a server-side error that doesn't fit the expected shape");
|
|
2709
|
+
throw state.error;
|
|
2710
|
+
}
|
|
2711
|
+
} else {
|
|
2712
|
+
router.dehydrateData(key, state);
|
|
2713
|
+
throw {
|
|
2714
|
+
data: (router.options.errorSerializer?.serialize ?? defaultSerializeError)(state.error),
|
|
2715
|
+
__isServerError: true
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
router.dehydrateData(key, state);
|
|
2720
|
+
return [state.data];
|
|
2721
|
+
}
|
|
2722
|
+
function Await(props) {
|
|
2723
|
+
const awaited = useAwaited(props);
|
|
2724
|
+
return props.children(...awaited);
|
|
2725
|
+
}
|
|
2879
2726
|
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
// If we need to potentially show the pending component,
|
|
2889
|
-
// start a timer to show it after the pendingMs
|
|
2890
|
-
if (shouldPending) {
|
|
2891
|
-
match.pendingPromise?.then(async () => {
|
|
2892
|
-
if (latestPromise = checkLatest()) return latestPromise;
|
|
2893
|
-
didShowPending = true;
|
|
2894
|
-
matches[index] = match = {
|
|
2895
|
-
...match,
|
|
2896
|
-
showPending: true
|
|
2897
|
-
};
|
|
2898
|
-
updateMatch(match);
|
|
2899
|
-
resolve();
|
|
2900
|
-
});
|
|
2901
|
-
}
|
|
2727
|
+
function useParams(opts) {
|
|
2728
|
+
return useRouterState({
|
|
2729
|
+
select: state => {
|
|
2730
|
+
const params = last(getRenderedMatches(state))?.params;
|
|
2731
|
+
return opts?.select ? opts.select(params) : params;
|
|
2732
|
+
}
|
|
2733
|
+
});
|
|
2734
|
+
}
|
|
2902
2735
|
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2736
|
+
function useSearch(opts) {
|
|
2737
|
+
return useMatch({
|
|
2738
|
+
...opts,
|
|
2739
|
+
select: match => {
|
|
2740
|
+
return opts?.select ? opts.select(match.search) : match.search;
|
|
2741
|
+
}
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
const rootRouteId = '__root__';
|
|
2746
|
+
|
|
2747
|
+
// The parse type here allows a zod schema to be passed directly to the validator
|
|
2748
|
+
|
|
2749
|
+
// TODO: This is part of a future APi to move away from classes and
|
|
2750
|
+
// towards a more functional API. It's not ready yet.
|
|
2751
|
+
|
|
2752
|
+
// type RouteApiInstance<
|
|
2753
|
+
// TId extends RouteIds<RegisteredRouter['routeTree']>,
|
|
2754
|
+
// TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
|
|
2755
|
+
// TFullSearchSchema extends Record<
|
|
2756
|
+
// string,
|
|
2757
|
+
// any
|
|
2758
|
+
// > = TRoute['types']['fullSearchSchema'],
|
|
2759
|
+
// TAllParams extends AnyPathParams = TRoute['types']['allParams'],
|
|
2760
|
+
// TAllContext extends Record<string, any> = TRoute['types']['allContext'],
|
|
2761
|
+
// TLoaderDeps extends Record<string, any> = TRoute['types']['loaderDeps'],
|
|
2762
|
+
// TLoaderData extends any = TRoute['types']['loaderData'],
|
|
2763
|
+
// > = {
|
|
2764
|
+
// id: TId
|
|
2765
|
+
// useMatch: <TSelected = TAllContext>(opts?: {
|
|
2766
|
+
// select?: (s: TAllContext) => TSelected
|
|
2767
|
+
// }) => TSelected
|
|
2768
|
+
|
|
2769
|
+
// useRouteContext: <TSelected = TAllContext>(opts?: {
|
|
2770
|
+
// select?: (s: TAllContext) => TSelected
|
|
2771
|
+
// }) => TSelected
|
|
2772
|
+
|
|
2773
|
+
// useSearch: <TSelected = TFullSearchSchema>(opts?: {
|
|
2774
|
+
// select?: (s: TFullSearchSchema) => TSelected
|
|
2775
|
+
// }) => TSelected
|
|
2776
|
+
|
|
2777
|
+
// useParams: <TSelected = TAllParams>(opts?: {
|
|
2778
|
+
// select?: (s: TAllParams) => TSelected
|
|
2779
|
+
// }) => TSelected
|
|
2780
|
+
|
|
2781
|
+
// useLoaderDeps: <TSelected = TLoaderDeps>(opts?: {
|
|
2782
|
+
// select?: (s: TLoaderDeps) => TSelected
|
|
2783
|
+
// }) => TSelected
|
|
2784
|
+
|
|
2785
|
+
// useLoaderData: <TSelected = TLoaderData>(opts?: {
|
|
2786
|
+
// select?: (s: TLoaderData) => TSelected
|
|
2787
|
+
// }) => TSelected
|
|
2788
|
+
// }
|
|
2789
|
+
|
|
2790
|
+
// export function RouteApi_v2<
|
|
2791
|
+
// TId extends RouteIds<RegisteredRouter['routeTree']>,
|
|
2792
|
+
// TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
|
|
2793
|
+
// TFullSearchSchema extends Record<
|
|
2794
|
+
// string,
|
|
2795
|
+
// any
|
|
2796
|
+
// > = TRoute['types']['fullSearchSchema'],
|
|
2797
|
+
// TAllParams extends AnyPathParams = TRoute['types']['allParams'],
|
|
2798
|
+
// TAllContext extends Record<string, any> = TRoute['types']['allContext'],
|
|
2799
|
+
// TLoaderDeps extends Record<string, any> = TRoute['types']['loaderDeps'],
|
|
2800
|
+
// TLoaderData extends any = TRoute['types']['loaderData'],
|
|
2801
|
+
// >({
|
|
2802
|
+
// id,
|
|
2803
|
+
// }: {
|
|
2804
|
+
// id: TId
|
|
2805
|
+
// }): RouteApiInstance<
|
|
2806
|
+
// TId,
|
|
2807
|
+
// TRoute,
|
|
2808
|
+
// TFullSearchSchema,
|
|
2809
|
+
// TAllParams,
|
|
2810
|
+
// TAllContext,
|
|
2811
|
+
// TLoaderDeps,
|
|
2812
|
+
// TLoaderData
|
|
2813
|
+
// > {
|
|
2814
|
+
// return {
|
|
2815
|
+
// id,
|
|
2816
|
+
|
|
2817
|
+
// useMatch: (opts) => {
|
|
2818
|
+
// return useMatch({ ...opts, from: id })
|
|
2819
|
+
// },
|
|
2820
|
+
|
|
2821
|
+
// useRouteContext: (opts) => {
|
|
2822
|
+
// return useMatch({
|
|
2823
|
+
// ...opts,
|
|
2824
|
+
// from: id,
|
|
2825
|
+
// select: (d: any) => (opts?.select ? opts.select(d.context) : d.context),
|
|
2826
|
+
// } as any)
|
|
2827
|
+
// },
|
|
2828
|
+
|
|
2829
|
+
// useSearch: (opts) => {
|
|
2830
|
+
// return useSearch({ ...opts, from: id } as any)
|
|
2831
|
+
// },
|
|
2832
|
+
|
|
2833
|
+
// useParams: (opts) => {
|
|
2834
|
+
// return useParams({ ...opts, from: id } as any)
|
|
2835
|
+
// },
|
|
2836
|
+
|
|
2837
|
+
// useLoaderDeps: (opts) => {
|
|
2838
|
+
// return useLoaderDeps({ ...opts, from: id } as any) as any
|
|
2839
|
+
// },
|
|
2840
|
+
|
|
2841
|
+
// useLoaderData: (opts) => {
|
|
2842
|
+
// return useLoaderData({ ...opts, from: id } as any) as any
|
|
2843
|
+
// },
|
|
2844
|
+
// }
|
|
2845
|
+
// }
|
|
2846
|
+
|
|
2847
|
+
class RouteApi {
|
|
2848
|
+
constructor({
|
|
2849
|
+
id
|
|
2850
|
+
}) {
|
|
2851
|
+
this.id = id;
|
|
2852
|
+
}
|
|
2853
|
+
useMatch = opts => {
|
|
2854
|
+
return useMatch({
|
|
2855
|
+
select: opts?.select,
|
|
2856
|
+
from: this.id
|
|
2857
|
+
});
|
|
2858
|
+
};
|
|
2859
|
+
useRouteContext = opts => {
|
|
2860
|
+
return useMatch({
|
|
2861
|
+
from: this.id,
|
|
2862
|
+
select: d => opts?.select ? opts.select(d.context) : d.context
|
|
2863
|
+
});
|
|
2864
|
+
};
|
|
2865
|
+
useSearch = opts => {
|
|
2866
|
+
return useSearch({
|
|
2867
|
+
...opts,
|
|
2868
|
+
from: this.id
|
|
2869
|
+
});
|
|
2870
|
+
};
|
|
2871
|
+
useParams = opts => {
|
|
2872
|
+
return useParams({
|
|
2873
|
+
...opts,
|
|
2874
|
+
from: this.id
|
|
2875
|
+
});
|
|
2876
|
+
};
|
|
2877
|
+
useLoaderDeps = opts => {
|
|
2878
|
+
return useLoaderDeps({
|
|
2879
|
+
...opts,
|
|
2880
|
+
from: this.id
|
|
2881
|
+
});
|
|
2882
|
+
};
|
|
2883
|
+
useLoaderData = opts => {
|
|
2884
|
+
return useLoaderData({
|
|
2885
|
+
...opts,
|
|
2886
|
+
from: this.id
|
|
2887
|
+
});
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
class Route {
|
|
2891
|
+
// Set up in this.init()
|
|
2892
|
+
|
|
2893
|
+
// customId!: TCustomId
|
|
2894
|
+
|
|
2895
|
+
// Optional
|
|
2896
|
+
|
|
2897
|
+
constructor(options) {
|
|
2898
|
+
this.options = options || {};
|
|
2899
|
+
this.isRoot = !options?.getParentRoute;
|
|
2900
|
+
invariant(!(options?.id && options?.path), `Route cannot have both an 'id' and a 'path' option.`);
|
|
2901
|
+
this.$$typeof = Symbol.for('react.memo');
|
|
2902
|
+
}
|
|
2903
|
+
init = opts => {
|
|
2904
|
+
this.originalIndex = opts.originalIndex;
|
|
2905
|
+
const options = this.options;
|
|
2906
|
+
const isRoot = !options?.path && !options?.id;
|
|
2907
|
+
this.parentRoute = this.options?.getParentRoute?.();
|
|
2908
|
+
if (isRoot) {
|
|
2909
|
+
this.path = rootRouteId;
|
|
2910
|
+
} else {
|
|
2911
|
+
invariant(this.parentRoute, `Child Route instances must pass a 'getParentRoute: () => ParentRoute' option that returns a Route instance.`);
|
|
2912
|
+
}
|
|
2913
|
+
let path = isRoot ? rootRouteId : options.path;
|
|
2914
|
+
|
|
2915
|
+
// If the path is anything other than an index path, trim it up
|
|
2916
|
+
if (path && path !== '/') {
|
|
2917
|
+
path = trimPath(path);
|
|
2918
|
+
}
|
|
2919
|
+
const customId = options?.id || path;
|
|
2920
|
+
|
|
2921
|
+
// Strip the parentId prefix from the first level of children
|
|
2922
|
+
let id = isRoot ? rootRouteId : joinPaths([this.parentRoute.id === rootRouteId ? '' : this.parentRoute.id, customId]);
|
|
2923
|
+
if (path === rootRouteId) {
|
|
2924
|
+
path = '/';
|
|
2925
|
+
}
|
|
2926
|
+
if (id !== rootRouteId) {
|
|
2927
|
+
id = joinPaths(['/', id]);
|
|
2928
|
+
}
|
|
2929
|
+
const fullPath = id === rootRouteId ? '/' : joinPaths([this.parentRoute.fullPath, path]);
|
|
2930
|
+
this.path = path;
|
|
2931
|
+
this.id = id;
|
|
2932
|
+
// this.customId = customId as TCustomId
|
|
2933
|
+
this.fullPath = fullPath;
|
|
2934
|
+
this.to = fullPath;
|
|
2935
|
+
};
|
|
2936
|
+
addChildren = children => {
|
|
2937
|
+
this.children = children;
|
|
2938
|
+
return this;
|
|
2939
|
+
};
|
|
2940
|
+
updateLoader = options => {
|
|
2941
|
+
Object.assign(this.options, options);
|
|
2942
|
+
return this;
|
|
2943
|
+
};
|
|
2944
|
+
update = options => {
|
|
2945
|
+
Object.assign(this.options, options);
|
|
2946
|
+
return this;
|
|
2947
|
+
};
|
|
2948
|
+
useMatch = opts => {
|
|
2949
|
+
return useMatch({
|
|
2950
|
+
...opts,
|
|
2951
|
+
from: this.id
|
|
2911
2952
|
});
|
|
2912
|
-
await Promise.all(matchPromises);
|
|
2913
|
-
return matches;
|
|
2914
2953
|
};
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
...
|
|
2918
|
-
|
|
2954
|
+
useRouteContext = opts => {
|
|
2955
|
+
return useMatch({
|
|
2956
|
+
...opts,
|
|
2957
|
+
from: this.id,
|
|
2958
|
+
select: d => opts?.select ? opts.select(d.context) : d.context
|
|
2919
2959
|
});
|
|
2920
|
-
this.__store.setState(s => ({
|
|
2921
|
-
...s,
|
|
2922
|
-
matches: s.matches.map(invalidate),
|
|
2923
|
-
cachedMatches: s.cachedMatches.map(invalidate),
|
|
2924
|
-
pendingMatches: s.pendingMatches?.map(invalidate)
|
|
2925
|
-
}));
|
|
2926
|
-
this.load();
|
|
2927
2960
|
};
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
this.
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2961
|
+
useSearch = opts => {
|
|
2962
|
+
return useSearch({
|
|
2963
|
+
...opts,
|
|
2964
|
+
from: this.id
|
|
2965
|
+
});
|
|
2966
|
+
};
|
|
2967
|
+
useParams = opts => {
|
|
2968
|
+
return useParams({
|
|
2969
|
+
...opts,
|
|
2970
|
+
from: this.id
|
|
2971
|
+
});
|
|
2972
|
+
};
|
|
2973
|
+
useLoaderDeps = opts => {
|
|
2974
|
+
return useLoaderDeps({
|
|
2975
|
+
...opts,
|
|
2976
|
+
from: this.id
|
|
2977
|
+
});
|
|
2978
|
+
};
|
|
2979
|
+
useLoaderData = opts => {
|
|
2980
|
+
return useLoaderData({
|
|
2981
|
+
...opts,
|
|
2982
|
+
from: this.id
|
|
2983
|
+
});
|
|
2984
|
+
};
|
|
2985
|
+
}
|
|
2986
|
+
function rootRouteWithContext() {
|
|
2987
|
+
return options => {
|
|
2988
|
+
return new RootRoute(options);
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
class RootRoute extends Route {
|
|
2992
|
+
constructor(options) {
|
|
2993
|
+
super(options);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
function createRouteMask(opts) {
|
|
2997
|
+
return opts;
|
|
2998
|
+
}
|
|
2947
2999
|
|
|
2948
|
-
|
|
2949
|
-
pendingMatches = this.matchRoutes(next.pathname, next.search, {
|
|
2950
|
-
debug: true
|
|
2951
|
-
});
|
|
3000
|
+
//
|
|
2952
3001
|
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
return !pendingMatches.find(e => e.id === d.id);
|
|
2962
|
-
})
|
|
2963
|
-
}));
|
|
2964
|
-
});
|
|
2965
|
-
try {
|
|
2966
|
-
try {
|
|
2967
|
-
// Load the matches
|
|
2968
|
-
await this.loadMatches({
|
|
2969
|
-
matches: pendingMatches,
|
|
2970
|
-
checkLatest: () => this.checkLatest(promise)
|
|
2971
|
-
});
|
|
2972
|
-
} catch (err) {
|
|
2973
|
-
// swallow this error, since we'll display the
|
|
2974
|
-
// errors on the route components
|
|
2975
|
-
}
|
|
3002
|
+
class NotFoundRoute extends Route {
|
|
3003
|
+
constructor(options) {
|
|
3004
|
+
super({
|
|
3005
|
+
...options,
|
|
3006
|
+
id: '404'
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
2976
3010
|
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
3011
|
+
class FileRoute {
|
|
3012
|
+
constructor(path) {
|
|
3013
|
+
this.path = path;
|
|
3014
|
+
}
|
|
3015
|
+
createRoute = options => {
|
|
3016
|
+
const route = new Route(options);
|
|
3017
|
+
route.isRoot = false;
|
|
3018
|
+
return route;
|
|
3019
|
+
};
|
|
3020
|
+
}
|
|
3021
|
+
function FileRouteLoader(_path) {
|
|
3022
|
+
return loaderFn => loaderFn;
|
|
3023
|
+
}
|
|
2984
3024
|
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
3025
|
+
function lazyRouteComponent(importer, exportName) {
|
|
3026
|
+
let loadPromise;
|
|
3027
|
+
const load = () => {
|
|
3028
|
+
if (!loadPromise) {
|
|
3029
|
+
loadPromise = importer();
|
|
3030
|
+
}
|
|
3031
|
+
return loadPromise;
|
|
3032
|
+
};
|
|
3033
|
+
const lazyComp = /*#__PURE__*/React__namespace.lazy(async () => {
|
|
3034
|
+
const moduleExports = await load();
|
|
3035
|
+
const comp = moduleExports[exportName ?? 'default'];
|
|
3036
|
+
return {
|
|
3037
|
+
default: comp
|
|
3038
|
+
};
|
|
3039
|
+
});
|
|
3040
|
+
lazyComp.preload = load;
|
|
3041
|
+
return lazyComp;
|
|
3042
|
+
}
|
|
2997
3043
|
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
this.emit({
|
|
3006
|
-
type: 'onLoad',
|
|
3007
|
-
fromLocation: prevLocation,
|
|
3008
|
-
toLocation: next,
|
|
3009
|
-
pathChanged: pathDidChange
|
|
3010
|
-
});
|
|
3011
|
-
resolve();
|
|
3012
|
-
} catch (err) {
|
|
3013
|
-
// Only apply the latest transition
|
|
3014
|
-
if (latestPromise = this.checkLatest(promise)) {
|
|
3015
|
-
return latestPromise;
|
|
3044
|
+
function _extends() {
|
|
3045
|
+
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
3046
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
3047
|
+
var source = arguments[i];
|
|
3048
|
+
for (var key in source) {
|
|
3049
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
3050
|
+
target[key] = source[key];
|
|
3016
3051
|
}
|
|
3017
|
-
reject(err);
|
|
3018
3052
|
}
|
|
3019
|
-
}
|
|
3020
|
-
|
|
3021
|
-
return this.latestLoadPromise;
|
|
3053
|
+
}
|
|
3054
|
+
return target;
|
|
3022
3055
|
};
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3056
|
+
return _extends.apply(this, arguments);
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
const preloadWarning = 'Error preloading route! ☝️';
|
|
3060
|
+
function useLinkProps(options) {
|
|
3061
|
+
const router = useRouter();
|
|
3062
|
+
const matchPathname = useMatch({
|
|
3063
|
+
strict: false,
|
|
3064
|
+
select: s => s.pathname
|
|
3065
|
+
});
|
|
3066
|
+
const {
|
|
3067
|
+
// custom props
|
|
3068
|
+
children,
|
|
3069
|
+
target,
|
|
3070
|
+
activeProps = () => ({
|
|
3071
|
+
className: 'active'
|
|
3072
|
+
}),
|
|
3073
|
+
inactiveProps = () => ({}),
|
|
3074
|
+
activeOptions,
|
|
3075
|
+
disabled,
|
|
3076
|
+
hash,
|
|
3077
|
+
search,
|
|
3078
|
+
params,
|
|
3079
|
+
to,
|
|
3080
|
+
state,
|
|
3081
|
+
mask,
|
|
3082
|
+
preload: userPreload,
|
|
3083
|
+
preloadDelay: userPreloadDelay,
|
|
3084
|
+
replace,
|
|
3085
|
+
startTransition,
|
|
3086
|
+
resetScroll,
|
|
3087
|
+
// element props
|
|
3088
|
+
style,
|
|
3089
|
+
className,
|
|
3090
|
+
onClick,
|
|
3091
|
+
onFocus,
|
|
3092
|
+
onMouseEnter,
|
|
3093
|
+
onMouseLeave,
|
|
3094
|
+
onTouchStart,
|
|
3095
|
+
...rest
|
|
3096
|
+
} = options;
|
|
3033
3097
|
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
let next = this.buildLocation(navigateOpts);
|
|
3044
|
-
let matches = this.matchRoutes(next.pathname, next.search, {
|
|
3045
|
-
throwOnError: true
|
|
3046
|
-
});
|
|
3047
|
-
const loadedMatchIds = Object.fromEntries([...this.state.matches, ...(this.state.pendingMatches ?? []), ...this.state.cachedMatches]?.map(d => [d.id, true]));
|
|
3048
|
-
this.__store.batch(() => {
|
|
3049
|
-
matches.forEach(match => {
|
|
3050
|
-
if (!loadedMatchIds[match.id]) {
|
|
3051
|
-
this.__store.setState(s => ({
|
|
3052
|
-
...s,
|
|
3053
|
-
cachedMatches: [...s.cachedMatches, match]
|
|
3054
|
-
}));
|
|
3055
|
-
}
|
|
3056
|
-
});
|
|
3057
|
-
});
|
|
3058
|
-
matches = await this.loadMatches({
|
|
3059
|
-
matches,
|
|
3060
|
-
preload: true,
|
|
3061
|
-
checkLatest: () => undefined
|
|
3062
|
-
});
|
|
3063
|
-
return matches;
|
|
3098
|
+
// If this link simply reloads the current route,
|
|
3099
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
3100
|
+
|
|
3101
|
+
// If this `to` is a valid external URL, return
|
|
3102
|
+
// null for LinkUtils
|
|
3103
|
+
|
|
3104
|
+
const dest = {
|
|
3105
|
+
from: options.to ? matchPathname : undefined,
|
|
3106
|
+
...options
|
|
3064
3107
|
};
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3108
|
+
let type = 'internal';
|
|
3109
|
+
try {
|
|
3110
|
+
new URL(`${to}`);
|
|
3111
|
+
type = 'external';
|
|
3112
|
+
} catch {}
|
|
3113
|
+
if (type === 'external') {
|
|
3114
|
+
return {
|
|
3115
|
+
href: to
|
|
3069
3116
|
};
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3117
|
+
}
|
|
3118
|
+
const next = router.buildLocation(dest);
|
|
3119
|
+
const preload = userPreload ?? router.options.defaultPreload;
|
|
3120
|
+
const preloadDelay = userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0;
|
|
3121
|
+
const isActive = useRouterState({
|
|
3122
|
+
select: s => {
|
|
3123
|
+
// Compare path/hash for matches
|
|
3124
|
+
const currentPathSplit = s.location.pathname.split('/');
|
|
3125
|
+
const nextPathSplit = next.pathname.split('/');
|
|
3126
|
+
const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
|
|
3127
|
+
// Combine the matches based on user router.options
|
|
3128
|
+
const pathTest = activeOptions?.exact ? s.location.pathname === next.pathname : pathIsFuzzyEqual;
|
|
3129
|
+
const hashTest = activeOptions?.includeHash ? s.location.hash === next.hash : true;
|
|
3130
|
+
const searchTest = activeOptions?.includeSearch ?? true ? deepEqual(s.location.search, next.search, !activeOptions?.exact) : true;
|
|
3131
|
+
|
|
3132
|
+
// The final "active" test
|
|
3133
|
+
return pathTest && hashTest && searchTest;
|
|
3084
3134
|
}
|
|
3085
|
-
|
|
3086
|
-
|
|
3135
|
+
});
|
|
3136
|
+
|
|
3137
|
+
// The click handler
|
|
3138
|
+
const handleClick = e => {
|
|
3139
|
+
if (!disabled && !isCtrlEvent(e) && !e.defaultPrevented && (!target || target === '_self') && e.button === 0) {
|
|
3140
|
+
e.preventDefault();
|
|
3141
|
+
|
|
3142
|
+
// All is well? Navigate!
|
|
3143
|
+
router.commitLocation({
|
|
3144
|
+
...next,
|
|
3145
|
+
replace,
|
|
3146
|
+
resetScroll,
|
|
3147
|
+
startTransition
|
|
3148
|
+
});
|
|
3087
3149
|
}
|
|
3088
|
-
return match;
|
|
3089
|
-
};
|
|
3090
|
-
injectHtml = async html => {
|
|
3091
|
-
this.injectedHtml.push(html);
|
|
3092
3150
|
};
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
;(() => {
|
|
3101
|
-
var el = document.getElementById('${id}')
|
|
3102
|
-
el.parentElement.removeChild(el)
|
|
3103
|
-
})()
|
|
3104
|
-
</script>`;
|
|
3151
|
+
|
|
3152
|
+
// The click handler
|
|
3153
|
+
const handleFocus = e => {
|
|
3154
|
+
if (preload) {
|
|
3155
|
+
router.preloadRoute(dest).catch(err => {
|
|
3156
|
+
console.warn(err);
|
|
3157
|
+
console.warn(preloadWarning);
|
|
3105
3158
|
});
|
|
3106
|
-
return () => this.hydrateData(key);
|
|
3107
3159
|
}
|
|
3108
|
-
return () => undefined;
|
|
3109
3160
|
};
|
|
3110
|
-
|
|
3111
|
-
if (
|
|
3112
|
-
|
|
3113
|
-
|
|
3161
|
+
const handleTouchStart = e => {
|
|
3162
|
+
if (preload) {
|
|
3163
|
+
router.preloadRoute(dest).catch(err => {
|
|
3164
|
+
console.warn(err);
|
|
3165
|
+
console.warn(preloadWarning);
|
|
3166
|
+
});
|
|
3114
3167
|
}
|
|
3115
|
-
return undefined;
|
|
3116
3168
|
};
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3169
|
+
const handleEnter = e => {
|
|
3170
|
+
const target = e.target || {};
|
|
3171
|
+
if (preload) {
|
|
3172
|
+
if (target.preloadTimeout) {
|
|
3173
|
+
return;
|
|
3121
3174
|
}
|
|
3122
|
-
|
|
3175
|
+
target.preloadTimeout = setTimeout(() => {
|
|
3176
|
+
target.preloadTimeout = null;
|
|
3177
|
+
router.preloadRoute(dest).catch(err => {
|
|
3178
|
+
console.warn(err);
|
|
3179
|
+
console.warn(preloadWarning);
|
|
3180
|
+
});
|
|
3181
|
+
}, preloadDelay);
|
|
3182
|
+
}
|
|
3123
3183
|
};
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3184
|
+
const handleLeave = e => {
|
|
3185
|
+
const target = e.target || {};
|
|
3186
|
+
if (target.preloadTimeout) {
|
|
3187
|
+
clearTimeout(target.preloadTimeout);
|
|
3188
|
+
target.preloadTimeout = null;
|
|
3129
3189
|
}
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
const dehydratedMatch = dehydratedState.dehydratedMatches.find(d => d.id === match.id);
|
|
3137
|
-
invariant(dehydratedMatch, `Could not find a client-side match for dehydrated match with id: ${match.id}!`);
|
|
3138
|
-
if (dehydratedMatch) {
|
|
3139
|
-
return {
|
|
3140
|
-
...match,
|
|
3141
|
-
...dehydratedMatch
|
|
3142
|
-
};
|
|
3143
|
-
}
|
|
3144
|
-
return match;
|
|
3145
|
-
});
|
|
3146
|
-
this.__store.setState(s => {
|
|
3147
|
-
return {
|
|
3148
|
-
...s,
|
|
3149
|
-
matches: matches
|
|
3150
|
-
};
|
|
3190
|
+
};
|
|
3191
|
+
const composeHandlers = handlers => e => {
|
|
3192
|
+
if (e.persist) e.persist();
|
|
3193
|
+
handlers.filter(Boolean).forEach(handler => {
|
|
3194
|
+
if (e.defaultPrevented) return;
|
|
3195
|
+
handler(e);
|
|
3151
3196
|
});
|
|
3152
3197
|
};
|
|
3153
3198
|
|
|
3154
|
-
//
|
|
3155
|
-
|
|
3156
|
-
// .find((d) => d.id === matchId)
|
|
3157
|
-
// ?.__promisesByKey[key]?.resolve(value)
|
|
3158
|
-
// }
|
|
3159
|
-
}
|
|
3199
|
+
// Get the active props
|
|
3200
|
+
const resolvedActiveProps = isActive ? functionalUpdate(activeProps, {}) ?? {} : {};
|
|
3160
3201
|
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
// information along the way
|
|
3164
|
-
function lazyFn(fn, key) {
|
|
3165
|
-
return async (...args) => {
|
|
3166
|
-
const imported = await fn();
|
|
3167
|
-
return imported[key || 'default'](...args);
|
|
3168
|
-
};
|
|
3169
|
-
}
|
|
3170
|
-
class SearchParamError extends Error {}
|
|
3171
|
-
class PathParamError extends Error {}
|
|
3172
|
-
function getInitialRouterState(location) {
|
|
3202
|
+
// Get the inactive props
|
|
3203
|
+
const resolvedInactiveProps = isActive ? {} : functionalUpdate(inactiveProps, {}) ?? {};
|
|
3173
3204
|
return {
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3205
|
+
...resolvedActiveProps,
|
|
3206
|
+
...resolvedInactiveProps,
|
|
3207
|
+
...rest,
|
|
3208
|
+
href: disabled ? undefined : next.maskedLocation ? next.maskedLocation.href : next.href,
|
|
3209
|
+
onClick: composeHandlers([onClick, handleClick]),
|
|
3210
|
+
onFocus: composeHandlers([onFocus, handleFocus]),
|
|
3211
|
+
onMouseEnter: composeHandlers([onMouseEnter, handleEnter]),
|
|
3212
|
+
onMouseLeave: composeHandlers([onMouseLeave, handleLeave]),
|
|
3213
|
+
onTouchStart: composeHandlers([onTouchStart, handleTouchStart]),
|
|
3214
|
+
target,
|
|
3215
|
+
style: {
|
|
3216
|
+
...style,
|
|
3217
|
+
...resolvedActiveProps.style,
|
|
3218
|
+
...resolvedInactiveProps.style
|
|
3179
3219
|
},
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3220
|
+
className: [className, resolvedActiveProps.className, resolvedInactiveProps.className].filter(Boolean).join(' ') || undefined,
|
|
3221
|
+
...(disabled ? {
|
|
3222
|
+
role: 'link',
|
|
3223
|
+
'aria-disabled': true
|
|
3224
|
+
} : undefined),
|
|
3225
|
+
['data-status']: isActive ? 'active' : undefined
|
|
3185
3226
|
};
|
|
3186
3227
|
}
|
|
3228
|
+
const Link = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
|
|
3229
|
+
const linkProps = useLinkProps(props);
|
|
3230
|
+
return /*#__PURE__*/React__namespace.createElement("a", _extends({
|
|
3231
|
+
ref: ref
|
|
3232
|
+
}, linkProps, {
|
|
3233
|
+
children: typeof props.children === 'function' ? props.children({
|
|
3234
|
+
isActive: linkProps['data-status'] === 'active'
|
|
3235
|
+
}) : props.children
|
|
3236
|
+
}));
|
|
3237
|
+
});
|
|
3238
|
+
function isCtrlEvent(e) {
|
|
3239
|
+
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
|
|
3240
|
+
}
|
|
3187
3241
|
|
|
3188
3242
|
const useLayoutEffect = typeof window !== 'undefined' ? React__namespace.useLayoutEffect : React__namespace.useEffect;
|
|
3189
3243
|
const windowKey = 'window';
|
|
@@ -3454,7 +3508,9 @@
|
|
|
3454
3508
|
exports.createRouteMask = createRouteMask;
|
|
3455
3509
|
exports.decode = decode;
|
|
3456
3510
|
exports.deepEqual = deepEqual;
|
|
3511
|
+
exports.defaultDeserializeError = defaultDeserializeError;
|
|
3457
3512
|
exports.defaultParseSearch = defaultParseSearch;
|
|
3513
|
+
exports.defaultSerializeError = defaultSerializeError;
|
|
3458
3514
|
exports.defaultStringifySearch = defaultStringifySearch;
|
|
3459
3515
|
exports.defer = defer;
|
|
3460
3516
|
exports.encode = encode;
|
|
@@ -3470,6 +3526,7 @@
|
|
|
3470
3526
|
exports.isPlainObject = isPlainObject;
|
|
3471
3527
|
exports.isRedirect = isRedirect;
|
|
3472
3528
|
exports.isServer = isServer;
|
|
3529
|
+
exports.isServerSideError = isServerSideError;
|
|
3473
3530
|
exports.joinPaths = joinPaths;
|
|
3474
3531
|
exports.last = last;
|
|
3475
3532
|
exports.lazyFn = lazyFn;
|