@tanstack/react-router 0.0.1-beta.162 → 0.0.1-beta.164
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/index.js +3 -0
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/scroll-restoration.js +56 -0
- package/build/cjs/scroll-restoration.js.map +1 -0
- package/build/esm/index.js +17 -2
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +113 -63
- package/build/types/index.d.ts +5 -2
- package/build/umd/index.development.js +196 -40
- 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 +3 -3
- package/src/index.tsx +2 -0
- package/src/scroll-restoration.tsx +27 -0
|
@@ -208,7 +208,7 @@
|
|
|
208
208
|
function createHistory(opts) {
|
|
209
209
|
let location = opts.getLocation();
|
|
210
210
|
let unsub = () => {};
|
|
211
|
-
let
|
|
211
|
+
let subscribers = new Set();
|
|
212
212
|
let blockers = [];
|
|
213
213
|
let queue = [];
|
|
214
214
|
const tryFlush = () => {
|
|
@@ -222,7 +222,7 @@
|
|
|
222
222
|
while (queue.length) {
|
|
223
223
|
queue.shift()?.();
|
|
224
224
|
}
|
|
225
|
-
if (!opts.
|
|
225
|
+
if (!opts.subscriber) {
|
|
226
226
|
onUpdate();
|
|
227
227
|
}
|
|
228
228
|
};
|
|
@@ -232,20 +232,20 @@
|
|
|
232
232
|
};
|
|
233
233
|
const onUpdate = () => {
|
|
234
234
|
location = opts.getLocation();
|
|
235
|
-
|
|
235
|
+
subscribers.forEach(subscriber => subscriber());
|
|
236
236
|
};
|
|
237
237
|
return {
|
|
238
238
|
get location() {
|
|
239
239
|
return location;
|
|
240
240
|
},
|
|
241
|
-
|
|
242
|
-
if (
|
|
243
|
-
unsub = typeof opts.
|
|
241
|
+
subscribe: cb => {
|
|
242
|
+
if (subscribers.size === 0) {
|
|
243
|
+
unsub = typeof opts.subscriber === 'function' ? opts.subscriber(onUpdate) : () => {};
|
|
244
244
|
}
|
|
245
|
-
|
|
245
|
+
subscribers.add(cb);
|
|
246
246
|
return () => {
|
|
247
|
-
|
|
248
|
-
if (
|
|
247
|
+
subscribers.delete(cb);
|
|
248
|
+
if (subscribers.size === 0) {
|
|
249
249
|
unsub();
|
|
250
250
|
}
|
|
251
251
|
};
|
|
@@ -298,7 +298,7 @@
|
|
|
298
298
|
const getLocation = () => parseLocation(getHref(), history.state);
|
|
299
299
|
return createHistory({
|
|
300
300
|
getLocation,
|
|
301
|
-
|
|
301
|
+
subscriber: onUpdate => {
|
|
302
302
|
window.addEventListener(pushStateEvent, onUpdate);
|
|
303
303
|
window.addEventListener(popStateEvent, onUpdate);
|
|
304
304
|
var pushState = window.history.pushState;
|
|
@@ -353,7 +353,7 @@
|
|
|
353
353
|
const getLocation = () => parseLocation(entries[index], currentState);
|
|
354
354
|
return createHistory({
|
|
355
355
|
getLocation,
|
|
356
|
-
|
|
356
|
+
subscriber: false,
|
|
357
357
|
pushState: (path, state) => {
|
|
358
358
|
currentState = {
|
|
359
359
|
...state,
|
|
@@ -928,6 +928,24 @@
|
|
|
928
928
|
});
|
|
929
929
|
}
|
|
930
930
|
}
|
|
931
|
+
subscribers = new Set();
|
|
932
|
+
subscribe = (eventType, fn) => {
|
|
933
|
+
const listener = {
|
|
934
|
+
eventType,
|
|
935
|
+
fn
|
|
936
|
+
};
|
|
937
|
+
this.subscribers.add(listener);
|
|
938
|
+
return () => {
|
|
939
|
+
this.subscribers.delete(listener);
|
|
940
|
+
};
|
|
941
|
+
};
|
|
942
|
+
#emit = routerEvent => {
|
|
943
|
+
this.subscribers.forEach(listener => {
|
|
944
|
+
if (listener.eventType === routerEvent.type) {
|
|
945
|
+
listener.fn(routerEvent);
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
};
|
|
931
949
|
reset = () => {
|
|
932
950
|
this.__store.setState(s => Object.assign(s, getInitialRouterState()));
|
|
933
951
|
};
|
|
@@ -958,7 +976,7 @@
|
|
|
958
976
|
resolvedLocation: parsedLocation,
|
|
959
977
|
location: parsedLocation
|
|
960
978
|
}));
|
|
961
|
-
this.#unsubHistory = this.history.
|
|
979
|
+
this.#unsubHistory = this.history.subscribe(() => {
|
|
962
980
|
this.safeLoad({
|
|
963
981
|
next: this.#parseLocation(this.state.location)
|
|
964
982
|
});
|
|
@@ -1000,6 +1018,8 @@
|
|
|
1000
1018
|
latestLoadPromise = Promise.resolve();
|
|
1001
1019
|
load = async opts => {
|
|
1002
1020
|
const promise = new Promise(async (resolve, reject) => {
|
|
1021
|
+
const prevLocation = this.state.resolvedLocation;
|
|
1022
|
+
const pathDidChange = !!(opts?.next && prevLocation.href !== opts.next.href);
|
|
1003
1023
|
let latestPromise;
|
|
1004
1024
|
const checkLatest = () => {
|
|
1005
1025
|
return this.latestLoadPromise !== promise ? this.latestLoadPromise : undefined;
|
|
@@ -1009,6 +1029,12 @@
|
|
|
1009
1029
|
// this.cancelMatches()
|
|
1010
1030
|
|
|
1011
1031
|
let pendingMatches;
|
|
1032
|
+
this.#emit({
|
|
1033
|
+
type: 'onBeforeLoad',
|
|
1034
|
+
from: prevLocation,
|
|
1035
|
+
to: opts?.next ?? this.state.location,
|
|
1036
|
+
pathChanged: pathDidChange
|
|
1037
|
+
});
|
|
1012
1038
|
this.__store.batch(() => {
|
|
1013
1039
|
if (opts?.next) {
|
|
1014
1040
|
// Ingest the new location
|
|
@@ -1043,7 +1069,6 @@
|
|
|
1043
1069
|
if (latestPromise = checkLatest()) {
|
|
1044
1070
|
return latestPromise;
|
|
1045
1071
|
}
|
|
1046
|
-
const prevLocation = this.state.resolvedLocation;
|
|
1047
1072
|
this.__store.setState(s => ({
|
|
1048
1073
|
...s,
|
|
1049
1074
|
status: 'idle',
|
|
@@ -1051,9 +1076,12 @@
|
|
|
1051
1076
|
matchIds: s.pendingMatchIds,
|
|
1052
1077
|
pendingMatchIds: []
|
|
1053
1078
|
}));
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1079
|
+
this.#emit({
|
|
1080
|
+
type: 'onLoad',
|
|
1081
|
+
from: prevLocation,
|
|
1082
|
+
to: this.state.location,
|
|
1083
|
+
pathChanged: pathDidChange
|
|
1084
|
+
});
|
|
1057
1085
|
resolve();
|
|
1058
1086
|
} catch (err) {
|
|
1059
1087
|
// Only apply the latest transition
|
|
@@ -1303,14 +1331,14 @@
|
|
|
1303
1331
|
try {
|
|
1304
1332
|
for (const [index, match] of resolvedMatches.entries()) {
|
|
1305
1333
|
const route = this.getRoute(match.routeId);
|
|
1306
|
-
const handleError = (err,
|
|
1334
|
+
const handleError = (err, code) => {
|
|
1335
|
+
err.routerCode = code;
|
|
1307
1336
|
firstBadMatchIndex = firstBadMatchIndex ?? index;
|
|
1308
|
-
handler = handler || route.options.onError;
|
|
1309
1337
|
if (isRedirect(err)) {
|
|
1310
1338
|
throw err;
|
|
1311
1339
|
}
|
|
1312
1340
|
try {
|
|
1313
|
-
|
|
1341
|
+
route.options.onError?.(err);
|
|
1314
1342
|
} catch (errorHandlerErr) {
|
|
1315
1343
|
err = errorHandlerErr;
|
|
1316
1344
|
if (isRedirect(errorHandlerErr)) {
|
|
@@ -1325,10 +1353,10 @@
|
|
|
1325
1353
|
}));
|
|
1326
1354
|
};
|
|
1327
1355
|
if (match.paramsError) {
|
|
1328
|
-
handleError(match.paramsError,
|
|
1356
|
+
handleError(match.paramsError, 'PARSE_PARAMS');
|
|
1329
1357
|
}
|
|
1330
1358
|
if (match.searchError) {
|
|
1331
|
-
handleError(match.searchError,
|
|
1359
|
+
handleError(match.searchError, 'VALIDATE_SEARCH');
|
|
1332
1360
|
}
|
|
1333
1361
|
let didError = false;
|
|
1334
1362
|
try {
|
|
@@ -1337,7 +1365,7 @@
|
|
|
1337
1365
|
preload: !!opts?.preload
|
|
1338
1366
|
});
|
|
1339
1367
|
} catch (err) {
|
|
1340
|
-
handleError(err,
|
|
1368
|
+
handleError(err, 'BEFORE_LOAD');
|
|
1341
1369
|
didError = true;
|
|
1342
1370
|
}
|
|
1343
1371
|
|
|
@@ -1395,28 +1423,18 @@
|
|
|
1395
1423
|
const [_, loader] = await Promise.all([componentsPromise, loaderPromise]);
|
|
1396
1424
|
if (latestPromise = checkLatest()) return await latestPromise;
|
|
1397
1425
|
this.setRouteMatchData(match.id, () => loader, opts);
|
|
1398
|
-
} catch (
|
|
1399
|
-
let latestError = loaderError;
|
|
1426
|
+
} catch (error) {
|
|
1400
1427
|
if (latestPromise = checkLatest()) return await latestPromise;
|
|
1401
|
-
if (handleIfRedirect(
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
if (handleIfRedirect(onLoadError)) return;
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
if ((!route.options.onLoadError || latestError !== loaderError) && route.options.onError) {
|
|
1411
|
-
try {
|
|
1412
|
-
route.options.onError(latestError);
|
|
1413
|
-
} catch (onErrorError) {
|
|
1414
|
-
if (handleIfRedirect(onErrorError)) return;
|
|
1415
|
-
}
|
|
1428
|
+
if (handleIfRedirect(error)) return;
|
|
1429
|
+
try {
|
|
1430
|
+
route.options.onError?.(error);
|
|
1431
|
+
} catch (onErrorError) {
|
|
1432
|
+
error = onErrorError;
|
|
1433
|
+
if (handleIfRedirect(onErrorError)) return;
|
|
1416
1434
|
}
|
|
1417
1435
|
this.setRouteMatch(match.id, s => ({
|
|
1418
1436
|
...s,
|
|
1419
|
-
error
|
|
1437
|
+
error,
|
|
1420
1438
|
status: 'error',
|
|
1421
1439
|
isFetching: false,
|
|
1422
1440
|
updatedAt: Date.now()
|
|
@@ -1973,6 +1991,140 @@
|
|
|
1973
1991
|
};
|
|
1974
1992
|
}
|
|
1975
1993
|
|
|
1994
|
+
const windowKey = 'window';
|
|
1995
|
+
const delimiter = '___';
|
|
1996
|
+
let weakScrolledElementsByRestoreKey = {};
|
|
1997
|
+
let cache;
|
|
1998
|
+
let pathDidChange = false;
|
|
1999
|
+
const sessionsStorage = typeof window !== 'undefined' && window.sessionStorage;
|
|
2000
|
+
const defaultGetKey = location => location.key;
|
|
2001
|
+
function watchScrollPositions(router, opts) {
|
|
2002
|
+
const getKey = opts?.getKey || defaultGetKey;
|
|
2003
|
+
if (sessionsStorage) {
|
|
2004
|
+
if (!cache) {
|
|
2005
|
+
cache = (() => {
|
|
2006
|
+
const storageKey = 'tsr-scroll-restoration-v1';
|
|
2007
|
+
const current = JSON.parse(window.sessionStorage.getItem(storageKey) || '{}');
|
|
2008
|
+
return {
|
|
2009
|
+
current,
|
|
2010
|
+
set: (key, value) => {
|
|
2011
|
+
current[key] = value;
|
|
2012
|
+
window.sessionStorage.setItem(storageKey, JSON.stringify(cache));
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
2015
|
+
})();
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
const {
|
|
2019
|
+
history
|
|
2020
|
+
} = window;
|
|
2021
|
+
if (history.scrollRestoration) {
|
|
2022
|
+
history.scrollRestoration = 'manual';
|
|
2023
|
+
}
|
|
2024
|
+
const onScroll = event => {
|
|
2025
|
+
const restoreKey = getKey(router.state.resolvedLocation);
|
|
2026
|
+
if (!weakScrolledElementsByRestoreKey[restoreKey]) {
|
|
2027
|
+
weakScrolledElementsByRestoreKey[restoreKey] = new WeakSet();
|
|
2028
|
+
}
|
|
2029
|
+
const set = weakScrolledElementsByRestoreKey[restoreKey];
|
|
2030
|
+
if (set.has(event.target)) return;
|
|
2031
|
+
set.add(event.target);
|
|
2032
|
+
const cacheKey = [restoreKey, event.target === document || event.target === window ? windowKey : getCssSelector(event.target)].join(delimiter);
|
|
2033
|
+
if (!cache.current[cacheKey]) {
|
|
2034
|
+
cache.set(cacheKey, {
|
|
2035
|
+
scrollX: NaN,
|
|
2036
|
+
scrollY: NaN
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
};
|
|
2040
|
+
const getCssSelector = el => {
|
|
2041
|
+
let path = [],
|
|
2042
|
+
parent;
|
|
2043
|
+
while (parent = el.parentNode) {
|
|
2044
|
+
path.unshift(`${el.tagName}:nth-child(${[].indexOf.call(parent.children, el) + 1})`);
|
|
2045
|
+
el = parent;
|
|
2046
|
+
}
|
|
2047
|
+
return `${path.join(' > ')}`.toLowerCase();
|
|
2048
|
+
};
|
|
2049
|
+
const onPathWillChange = from => {
|
|
2050
|
+
const restoreKey = getKey(from);
|
|
2051
|
+
for (const cacheKey in cache.current) {
|
|
2052
|
+
const entry = cache.current[cacheKey];
|
|
2053
|
+
const [key, elementSelector] = cacheKey.split(delimiter);
|
|
2054
|
+
if (restoreKey === key) {
|
|
2055
|
+
if (elementSelector === windowKey) {
|
|
2056
|
+
entry.scrollX = window.scrollX || 0;
|
|
2057
|
+
entry.scrollY = window.scrollY || 0;
|
|
2058
|
+
} else if (elementSelector) {
|
|
2059
|
+
const element = document.querySelector(elementSelector);
|
|
2060
|
+
entry.scrollX = element?.scrollLeft || 0;
|
|
2061
|
+
entry.scrollY = element?.scrollTop || 0;
|
|
2062
|
+
}
|
|
2063
|
+
cache.set(cacheKey, entry);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
};
|
|
2067
|
+
const onPathChange = () => {
|
|
2068
|
+
pathDidChange = true;
|
|
2069
|
+
};
|
|
2070
|
+
if (typeof document !== 'undefined') {
|
|
2071
|
+
document.addEventListener('scroll', onScroll, true);
|
|
2072
|
+
}
|
|
2073
|
+
const unsubOnBeforeLoad = router.subscribe('onBeforeLoad', event => {
|
|
2074
|
+
if (event.pathChanged) onPathWillChange(event.from);
|
|
2075
|
+
});
|
|
2076
|
+
const unsubOnLoad = router.subscribe('onLoad', event => {
|
|
2077
|
+
if (event.pathChanged) onPathChange();
|
|
2078
|
+
});
|
|
2079
|
+
return () => {
|
|
2080
|
+
document.removeEventListener('scroll', onScroll);
|
|
2081
|
+
unsubOnBeforeLoad();
|
|
2082
|
+
unsubOnLoad();
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
function restoreScrollPositions(router, opts) {
|
|
2086
|
+
if (pathDidChange) {
|
|
2087
|
+
const getKey = opts?.getKey || defaultGetKey;
|
|
2088
|
+
pathDidChange = false;
|
|
2089
|
+
const restoreKey = getKey(router.state.location);
|
|
2090
|
+
let windowRestored = false;
|
|
2091
|
+
for (const cacheKey in cache.current) {
|
|
2092
|
+
const entry = cache.current[cacheKey];
|
|
2093
|
+
const [key, elementSelector] = cacheKey.split(delimiter);
|
|
2094
|
+
if (key === restoreKey) {
|
|
2095
|
+
if (elementSelector === windowKey) {
|
|
2096
|
+
windowRestored = true;
|
|
2097
|
+
window.scrollTo(entry.scrollX, entry.scrollY);
|
|
2098
|
+
} else if (elementSelector) {
|
|
2099
|
+
const element = document.querySelector(elementSelector);
|
|
2100
|
+
if (element) {
|
|
2101
|
+
element.scrollLeft = entry.scrollX;
|
|
2102
|
+
element.scrollTop = entry.scrollY;
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
if (!windowRestored) {
|
|
2108
|
+
window.scrollTo(0, 0);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
const useLayoutEffect = typeof window !== 'undefined' ? React__namespace.useLayoutEffect : React__namespace.useEffect;
|
|
2114
|
+
function useScrollRestoration(options) {
|
|
2115
|
+
const router = useRouter();
|
|
2116
|
+
useLayoutEffect(() => {
|
|
2117
|
+
return watchScrollPositions(router, options);
|
|
2118
|
+
}, []);
|
|
2119
|
+
useLayoutEffect(() => {
|
|
2120
|
+
restoreScrollPositions(router, options);
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
function ScrollRestoration(props) {
|
|
2124
|
+
useScrollRestoration(props);
|
|
2125
|
+
return null;
|
|
2126
|
+
}
|
|
2127
|
+
|
|
1976
2128
|
Route.__onInit = route => {
|
|
1977
2129
|
Object.assign(route, {
|
|
1978
2130
|
useMatch: (opts = {}) => {
|
|
@@ -2551,6 +2703,7 @@
|
|
|
2551
2703
|
exports.Router = Router;
|
|
2552
2704
|
exports.RouterContext = RouterContext;
|
|
2553
2705
|
exports.RouterProvider = RouterProvider;
|
|
2706
|
+
exports.ScrollRestoration = ScrollRestoration;
|
|
2554
2707
|
exports.SearchParamError = SearchParamError;
|
|
2555
2708
|
exports.cleanPath = cleanPath;
|
|
2556
2709
|
exports.componentTypes = componentTypes;
|
|
@@ -2580,6 +2733,7 @@
|
|
|
2580
2733
|
exports.redirect = redirect;
|
|
2581
2734
|
exports.replaceEqualDeep = replaceEqualDeep;
|
|
2582
2735
|
exports.resolvePath = resolvePath;
|
|
2736
|
+
exports.restoreScrollPositions = restoreScrollPositions;
|
|
2583
2737
|
exports.rootRouteId = rootRouteId;
|
|
2584
2738
|
exports.routerContext = routerContext;
|
|
2585
2739
|
exports.shallow = shallow;
|
|
@@ -2602,9 +2756,11 @@
|
|
|
2602
2756
|
exports.useRouter = useRouter;
|
|
2603
2757
|
exports.useRouterContext = useRouterContext;
|
|
2604
2758
|
exports.useRouterState = useRouterState;
|
|
2759
|
+
exports.useScrollRestoration = useScrollRestoration;
|
|
2605
2760
|
exports.useSearch = useSearch;
|
|
2606
2761
|
exports.useStore = useStore;
|
|
2607
2762
|
exports.warning = warning;
|
|
2763
|
+
exports.watchScrollPositions = watchScrollPositions;
|
|
2608
2764
|
|
|
2609
2765
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2610
2766
|
|