@tanstack/react-router 0.0.1-beta.245 → 0.0.1-beta.247

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.
@@ -29,7 +29,6 @@ export declare class FileRoute<TFilePath extends keyof FileRoutesByPath, TParent
29
29
  onEnter?: ((match: import("./Matches").AnyRouteMatch) => void) | undefined;
30
30
  onTransition?: ((match: import("./Matches").AnyRouteMatch) => void) | undefined;
31
31
  onLeave?: ((match: import("./Matches").AnyRouteMatch) => void) | undefined;
32
- reloadOnWindowFocus?: boolean | undefined;
33
32
  }) | undefined) => Route<TParentRoute, TPath, TFullPath, TFilePath, TId, TSearchSchema, TFullSearchSchema, TParams, TAllParams, TRouteContext, TContext, TRouterContext, TLoaderData, TChildren, TRouteTree>;
34
33
  }
35
34
  export {};
@@ -13,4 +13,5 @@ export declare function resolvePath(basepath: string, base: string, to: string):
13
13
  export declare function parsePathname(pathname?: string): Segment[];
14
14
  export declare function interpolatePath(path: string | undefined, params: any, leaveWildcards?: boolean): string;
15
15
  export declare function matchPathname(basepath: string, currentPathname: string, matchLocation: Pick<MatchLocation, 'to' | 'fuzzy' | 'caseSensitive'>): AnyPathParams | undefined;
16
+ export declare function removeBasepath(basepath: string, pathname: string): string;
16
17
  export declare function matchByPath(basepath: string, from: string, matchLocation: Pick<MatchLocation, 'to' | 'caseSensitive' | 'fuzzy'>): Record<string, string> | undefined;
@@ -77,7 +77,6 @@ export type UpdatableRouteOptions<TFullSearchSchema extends Record<string, any>,
77
77
  onEnter?: (match: AnyRouteMatch) => void;
78
78
  onTransition?: (match: AnyRouteMatch) => void;
79
79
  onLeave?: (match: AnyRouteMatch) => void;
80
- reloadOnWindowFocus?: boolean;
81
80
  };
82
81
  export type ParseParamsOption<TPath extends string, TParams> = ParseParamsFn<TPath, TParams>;
83
82
  export type ParseParamsFn<TPath extends string, TParams> = (rawParams: IsAny<TPath, any, Record<ParsePathParams<TPath>, string>>) => TParams extends Record<ParsePathParams<TPath>, any> ? TParams : 'parseParams must return an object';
@@ -179,7 +178,6 @@ export declare class Route<TParentRoute extends RouteConstraints['TParentRoute']
179
178
  }) => void;
180
179
  addChildren: <TNewChildren extends AnyRoute[]>(children: TNewChildren) => Route<TParentRoute, TPath, TFullPath, TCustomId, TId, TSearchSchema, TFullSearchSchema, TParams, TAllParams, TRouteContext, TAllContext, TRouterContext, TNewChildren, TRouteTree, AnyRoute>;
181
180
  update: (options: UpdatableRouteOptions<TFullSearchSchema, TAllParams, Expand<Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>>, TLoaderData>) => this;
182
- static __onInit: (route: any) => void;
183
181
  useMatch: <TSelected = TAllContext>(opts?: {
184
182
  select?: ((search: TAllContext) => TSelected) | undefined;
185
183
  } | undefined) => TSelected;
@@ -275,4 +273,7 @@ export type ErrorRouteComponent<TFullSearchSchema extends Record<string, any>, T
275
273
  export type PendingRouteComponent<TFullSearchSchema extends Record<string, any>, TAllParams extends AnyPathParams, TAllContext extends Record<string, any>> = AsyncRouteComponent<PendingRouteProps<TFullSearchSchema, TAllParams, TAllContext>>;
276
274
  export type AnyRouteComponent = RouteComponent<any, any, any, any>;
