@tanstack/router-core 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/history.js +11 -11
- package/build/cjs/history.js.map +1 -1
- package/build/cjs/route.js.map +1 -1
- package/build/cjs/router.js +47 -29
- package/build/cjs/router.js.map +1 -1
- package/build/esm/index.js +58 -40
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +118 -118
- package/build/types/index.d.ts +24 -12
- package/build/umd/index.development.js +58 -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 +2 -2
- package/src/history.ts +14 -14
- package/src/route.ts +1 -10
- package/src/router.ts +82 -39
package/build/types/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { Store } from '@tanstack/react-store';
|
|
|
14
14
|
|
|
15
15
|
interface RouterHistory {
|
|
16
16
|
location: RouterLocation;
|
|
17
|
-
|
|
17
|
+
subscribe: (cb: () => void) => () => void;
|
|
18
18
|
push: (path: string, state?: any) => void;
|
|
19
19
|
replace: (path: string, state?: any) => void;
|
|
20
20
|
go: (index: number) => void;
|
|
@@ -201,7 +201,6 @@ interface RouterOptions<TRouteTree extends AnyRoute, TDehydrated extends Record<
|
|
|
201
201
|
route: AnyRoute;
|
|
202
202
|
router: AnyRouter;
|
|
203
203
|
}) => void;
|
|
204
|
-
onRouteChange?: () => void;
|
|
205
204
|
context?: TRouteTree['types']['routerContext'];
|
|
206
205
|
Wrap?: React.ComponentType<{
|
|
207
206
|
children: React.ReactNode;
|
|
@@ -222,7 +221,7 @@ interface RouterState<TRouteTree extends AnyRoute = AnyRoute> {
|
|
|
222
221
|
resolvedLocation: ParsedLocation<FullSearchSchema<TRouteTree>>;
|
|
223
222
|
lastUpdated: number;
|
|
224
223
|
}
|
|
225
|
-
type ListenerFn = () => void;
|
|
224
|
+
type ListenerFn<TEvent extends RouterEvent> = (event: TEvent) => void;
|
|
226
225
|
interface BuildNextOptions {
|
|
227
226
|
to?: string | number | null;
|
|
228
227
|
params?: true | Updater<unknown>;
|
|
@@ -254,6 +253,25 @@ interface DehydratedRouter {
|
|
|
254
253
|
}
|
|
255
254
|
type RouterConstructorOptions<TRouteTree extends AnyRoute, TDehydrated extends Record<string, any>> = Omit<RouterOptions<TRouteTree, TDehydrated>, 'context'> & RouterContextOptions<TRouteTree>;
|
|
256
255
|
declare const componentTypes: readonly ["component", "errorComponent", "pendingComponent"];
|
|
256
|
+
type RouterEvents = {
|
|
257
|
+
onBeforeLoad: {
|
|
258
|
+
type: 'onBeforeLoad';
|
|
259
|
+
from: ParsedLocation;
|
|
260
|
+
to: ParsedLocation;
|
|
261
|
+
pathChanged: boolean;
|
|
262
|
+
};
|
|
263
|
+
onLoad: {
|
|
264
|
+
type: 'onLoad';
|
|
265
|
+
from: ParsedLocation;
|
|
266
|
+
to: ParsedLocation;
|
|
267
|
+
pathChanged: boolean;
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
type RouterEvent = RouterEvents[keyof RouterEvents];
|
|
271
|
+
type RouterListener<TRouterEvent extends RouterEvent> = {
|
|
272
|
+
eventType: TRouterEvent['type'];
|
|
273
|
+
fn: ListenerFn<TRouterEvent>;
|
|
274
|
+
};
|
|
257
275
|
declare class Router<TRouteTree extends AnyRoute = AnyRoute, TDehydrated extends Record<string, any> = Record<string, any>> {
|
|
258
276
|
#private;
|
|
259
277
|
types: {
|
|
@@ -273,6 +291,8 @@ declare class Router<TRouteTree extends AnyRoute = AnyRoute, TDehydrated extends
|
|
|
273
291
|
state: RouterState<TRouteTree>;
|
|
274
292
|
dehydratedData?: TDehydrated;
|
|
275
293
|
constructor(options: RouterConstructorOptions<TRouteTree, TDehydrated>);
|
|
294
|
+
subscribers: Set<RouterListener<RouterEvent>>;
|
|
295
|
+
subscribe: <TType extends keyof RouterEvents>(eventType: TType, fn: ListenerFn<RouterEvents[TType]>) => () => void;
|
|
276
296
|
reset: () => void;
|
|
277
297
|
mount: () => void;
|
|
278
298
|
update: (opts?: RouterOptions<any, any>) => this;
|
|
@@ -437,10 +457,6 @@ type UpdatableRouteOptions<TLoader, TSearchSchema extends AnySearchSchema, TFull
|
|
|
437
457
|
maxAge?: number;
|
|
438
458
|
gcMaxAge?: number;
|
|
439
459
|
beforeLoad?: (opts: LoaderContext<TSearchSchema, TFullSearchSchema, TAllParams, NoInfer<TRouteContext>, TContext>) => Promise<void> | void;
|
|
440
|
-
onBeforeLoadError?: (err: any) => void;
|
|
441
|
-
onValidateSearchError?: (err: any) => void;
|
|
442
|
-
onParseParamsError?: (err: any) => void;
|
|
443
|
-
onLoadError?: (err: any) => void;
|
|
444
460
|
onError?: (err: any) => void;
|
|
445
461
|
onLoaded?: (matchContext: {
|
|
446
462
|
params: TAllParams;
|
|
@@ -615,10 +631,6 @@ declare class FileRoute<TFilePath extends keyof FileRoutesByPath, TParentRoute e
|
|
|
615
631
|
maxAge?: number | undefined;
|
|
616
632
|
gcMaxAge?: number | undefined;
|
|
617
633
|
beforeLoad?: ((opts: LoaderContext<TSearchSchema, TFullSearchSchema, TAllParams, NoInfer<TRouteContext>, TContext>) => void | Promise<void>) | undefined;
|
|
618
|
-
onBeforeLoadError?: ((err: any) => void) | undefined;
|
|
619
|
-
onValidateSearchError?: ((err: any) => void) | undefined;
|
|
620
|
-
onParseParamsError?: ((err: any) => void) | undefined;
|
|
621
|
-
onLoadError?: ((err: any) => void) | undefined;
|
|
622
634
|
onError?: ((err: any) => void) | undefined;
|
|
623
635
|
onLoaded?: ((matchContext: {
|
|
624
636
|
params: TAllParams;
|
|
@@ -743,4 +755,4 @@ declare const defaultStringifySearch: (search: Record<string, any>) => string;
|
|
|
743
755
|
declare function parseSearchWith(parser: (str: string) => any): (searchStr: string) => AnySearchSchema;
|
|
744
756
|
declare function stringifySearchWith(stringify: (search: any) => string, parser?: (str: string) => any): (search: Record<string, any>) => string;
|
|
745
757
|
|
|
746
|
-
export { ActiveOptions, AllParams, AnyContext, AnyPathParams, AnyRedirect, AnyRootRoute, AnyRoute, AnyRouteMatch, AnyRouteProps, AnyRouter, AnySearchSchema, BaseRouteOptions, BuildNextOptions, CheckId, CheckIdError, CheckPath, CheckPathError, CheckRelativePath, CleanPath, ComponentFromRoute, ComponentPropsFromRoute, DeepAwaited, DefinedPathParamWarning, DehydratedRouter, DehydratedRouterState, Expand, FileRoute, FileRoutePath, FileRoutesByPath, FromLocation, FullSearchSchema, GetKeyFn, HydrationCtx, InferFullSearchSchema, IsAny, IsAnyBoolean, IsKnown, Join, Last, LinkInfo, LinkOptions, ListenerFn, LoaderContext, LoaderFn, LocationState, MatchLocation, MatchRouteOptions, MergeParamsFromParent, MergeUnion, MetaOptions, NavigateOptions, NoInfer, ParamsFallback, ParentParams, ParseParamsFn, ParseParamsObj, ParseParamsOption, ParsePathParams, ParseRoute, ParseRouteChildren, ParsedLocation, ParsedPath, PathParamError, PathParamMask, PathParamOptions, PickAsPartial, PickAsRequired, PickExclude, PickExtra, PickExtract, PickRequired, PickUnsafe, PreloadableObj, Redirect, Register, RegisterRouteComponent, RegisterRouteErrorComponent, RegisteredRouteComponent, RegisteredRouteErrorComponent, RegisteredRouter, RelativeToPathAutoComplete, RemoveUnderScores, ResolveFilePath, ResolveFullPath, ResolveFullSearchSchema, ResolveId, ResolveRelativePath, RootRoute, RootRouteId, Route, RouteById, RouteByPath, RouteConstraints, RouteContext, RouteIds, RouteLoaderFromRoute, RouteMatch, RouteMeta, RouteOptions, RoutePathOptions, RoutePathOptionsIntersection, RoutePaths, RouteProps, Router, RouterConstructorOptions, RouterContext, RouterContextOptions, RouterHistory, RouterLocation, RouterOptions, RouterState, RoutesById, RoutesByPath, SearchFilter, SearchParamError, SearchParamOptions, SearchParser, SearchSchemaValidator, SearchSchemaValidatorFn, SearchSchemaValidatorObj, SearchSerializer, Segment, Split, StreamedPromise, Timeout, ToIdOption, ToOptions, ToPathOption, Trim, TrimLeft, TrimPath, TrimPathLeft, TrimPathRight, TrimRight, UnionToIntersection, UnloaderFn, UpdatableRouteOptions, Updater, UseLoaderResult, UseLoaderResultPromise, ValueKeys, Values, cleanPath, componentTypes, createBrowserHistory, createHashHistory, createMemoryHistory, decode, defaultParseSearch, defaultStringifySearch, encode, functionalUpdate, interpolatePath, isPlainObject, isRedirect, joinPaths, last, lazyFn, matchByPath, matchPathname, parsePathname, parseSearchWith, partialDeepEqual, pick, redirect, replaceEqualDeep, resolvePath, rootRouteId, stringifySearchWith, trimPath, trimPathLeft, trimPathRight };
|
|
758
|
+
export { ActiveOptions, AllParams, AnyContext, AnyPathParams, AnyRedirect, AnyRootRoute, AnyRoute, AnyRouteMatch, AnyRouteProps, AnyRouter, AnySearchSchema, BaseRouteOptions, BuildNextOptions, CheckId, CheckIdError, CheckPath, CheckPathError, CheckRelativePath, CleanPath, ComponentFromRoute, ComponentPropsFromRoute, DeepAwaited, DefinedPathParamWarning, DehydratedRouter, DehydratedRouterState, Expand, FileRoute, FileRoutePath, FileRoutesByPath, FromLocation, FullSearchSchema, GetKeyFn, HydrationCtx, InferFullSearchSchema, IsAny, IsAnyBoolean, IsKnown, Join, Last, LinkInfo, LinkOptions, ListenerFn, LoaderContext, LoaderFn, LocationState, MatchLocation, MatchRouteOptions, MergeParamsFromParent, MergeUnion, MetaOptions, NavigateOptions, NoInfer, ParamsFallback, ParentParams, ParseParamsFn, ParseParamsObj, ParseParamsOption, ParsePathParams, ParseRoute, ParseRouteChildren, ParsedLocation, ParsedPath, PathParamError, PathParamMask, PathParamOptions, PickAsPartial, PickAsRequired, PickExclude, PickExtra, PickExtract, PickRequired, PickUnsafe, PreloadableObj, Redirect, Register, RegisterRouteComponent, RegisterRouteErrorComponent, RegisteredRouteComponent, RegisteredRouteErrorComponent, RegisteredRouter, RelativeToPathAutoComplete, RemoveUnderScores, ResolveFilePath, ResolveFullPath, ResolveFullSearchSchema, ResolveId, ResolveRelativePath, RootRoute, RootRouteId, Route, RouteById, RouteByPath, RouteConstraints, RouteContext, RouteIds, RouteLoaderFromRoute, RouteMatch, RouteMeta, RouteOptions, RoutePathOptions, RoutePathOptionsIntersection, RoutePaths, RouteProps, Router, RouterConstructorOptions, RouterContext, RouterContextOptions, RouterEvent, RouterEvents, RouterHistory, RouterListener, RouterLocation, RouterOptions, RouterState, RoutesById, RoutesByPath, SearchFilter, SearchParamError, SearchParamOptions, SearchParser, SearchSchemaValidator, SearchSchemaValidatorFn, SearchSchemaValidatorObj, SearchSerializer, Segment, Split, StreamedPromise, Timeout, ToIdOption, ToOptions, ToPathOption, Trim, TrimLeft, TrimPath, TrimPathLeft, TrimPathRight, TrimRight, UnionToIntersection, UnloaderFn, UpdatableRouteOptions, Updater, UseLoaderResult, UseLoaderResultPromise, ValueKeys, Values, cleanPath, componentTypes, createBrowserHistory, createHashHistory, createMemoryHistory, decode, defaultParseSearch, defaultStringifySearch, encode, functionalUpdate, interpolatePath, isPlainObject, isRedirect, joinPaths, last, lazyFn, matchByPath, matchPathname, parsePathname, parseSearchWith, partialDeepEqual, pick, redirect, replaceEqualDeep, resolvePath, rootRouteId, stringifySearchWith, trimPath, trimPathLeft, trimPathRight };
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
function createHistory(opts) {
|
|
63
63
|
let location = opts.getLocation();
|
|
64
64
|
let unsub = () => {};
|
|
65
|
-
let
|
|
65
|
+
let subscribers = new Set();
|
|
66
66
|
let blockers = [];
|
|
67
67
|
let queue = [];
|
|
68
68
|
const tryFlush = () => {
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
while (queue.length) {
|
|
77
77
|
queue.shift()?.();
|
|
78
78
|
}
|
|
79
|
-
if (!opts.
|
|
79
|
+
if (!opts.subscriber) {
|
|
80
80
|
onUpdate();
|
|
81
81
|
}
|
|
82
82
|
};
|
|
@@ -86,20 +86,20 @@
|
|
|
86
86
|
};
|
|
87
87
|
const onUpdate = () => {
|
|
88
88
|
location = opts.getLocation();
|
|
89
|
-
|
|
89
|
+
subscribers.forEach(subscriber => subscriber());
|
|
90
90
|
};
|
|
91
91
|
return {
|
|
92
92
|
get location() {
|
|
93
93
|
return location;
|
|
94
94
|
},
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
unsub = typeof opts.
|
|
95
|
+
subscribe: cb => {
|
|
96
|
+
if (subscribers.size === 0) {
|
|
97
|
+
unsub = typeof opts.subscriber === 'function' ? opts.subscriber(onUpdate) : () => {};
|
|
98
98
|
}
|
|
99
|
-
|
|
99
|
+
subscribers.add(cb);
|
|
100
100
|
return () => {
|
|
101
|
-
|
|
102
|
-
if (
|
|
101
|
+
subscribers.delete(cb);
|
|
102
|
+
if (subscribers.size === 0) {
|
|
103
103
|
unsub();
|
|
104
104
|
}
|
|
105
105
|
};
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
const getLocation = () => parseLocation(getHref(), history.state);
|
|
153
153
|
return createHistory({
|
|
154
154
|
getLocation,
|
|
155
|
-
|
|
155
|
+
subscriber: onUpdate => {
|
|
156
156
|
window.addEventListener(pushStateEvent, onUpdate);
|
|
157
157
|
window.addEventListener(popStateEvent, onUpdate);
|
|
158
158
|
var pushState = window.history.pushState;
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
const getLocation = () => parseLocation(entries[index], currentState);
|
|
208
208
|
return createHistory({
|
|
209
209
|
getLocation,
|
|
210
|
-
|
|
210
|
+
subscriber: false,
|
|
211
211
|
pushState: (path, state) => {
|
|
212
212
|
currentState = {
|
|
213
213
|
...state,
|
|
@@ -848,6 +848,24 @@
|
|
|
848
848
|
});
|
|
849
849
|
}
|
|
850
850
|
}
|
|
851
|
+
subscribers = new Set();
|
|
852
|
+
subscribe = (eventType, fn) => {
|
|
853
|
+
const listener = {
|
|
854
|
+
eventType,
|
|
855
|
+
fn
|
|
856
|
+
};
|
|
857
|
+
this.subscribers.add(listener);
|
|
858
|
+
return () => {
|
|
859
|
+
this.subscribers.delete(listener);
|
|
860
|
+
};
|
|
861
|
+
};
|
|
862
|
+
#emit = routerEvent => {
|
|
863
|
+
this.subscribers.forEach(listener => {
|
|
864
|
+
if (listener.eventType === routerEvent.type) {
|
|
865
|
+
listener.fn(routerEvent);
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
};
|
|
851
869
|
reset = () => {
|
|
852
870
|
this.__store.setState(s => Object.assign(s, getInitialRouterState()));
|
|
853
871
|
};
|
|
@@ -878,7 +896,7 @@
|
|
|
878
896
|
resolvedLocation: parsedLocation,
|
|
879
897
|
location: parsedLocation
|
|
880
898
|
}));
|
|
881
|
-
this.#unsubHistory = this.history.
|
|
899
|
+
this.#unsubHistory = this.history.subscribe(() => {
|
|
882
900
|
this.safeLoad({
|
|
883
901
|
next: this.#parseLocation(this.state.location)
|
|
884
902
|
});
|
|
@@ -920,6 +938,8 @@
|
|
|
920
938
|
latestLoadPromise = Promise.resolve();
|
|
921
939
|
load = async opts => {
|
|
922
940
|
const promise = new Promise(async (resolve, reject) => {
|
|
941
|
+
const prevLocation = this.state.resolvedLocation;
|
|
942
|
+
const pathDidChange = !!(opts?.next && prevLocation.href !== opts.next.href);
|
|
923
943
|
let latestPromise;
|
|
924
944
|
const checkLatest = () => {
|
|
925
945
|
return this.latestLoadPromise !== promise ? this.latestLoadPromise : undefined;
|
|
@@ -929,6 +949,12 @@
|
|
|
929
949
|
// this.cancelMatches()
|
|
930
950
|
|
|
931
951
|
let pendingMatches;
|
|
952
|
+
this.#emit({
|
|
953
|
+
type: 'onBeforeLoad',
|
|
954
|
+
from: prevLocation,
|
|
955
|
+
to: opts?.next ?? this.state.location,
|
|
956
|
+
pathChanged: pathDidChange
|
|
957
|
+
});
|
|
932
958
|
this.__store.batch(() => {
|
|
933
959
|
if (opts?.next) {
|
|
934
960
|
// Ingest the new location
|
|
@@ -963,7 +989,6 @@
|
|
|
963
989
|
if (latestPromise = checkLatest()) {
|
|
964
990
|
return latestPromise;
|
|
965
991
|
}
|
|
966
|
-
const prevLocation = this.state.resolvedLocation;
|
|
967
992
|
this.__store.setState(s => ({
|
|
968
993
|
...s,
|
|
969
994
|
status: 'idle',
|
|
@@ -971,9 +996,12 @@
|
|
|
971
996
|
matchIds: s.pendingMatchIds,
|
|
972
997
|
pendingMatchIds: []
|
|
973
998
|
}));
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
999
|
+
this.#emit({
|
|
1000
|
+
type: 'onLoad',
|
|
1001
|
+
from: prevLocation,
|
|
1002
|
+
to: this.state.location,
|
|
1003
|
+
pathChanged: pathDidChange
|
|
1004
|
+
});
|
|
977
1005
|
resolve();
|
|
978
1006
|
} catch (err) {
|
|
979
1007
|
// Only apply the latest transition
|
|
@@ -1223,14 +1251,14 @@
|
|
|
1223
1251
|
try {
|
|
1224
1252
|
for (const [index, match] of resolvedMatches.entries()) {
|
|
1225
1253
|
const route = this.getRoute(match.routeId);
|
|
1226
|
-
const handleError = (err,
|
|
1254
|
+
const handleError = (err, code) => {
|
|
1255
|
+
err.routerCode = code;
|
|
1227
1256
|
firstBadMatchIndex = firstBadMatchIndex ?? index;
|
|
1228
|
-
handler = handler || route.options.onError;
|
|
1229
1257
|
if (isRedirect(err)) {
|
|
1230
1258
|
throw err;
|
|
1231
1259
|
}
|
|
1232
1260
|
try {
|
|
1233
|
-
|
|
1261
|
+
route.options.onError?.(err);
|
|
1234
1262
|
} catch (errorHandlerErr) {
|
|
1235
1263
|
err = errorHandlerErr;
|
|
1236
1264
|
if (isRedirect(errorHandlerErr)) {
|
|
@@ -1245,10 +1273,10 @@
|
|
|
1245
1273
|
}));
|
|
1246
1274
|
};
|
|
1247
1275
|
if (match.paramsError) {
|
|
1248
|
-
handleError(match.paramsError,
|
|
1276
|
+
handleError(match.paramsError, 'PARSE_PARAMS');
|
|
1249
1277
|
}
|
|
1250
1278
|
if (match.searchError) {
|
|
1251
|
-
handleError(match.searchError,
|
|
1279
|
+
handleError(match.searchError, 'VALIDATE_SEARCH');
|
|
1252
1280
|
}
|
|
1253
1281
|
let didError = false;
|
|
1254
1282
|
try {
|
|
@@ -1257,7 +1285,7 @@
|
|
|
1257
1285
|
preload: !!opts?.preload
|
|
1258
1286
|
});
|
|
1259
1287
|
} catch (err) {
|
|
1260
|
-
handleError(err,
|
|
1288
|
+
handleError(err, 'BEFORE_LOAD');
|
|
1261
1289
|
didError = true;
|
|
1262
1290
|
}
|
|
1263
1291
|
|
|
@@ -1315,28 +1343,18 @@
|
|
|
1315
1343
|
const [_, loader] = await Promise.all([componentsPromise, loaderPromise]);
|
|
1316
1344
|
if (latestPromise = checkLatest()) return await latestPromise;
|
|
1317
1345
|
this.setRouteMatchData(match.id, () => loader, opts);
|
|
1318
|
-
} catch (
|
|
1319
|
-
let latestError = loaderError;
|
|
1346
|
+
} catch (error) {
|
|
1320
1347
|
if (latestPromise = checkLatest()) return await latestPromise;
|
|
1321
|
-
if (handleIfRedirect(
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
if (handleIfRedirect(onLoadError)) return;
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
if ((!route.options.onLoadError || latestError !== loaderError) && route.options.onError) {
|
|
1331
|
-
try {
|
|
1332
|
-
route.options.onError(latestError);
|
|
1333
|
-
} catch (onErrorError) {
|
|
1334
|
-
if (handleIfRedirect(onErrorError)) return;
|
|
1335
|
-
}
|
|
1348
|
+
if (handleIfRedirect(error)) return;
|
|
1349
|
+
try {
|
|
1350
|
+
route.options.onError?.(error);
|
|
1351
|
+
} catch (onErrorError) {
|
|
1352
|
+
error = onErrorError;
|
|
1353
|
+
if (handleIfRedirect(onErrorError)) return;
|
|
1336
1354
|
}
|
|
1337
1355
|
this.setRouteMatch(match.id, s => ({
|
|
1338
1356
|
...s,
|
|
1339
|
-
error
|
|
1357
|
+
error,
|
|
1340
1358
|
status: 'error',
|
|
1341
1359
|
isFetching: false,
|
|
1342
1360
|
updatedAt: Date.now()
|