@tanstack/react-router 0.0.1-beta.162 → 0.0.1-beta.163
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 +2 -0
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/scroll-restoration.js +164 -0
- package/build/cjs/scroll-restoration.js.map +1 -0
- package/build/esm/index.js +126 -1
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +107 -63
- package/build/types/index.d.ts +3 -1
- package/build/umd/index.development.js +184 -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 +163 -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,131 @@
|
|
|
1973
1991
|
};
|
|
1974
1992
|
}
|
|
1975
1993
|
|
|
1994
|
+
const useLayoutEffect = typeof window !== 'undefined' ? React__namespace.useLayoutEffect : React__namespace.useEffect;
|
|
1995
|
+
const windowKey = 'window';
|
|
1996
|
+
const delimiter = '___';
|
|
1997
|
+
let weakScrolledElementsByRestoreKey = {};
|
|
1998
|
+
const cache = (() => {
|
|
1999
|
+
if (typeof window === 'undefined') {
|
|
2000
|
+
return {
|
|
2001
|
+
set: () => {},
|
|
2002
|
+
get: () => {}
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
const storageKey = 'tsr-scroll-restoration-v1';
|
|
2006
|
+
let cache = JSON.parse(window.sessionStorage.getItem(storageKey) || '{}');
|
|
2007
|
+
return {
|
|
2008
|
+
current: cache,
|
|
2009
|
+
set: (key, value) => {
|
|
2010
|
+
cache[key] = value;
|
|
2011
|
+
window.sessionStorage.setItem(storageKey, JSON.stringify(cache));
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
})();
|
|
2015
|
+
function ScrollRestoration() {
|
|
2016
|
+
const router = useRouter();
|
|
2017
|
+
const getKey = location => location.key;
|
|
2018
|
+
const pathDidChangeRef = React__namespace.useRef(false);
|
|
2019
|
+
const restoreScrollPositions = () => {
|
|
2020
|
+
const restoreKey = getKey(router.state.location);
|
|
2021
|
+
let windowRestored = false;
|
|
2022
|
+
for (const cacheKey in cache.current) {
|
|
2023
|
+
const entry = cache.current[cacheKey];
|
|
2024
|
+
const [key, elementSelector] = cacheKey.split(delimiter);
|
|
2025
|
+
if (key === restoreKey) {
|
|
2026
|
+
if (elementSelector === windowKey) {
|
|
2027
|
+
windowRestored = true;
|
|
2028
|
+
window.scrollTo(entry.scrollX, entry.scrollY);
|
|
2029
|
+
} else if (elementSelector) {
|
|
2030
|
+
const element = document.querySelector(elementSelector);
|
|
2031
|
+
if (element) {
|
|
2032
|
+
element.scrollLeft = entry.scrollX;
|
|
2033
|
+
element.scrollTop = entry.scrollY;
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
if (!windowRestored) {
|
|
2039
|
+
window.scrollTo(0, 0);
|
|
2040
|
+
}
|
|
2041
|
+
};
|
|
2042
|
+
useLayoutEffect(() => {
|
|
2043
|
+
const {
|
|
2044
|
+
history
|
|
2045
|
+
} = window;
|
|
2046
|
+
if (history.scrollRestoration) {
|
|
2047
|
+
history.scrollRestoration = 'manual';
|
|
2048
|
+
}
|
|
2049
|
+
const onScroll = event => {
|
|
2050
|
+
const restoreKey = getKey(router.state.resolvedLocation);
|
|
2051
|
+
if (!weakScrolledElementsByRestoreKey[restoreKey]) {
|
|
2052
|
+
weakScrolledElementsByRestoreKey[restoreKey] = new WeakSet();
|
|
2053
|
+
}
|
|
2054
|
+
const set = weakScrolledElementsByRestoreKey[restoreKey];
|
|
2055
|
+
if (set.has(event.target)) return;
|
|
2056
|
+
set.add(event.target);
|
|
2057
|
+
const cacheKey = [restoreKey, event.target === document || event.target === window ? windowKey : getCssSelector(event.target)].join(delimiter);
|
|
2058
|
+
if (!cache.current[cacheKey]) {
|
|
2059
|
+
cache.set(cacheKey, {
|
|
2060
|
+
scrollX: NaN,
|
|
2061
|
+
scrollY: NaN
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
};
|
|
2065
|
+
const getCssSelector = el => {
|
|
2066
|
+
let path = [],
|
|
2067
|
+
parent;
|
|
2068
|
+
while (parent = el.parentNode) {
|
|
2069
|
+
path.unshift(`${el.tagName}:nth-child(${[].indexOf.call(parent.children, el) + 1})`);
|
|
2070
|
+
el = parent;
|
|
2071
|
+
}
|
|
2072
|
+
return `${path.join(' > ')}`.toLowerCase();
|
|
2073
|
+
};
|
|
2074
|
+
const onPathWillChange = from => {
|
|
2075
|
+
const restoreKey = getKey(from);
|
|
2076
|
+
for (const cacheKey in cache.current) {
|
|
2077
|
+
const entry = cache.current[cacheKey];
|
|
2078
|
+
const [key, elementSelector] = cacheKey.split(delimiter);
|
|
2079
|
+
if (restoreKey === key) {
|
|
2080
|
+
if (elementSelector === windowKey) {
|
|
2081
|
+
entry.scrollX = window.scrollX || 0;
|
|
2082
|
+
entry.scrollY = window.scrollY || 0;
|
|
2083
|
+
} else if (elementSelector) {
|
|
2084
|
+
const element = document.querySelector(elementSelector);
|
|
2085
|
+
entry.scrollX = element?.scrollLeft || 0;
|
|
2086
|
+
entry.scrollY = element?.scrollTop || 0;
|
|
2087
|
+
}
|
|
2088
|
+
cache.set(cacheKey, entry);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
};
|
|
2092
|
+
const onPathChange = () => {
|
|
2093
|
+
pathDidChangeRef.current = true;
|
|
2094
|
+
};
|
|
2095
|
+
if (typeof document !== 'undefined') {
|
|
2096
|
+
document.addEventListener('scroll', onScroll, true);
|
|
2097
|
+
}
|
|
2098
|
+
const unsubOnBeforeLoad = router.subscribe('onBeforeLoad', event => {
|
|
2099
|
+
if (event.pathChanged) onPathWillChange(event.from);
|
|
2100
|
+
});
|
|
2101
|
+
const unsubOnLoad = router.subscribe('onLoad', event => {
|
|
2102
|
+
if (event.pathChanged) onPathChange();
|
|
2103
|
+
});
|
|
2104
|
+
return () => {
|
|
2105
|
+
document.removeEventListener('scroll', onScroll);
|
|
2106
|
+
unsubOnBeforeLoad();
|
|
2107
|
+
unsubOnLoad();
|
|
2108
|
+
};
|
|
2109
|
+
}, []);
|
|
2110
|
+
useLayoutEffect(() => {
|
|
2111
|
+
if (pathDidChangeRef.current) {
|
|
2112
|
+
pathDidChangeRef.current = false;
|
|
2113
|
+
restoreScrollPositions();
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
return null;
|
|
2117
|
+
}
|
|
2118
|
+
|
|
1976
2119
|
Route.__onInit = route => {
|
|
1977
2120
|
Object.assign(route, {
|
|
1978
2121
|
useMatch: (opts = {}) => {
|
|
@@ -2551,6 +2694,7 @@
|
|
|
2551
2694
|
exports.Router = Router;
|
|
2552
2695
|
exports.RouterContext = RouterContext;
|
|
2553
2696
|
exports.RouterProvider = RouterProvider;
|
|
2697
|
+
exports.ScrollRestoration = ScrollRestoration;
|
|
2554
2698
|
exports.SearchParamError = SearchParamError;
|
|
2555
2699
|
exports.cleanPath = cleanPath;
|
|
2556
2700
|
exports.componentTypes = componentTypes;
|