277
275
  export type ComponentPropsFromRoute<TRoute> = TRoute extends (() => infer T extends AnyRoute) ? ComponentPropsFromRoute<T> : TRoute extends Route<infer TParentRoute, infer TPath, infer TFullPath, infer TCustomId, infer TId, infer TSearchSchema, infer TFullSearchSchema, infer TParams, infer TAllParams, infer TRouteContext, infer TAllContext, infer TRouterContext, infer TLoaderData, infer TChildren> ? RouteProps<TFullSearchSchema, TAllParams, TAllContext, TLoaderData> : {};
276
+ export declare class NotFoundRoute<TParentRoute extends AnyRootRoute, TSearchSchema extends RouteConstraints['TSearchSchema'] = {}, TFullSearchSchema extends RouteConstraints['TFullSearchSchema'] = ResolveFullSearchSchema<TParentRoute, TSearchSchema>, TRouteContext extends RouteConstraints['TRouteContext'] = RouteContext, TAllContext extends Expand<Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>> = Expand<Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>>, TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext, TLoaderData extends any = unknown, TChildren extends RouteConstraints['TChildren'] = unknown, TRouteTree extends RouteConstraints['TRouteTree'] = AnyRoute> extends Route<TParentRoute, '/404', '/404', '404', '404', TSearchSchema, TFullSearchSchema, {}, {}, TRouteContext, TAllContext, TRouterContext, TLoaderData, TChildren, TRouteTree> {
277
+ constructor(options: Omit<RouteOptions<TParentRoute, string, string, TSearchSchema, TFullSearchSchema, {}, {}, TRouteContext, TAllContext, TLoaderData>, 'caseSensitive' | 'parseParams' | 'stringifyParams' | 'path' | 'id'>);
278
+ }
278
279
  export {};
@@ -54,6 +54,7 @@ export interface RouterOptions<TRouteTree extends AnyRoute, TDehydrated extends
54
54
  Wrap?: (props: {
55
55
  children: any;
56
56
  }) => JSX.Element;
57
+ notFoundRoute?: AnyRoute;
57
58
  }
58
59
  export interface RouterState<TRouteTree extends AnyRoute = AnyRoute> {
59
60
  status: 'pending' | 'idle';
@@ -165,7 +166,7 @@ export declare class Router<TRouteTree extends AnyRoute = AnyRoute, TDehydrated
165
166
  load: (opts?: {
166
167
  invalidate?: boolean;
167
168
  }) => Promise<void>;
168
- preloadRoute: (navigateOpts?: BuildNextOptions) => Promise<readonly [RouteMatch<AnyRoute, any>, RouteMatch<AnyRoute, any>[]]>;
169
+ preloadRoute: (navigateOpts?: BuildNextOptions) => Promise<RouteMatch<AnyRoute, any>[]>;
169
170
  buildLink: BuildLinkFn<TRouteTree>;
170
171
  matchRoute: MatchRouteFn<TRouteTree>;
171
172
  injectHtml: (html: string | (() => Promise<string> | string)) => Promise<void>;
@@ -64,26 +64,20 @@
64
64
  let location = opts.getLocation();
65
65
  let subscribers = new Set();
66
66
  let blockers = [];
67
- let queue = [];
68
67
  const onUpdate = () => {
69
68
  location = opts.getLocation();
70
69
  subscribers.forEach(subscriber => subscriber());
71
70
  };
72
- const tryUnblock = () => {
71
+ const tryNavigation = task => {
73
72
  if (blockers.length) {
74
- blockers[0]?.(tryUnblock, () => {
75
- blockers = [];
76
- stopBlocking();
77
- });
78
- return;
79
- }
80
- while (queue.length) {
81
- queue.shift()?.();
73
+ for (let blocker of blockers) {
74
+ if (!window.confirm(blocker.message)) {
75
+ opts.onBlocked?.(onUpdate);
76
+ return;
77
+ }
78
+ }
82
79
  }
83
- };
84
- const queueTask = task => {
85
- queue.push(task);
86
- tryUnblock();
80
+ task();
87
81
  };
88
82
  return {
89
83
  get location() {
@@ -97,41 +91,44 @@
97
91
  },
98
92
  push: (path, state) => {
99
93
  state = assignKey(state);
100
- queueTask(() => {
94
+ tryNavigation(() => {
101
95
  opts.pushState(path, state, onUpdate);
102
96
  });
103
97
  },
104
98
  replace: (path, state) => {
105
99
  state = assignKey(state);
106
- queueTask(() => {
100
+ tryNavigation(() => {
107
101
  opts.replaceState(path, state, onUpdate);
108
102
  });
109
103
  },
110
104
  go: index => {
111
- queueTask(() => {
105
+ tryNavigation(() => {
112
106
  opts.go(index);
113
107
  });
114
108
  },
115
109
  back: () => {
116
- queueTask(() => {
110
+ tryNavigation(() => {
117
111
  opts.back();
118
112
  });
119
113
  },
120
114
  forward: () => {
121
- queueTask(() => {
115
+ tryNavigation(() => {
122
116
  opts.forward();
123
117
  });
124
118
  },
125
119
  createHref: str => opts.createHref(str),
126
- block: cb => {
127
- blockers.push(cb);
120
+ block: message => {
121
+ const payload = {
122
+ message
123
+ };
124
+ blockers.push(payload);
128
125
  if (blockers.length === 1) {
129
126
  addEventListener(beforeUnloadEvent, beforeUnloadListener, {
130
127
  capture: true
131
128
  });
132
129
  }
133
130
  return () => {
134
- blockers = blockers.filter(b => b !== cb);
131
+ blockers = blockers.filter(b => b !== payload);
135
132
  if (!blockers.length) {
136
133
  stopBlocking();
137
134
  }
@@ -172,6 +169,7 @@
172
169
  const getHref = opts?.getHref ?? (() => `${window.location.pathname}${window.location.search}${window.location.hash}`);
173
170
  const createHref = opts?.createHref ?? (path => path);
174
171
  let currentLocation = parseLocation(getHref(), window.history.state);
172
+ let rollbackLocation;
175
173
  const getLocation = () => currentLocation;
176
174
  let next;
177
175
 
@@ -202,12 +200,16 @@
202
200
  // Reset the nextIsPush flag and clear the scheduled update
203
201
  next = undefined;
204
202
  scheduled = undefined;
203
+ rollbackLocation = undefined;
205
204
  });
206
205
  };
207
206
 
208
207
  // This function queues up a call to update the browser history
209
208
  const queueHistoryAction = (type, path, state, onUpdate) => {
210
209
  const href = createHref(path);
210
+ if (!scheduled) {
211
+ rollbackLocation = currentLocation;
212
+ }
211
213
 
212
214
  // Update the location in memory
213
215
  currentLocation = parseLocation(href, state);
@@ -218,6 +220,7 @@
218
220
  state,
219
221
  isPush: next?.isPush || type === 'push'
220
222
  };
223
+
221
224
  // Notify subscribers
222
225
  onUpdate();
223
226
  if (!scheduled) {
@@ -245,6 +248,15 @@
245
248
  window.history.replaceState = originalReplaceState;
246
249
  window.removeEventListener(pushStateEvent, onPushPop);
247
250
  window.removeEventListener(popStateEvent, onPushPop);
251
+ },
252
+ onBlocked: onUpdate => {
253
+ // If a navigation is blocked, we need to rollback the location
254
+ // that we optimistically updated in memory.
255
+ if (rollbackLocation && currentLocation !== rollbackLocation) {
256
+ currentLocation = rollbackLocation;
257
+ // Notify subscribers
258
+ onUpdate();
259
+ }
248
260
  }
249
261
  });
250
262
  window.addEventListener(pushStateEvent, onPushPop);
@@ -1005,9 +1017,12 @@
1005
1017
  }
1006
1018
  return pathParams ?? {};
1007
1019
  }
1020
+ function removeBasepath(basepath, pathname) {
1021
+ return basepath != '/' ? pathname.substring(basepath.length) : pathname;
1022
+ }
1008
1023
  function matchByPath(basepath, from, matchLocation) {
1009
1024
  // Remove the base path from the pathname
1010
- from = basepath != '/' ? from.substring(basepath.length) : from;
1025
+ from = removeBasepath(basepath, from);
1011
1026
  // Default to to $ (wildcard)
1012
1027
  const to = `${matchLocation.to ?? '$'}`;
1013
1028
  // Parse the from and to
@@ -1067,7 +1082,8 @@
1067
1082
  }
1068
1083
  }
1069
1084
  if (!isLastBaseSegment && isLastRouteSegment) {
1070
- return !!matchLocation.fuzzy;
1085
+ params['**'] = joinPaths(baseSegments.slice(i + 1).map(d => d.value));
1086
+ return !!matchLocation.fuzzy && routeSegment?.value !== '/';
1071
1087
  }
1072
1088
  }
1073
1089
  return true;
@@ -1107,7 +1123,6 @@
1107
1123
  constructor(options) {
1108
1124
  this.options = options || {};
1109
1125
  this.isRoot = !options?.getParentRoute;
1110
- Route.__onInit(this);
1111
1126
  this.$$typeof = Symbol.for('react.memo');
1112
1127
  }
1113
1128
  init = opts => {
@@ -1151,10 +1166,6 @@
1151
1166
  Object.assign(this.options, options);
1152
1167
  return this;
1153
1168
  };
1154
- static __onInit = route => {
1155
- // This is a dummy static method that should get
1156
- // replaced by a framework specific implementation if necessary
1157
- };
1158
1169
  useMatch = opts => {
1159
1170
  return useMatch({
1160
1171
  ...opts,
@@ -1203,6 +1214,15 @@
1203
1214
 
1204
1215
  //
1205
1216
 
1217
+ class NotFoundRoute extends Route {
1218
+ constructor(options) {
1219
+ super({
1220
+ ...options,
1221
+ id: '404'
1222
+ });
1223
+ }
1224
+ }
1225
+
1206
1226
  const matchContext = /*#__PURE__*/React__namespace.createContext(undefined);
1207
1227
  function Matches() {
1208
1228
  const router = useRouter();
@@ -1900,11 +1920,15 @@
1900
1920
  buildRouteTree = () => {
1901
1921
  this.routesById = {};
1902
1922
  this.routesByPath = {};
1923
+ const notFoundRoute = this.options.notFoundRoute;
1924
+ if (notFoundRoute) {
1925
+ notFoundRoute.init({
1926
+ originalIndex: 99999999999
1927
+ });
1928
+ this.routesById[notFoundRoute.id] = notFoundRoute;
1929
+ }
1903
1930
  const recurseRoutes = childRoutes => {
1904
1931
  childRoutes.forEach((childRoute, i) => {
1905
- // if (typeof childRoute === 'function') {
1906
- // childRoute = (childRoute as any)()
1907
- // }
1908
1932
  childRoute.init({
1909
1933
  originalIndex: i
1910
1934
  });
@@ -1924,8 +1948,12 @@
1924
1948
  });
1925
1949
  };
1926
1950
  recurseRoutes([this.routeTree]);
1927
- this.flatRoutes = Object.values(this.routesByPath).map((d, i) => {
1928
- const trimmed = trimPath(d.fullPath);
1951
+ const scoredRoutes = [];
1952
+ Object.values(this.routesById).forEach((d, i) => {
1953
+ if (d.isRoot || !d.path) {
1954
+ return;
1955
+ }
1956
+ const trimmed = trimPathLeft(d.fullPath);
1929
1957
  const parsed = parsePathname(trimmed);
1930
1958
  while (parsed.length > 1 && parsed[0]?.value === '/') {
1931
1959
  parsed.shift();
@@ -1939,14 +1967,15 @@
1939
1967
  }
1940
1968
  return 1;
1941
1969
  });
1942
- return {
1970
+ scoredRoutes.push({
1943
1971
  child: d,
1944
1972
  trimmed,
1945
1973
  parsed,
1946
1974
  index: i,
1947
1975
  score
1948
- };
1949
- }).sort((a, b) => {
1976
+ });
1977
+ });
1978
+ this.flatRoutes = scoredRoutes.sort((a, b) => {
1950
1979
  let isIndex = a.trimmed === '/' ? 1 : b.trimmed === '/' ? -1 : 0;
1951
1980
  if (isIndex !== 0) return isIndex;
1952
1981
  const length = Math.min(a.score.length, b.score.length);
@@ -2048,7 +2077,7 @@
2048
2077
  const matchedParams = matchPathname(this.basepath, trimPathRight(pathname), {
2049
2078
  to: route.fullPath,
2050
2079
  caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive,
2051
- fuzzy: false
2080
+ fuzzy: true
2052
2081
  });
2053
2082
  if (matchedParams) {
2054
2083
  routeParams = matchedParams;
@@ -2058,7 +2087,17 @@
2058
2087
  });
2059
2088
  let routeCursor = foundRoute || this.routesById['__root__'];
2060
2089
  let matchedRoutes = [routeCursor];
2061
- // let includingLayouts = true
2090
+
2091
+ // Check to see if the route needs a 404 entry
2092
+ if (
2093
+ // If we found a route, and it's not an index route and we have left over path
2094
+ (foundRoute ? foundRoute.path !== '/' && routeParams['**'] :
2095
+ // Or if we didn't find a route and we have left over path
2096
+ trimPathRight(pathname)) &&
2097
+ // And we have a 404 route configured
2098
+ this.options.notFoundRoute) {
2099
+ matchedRoutes.push(this.options.notFoundRoute);
2100
+ }
2062
2101
  while (routeCursor?.parentRoute) {
2063
2102
  routeCursor = routeCursor.parentRoute;
2064
2103
  if (routeCursor) matchedRoutes.unshift(routeCursor);
@@ -2682,12 +2721,12 @@
2682
2721
  let matches = this.matchRoutes(next.pathname, next.search, {
2683
2722
  throwOnError: true
2684
2723
  });
2685
- await this.loadMatches({
2724
+ matches = await this.loadMatches({
2686
2725
  matches,
2687
2726
  preload: true,
2688
2727
  checkLatest: () => undefined
2689
2728
  });
2690
- return [last(matches), matches];
2729
+ return matches;
2691
2730
  };
2692
2731
  buildLink = dest => {
2693
2732
  // If this link simply reloads the current route,
@@ -3089,13 +3128,7 @@
3089
3128
  } = useRouter();
3090
3129
  React__namespace.useEffect(() => {
3091
3130
  if (!condition) return;
3092
- let unblock = history.block((retry, cancel) => {
3093
- if (window.confirm(message)) {
3094
- unblock();
3095
- retry();
3096
- }
3097
- });
3098
- return unblock;
3131
+ return history.block(message);
3099
3132
  });
3100
3133
  }
3101
3134
  function Block({
@@ -3154,6 +3187,7 @@
3154
3187
  exports.MatchRoute = MatchRoute;
3155
3188
  exports.Matches = Matches;
3156
3189
  exports.Navigate = Navigate;
3190
+ exports.NotFoundRoute = NotFoundRoute;
3157
3191
  exports.Outlet = Outlet;
3158
3192
  exports.PathParamError = PathParamError;
3159
3193
  exports.RootRoute = RootRoute;
@@ -3195,6 +3229,7 @@
3195
3229
  exports.parseSearchWith = parseSearchWith;
3196
3230
  exports.pick = pick;
3197
3231
  exports.redirect = redirect;
3232
+ exports.removeBasepath = removeBasepath;
3198
3233
  exports.replaceEqualDeep = replaceEqualDeep;
3199
3234
  exports.resolvePath = resolvePath;
3200
3235
  exports.rootRouteId = rootRouteId;