@tanstack/router-core 0.0.1-beta.195 → 0.0.1-beta.196

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.
@@ -9,2326 +9,2334 @@
9
9
  * @license MIT
10
10
  */
11
11
  (function (global, factory) {
12
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
13
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
14
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.RouterCore = {}));
12
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
13
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
14
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.RouterCore = {}));
15
15
  })(this, (function (exports) { 'use strict';
16
16
 
17
- var prefix = 'Invariant failed';
18
- function invariant(condition, message) {
19
- if (condition) {
20
- return;
21
- }
22
- var provided = typeof message === 'function' ? message() : message;
23
- var value = provided ? "".concat(prefix, ": ").concat(provided) : prefix;
24
- throw new Error(value);
25
- }
26
-
27
- function warning(condition, message) {
28
- {
29
- if (condition) {
30
- return;
31
- }
32
-
33
- var text = "Warning: " + message;
34
-
35
- if (typeof console !== 'undefined') {
36
- console.warn(text);
37
- }
38
-
39
- try {
40
- throw Error(text);
41
- } catch (x) {}
17
+ /**
18
+ * @tanstack/history/src/index.ts
19
+ *
20
+ * Copyright (c) TanStack
21
+ *
22
+ * This source code is licensed under the MIT license found in the
23
+ * LICENSE.md file in the root directory of this source tree.
24
+ *
25
+ * @license MIT
26
+ */
27
+ // While the public API was clearly inspired by the "history" npm package,
28
+ // This implementation attempts to be more lightweight by
29
+ // making assumptions about the way TanStack Router works
30
+
31
+ const pushStateEvent = 'pushstate';
32
+ const popStateEvent = 'popstate';
33
+ const beforeUnloadEvent = 'beforeunload';
34
+ const beforeUnloadListener = event => {
35
+ event.preventDefault();
36
+ // @ts-ignore
37
+ return event.returnValue = '';
38
+ };
39
+ const stopBlocking = () => {
40
+ removeEventListener(beforeUnloadEvent, beforeUnloadListener, {
41
+ capture: true
42
+ });
43
+ };
44
+ function createHistory(opts) {
45
+ let location = opts.getLocation();
46
+ let unsub = () => {};
47
+ let subscribers = new Set();
48
+ let blockers = [];
49
+ let queue = [];
50
+ const tryFlush = () => {
51
+ if (blockers.length) {
52
+ blockers[0]?.(tryFlush, () => {
53
+ blockers = [];
54
+ stopBlocking();
55
+ });
56
+ return;
57
+ }
58
+ while (queue.length) {
59
+ queue.shift()?.();
60
+ }
61
+ if (!opts.subscriber) {
62
+ onUpdate();
42
63
  }
43
- }
44
-
45
- // While the public API was clearly inspired by the "history" npm package,
46
- // This implementation attempts to be more lightweight by
47
- // making assumptions about the way TanStack Router works
48
-
49
- const pushStateEvent = 'pushstate';
50
- const popStateEvent = 'popstate';
51
- const beforeUnloadEvent = 'beforeunload';
52
- const beforeUnloadListener = event => {
53
- event.preventDefault();
54
- // @ts-ignore
55
- return event.returnValue = '';
56
64
  };
57
- const stopBlocking = () => {
58
- removeEventListener(beforeUnloadEvent, beforeUnloadListener, {
59
- capture: true
60
- });
65
+ const queueTask = task => {
66
+ queue.push(task);
67
+ tryFlush();
61
68
  };
62
- function createHistory(opts) {
63
- let location = opts.getLocation();
64
- let unsub = () => {};
65
- let subscribers = new Set();
66
- let blockers = [];
67
- let queue = [];
68
- const tryFlush = () => {
69
- if (blockers.length) {
70
- blockers[0]?.(tryFlush, () => {
71
- blockers = [];
72
- stopBlocking();
73
- });
74
- return;
75
- }
76
- while (queue.length) {
77
- queue.shift()?.();
78
- }
79
- if (!opts.subscriber) {
80
- onUpdate();
69
+ const onUpdate = () => {
70
+ location = opts.getLocation();
71
+ subscribers.forEach(subscriber => subscriber());
72
+ };
73
+ return {
74
+ get location() {
75
+ return location;
76
+ },
77
+ subscribe: cb => {
78
+ if (subscribers.size === 0) {
79
+ unsub = typeof opts.subscriber === 'function' ? opts.subscriber(onUpdate) : () => {};
81
80
  }
82
- };
83
- const queueTask = task => {
84
- queue.push(task);
85
- tryFlush();
86
- };
87
- const onUpdate = () => {
88
- location = opts.getLocation();
89
- subscribers.forEach(subscriber => subscriber());
90
- };
91
- return {
92
- get location() {
93
- return location;
94
- },
95
- subscribe: cb => {
81
+ subscribers.add(cb);
82
+ return () => {
83
+ subscribers.delete(cb);
96
84
  if (subscribers.size === 0) {
97
- unsub = typeof opts.subscriber === 'function' ? opts.subscriber(onUpdate) : () => {};
85
+ unsub();
98
86
  }
99
- subscribers.add(cb);
100
- return () => {
101
- subscribers.delete(cb);
102
- if (subscribers.size === 0) {
103
- unsub();
104
- }
105
- };
106
- },
107
- push: (path, state) => {
108
- assignKey(state);
109
- queueTask(() => {
110
- opts.pushState(path, state, onUpdate);
111
- });
112
- },
113
- replace: (path, state) => {
114
- assignKey(state);
115
- queueTask(() => {
116
- opts.replaceState(path, state, onUpdate);
117
- });
118
- },
119
- go: index => {
120
- queueTask(() => {
121
- opts.go(index);
122
- });
123
- },
124
- back: () => {
125
- queueTask(() => {
126
- opts.back();
127
- });
128
- },
129
- forward: () => {
130
- queueTask(() => {
131
- opts.forward();
87
+ };
88
+ },
89
+ push: (path, state) => {
90
+ state = assignKey(state);
91
+ queueTask(() => {
92
+ opts.pushState(path, state, onUpdate);
93
+ });
94
+ },
95
+ replace: (path, state) => {
96
+ state = assignKey(state);
97
+ queueTask(() => {
98
+ opts.replaceState(path, state, onUpdate);
99
+ });
100
+ },
101
+ go: index => {
102
+ queueTask(() => {
103
+ opts.go(index);
104
+ });
105
+ },
106
+ back: () => {
107
+ queueTask(() => {
108
+ opts.back();
109
+ });
110
+ },
111
+ forward: () => {
112
+ queueTask(() => {
113
+ opts.forward();
114
+ });
115
+ },
116
+ createHref: str => opts.createHref(str),
117
+ block: cb => {
118
+ blockers.push(cb);
119
+ if (blockers.length === 1) {
120
+ addEventListener(beforeUnloadEvent, beforeUnloadListener, {
121
+ capture: true
132
122
  });
133
- },
134
- createHref: str => opts.createHref(str),
135
- block: cb => {
136
- blockers.push(cb);
137
- if (blockers.length === 1) {
138
- addEventListener(beforeUnloadEvent, beforeUnloadListener, {
139
- capture: true
140
- });
123
+ }
124
+ return () => {
125
+ blockers = blockers.filter(b => b !== cb);
126
+ if (!blockers.length) {
127
+ stopBlocking();
141
128
  }
142
- return () => {
143
- blockers = blockers.filter(b => b !== cb);
144
- if (!blockers.length) {
145
- stopBlocking();
146
- }
147
- };
148
- },
149
- flush: () => opts.flush?.()
150
- };
151
- }
152
- function assignKey(state) {
153
- state.key = createRandomKey();
154
- // if (state.__actualLocation) {
155
- // state.__actualLocation.state = {
156
- // ...state.__actualLocation.state,
157
- // key,
158
- // }
159
- // }
129
+ };
130
+ },
131
+ flush: () => opts.flush?.()
132
+ };
133
+ }
134
+ function assignKey(state) {
135
+ if (!state) {
136
+ state = {};
160
137
  }
138
+ state.key = createRandomKey();
139
+ return state;
140
+ }
141
+
142
+ /**
143
+ * Creates a history object that can be used to interact with the browser's
144
+ * navigation. This is a lightweight API wrapping the browser's native methods.
145
+ * It is designed to work with TanStack Router, but could be used as a standalone API as well.
146
+ * IMPORTANT: This API implements history throttling via a microtask to prevent
147
+ * excessive calls to the history API. In some browsers, calling history.pushState or
148
+ * history.replaceState in quick succession can cause the browser to ignore subsequent
149
+ * calls. This API smooths out those differences and ensures that your application
150
+ * state will *eventually* match the browser state. In most cases, this is not a problem,
151
+ * but if you need to ensure that the browser state is up to date, you can use the
152
+ * `history.flush` method to immediately flush all pending state changes to the browser URL.
153
+ * @param opts
154
+ * @param opts.getHref A function that returns the current href (path + search + hash)
155
+ * @param opts.createHref A function that takes a path and returns a href (path + search + hash)
156
+ * @returns A history instance
157
+ */
158
+ function createBrowserHistory(opts) {
159
+ const getHref = opts?.getHref ?? (() => `${window.location.pathname}${window.location.search}${window.location.hash}`);
160
+ const createHref = opts?.createHref ?? (path => path);
161
+ let currentLocation = parseLocation(getHref(), window.history.state);
162
+ const getLocation = () => currentLocation;
163
+ let next;
164
+
165
+ // Because we are proactively updating the location
166
+ // in memory before actually updating the browser history,
167
+ // we need to track when we are doing this so we don't
168
+ // notify subscribers twice on the last update.
169
+ let tracking = true;
170
+
171
+ // We need to track the current scheduled update to prevent
172
+ // multiple updates from being scheduled at the same time.
173
+ let scheduled;
174
+
175
+ // This function is a wrapper to prevent any of the callback's
176
+ // side effects from causing a subscriber notification
177
+ const untrack = fn => {
178
+ tracking = false;
179
+ fn();
180
+ tracking = true;
181
+ };
161
182
 
162
- /**
163
- * Creates a history object that can be used to interact with the browser's
164
- * navigation. This is a lightweight API wrapping the browser's native methods.
165
- * It is designed to work with TanStack Router, but could be used as a standalone API as well.
166
- * IMPORTANT: This API implements history throttling via a microtask to prevent
167
- * excessive calls to the history API. In some browsers, calling history.pushState or
168
- * history.replaceState in quick succession can cause the browser to ignore subsequent
169
- * calls. This API smooths out those differences and ensures that your application
170
- * state will *eventually* match the browser state. In most cases, this is not a problem,
171
- * but if you need to ensure that the browser state is up to date, you can use the
172
- * `history.flush` method to immediately flush all pending state changes to the browser URL.
173
- * @param opts
174
- * @param opts.getHref A function that returns the current href (path + search + hash)
175
- * @param opts.createHref A function that takes a path and returns a href (path + search + hash)
176
- * @returns A history instance
177
- */
178
- function createBrowserHistory(opts) {
179
- const getHref = opts?.getHref ?? (() => `${window.location.pathname}${window.location.search}${window.location.hash}`);
180
- const createHref = opts?.createHref ?? (path => path);
181
- let currentLocation = parseLocation(getHref(), window.history.state);
182
- const getLocation = () => currentLocation;
183
- let next;
184
-
185
- // Because we are proactively updating the location
186
- // in memory before actually updating the browser history,
187
- // we need to track when we are doing this so we don't
188
- // notify subscribers twice on the last update.
189
- let tracking = true;
190
-
191
- // We need to track the current scheduled update to prevent
192
- // multiple updates from being scheduled at the same time.
193
- let scheduled;
194
-
195
- // This function is a wrapper to prevent any of the callback's
196
- // side effects from causing a subscriber notification
197
- const untrack = fn => {
198
- tracking = false;
199
- fn();
200
- tracking = true;
201
- };
202
-
203
- // This function flushes the next update to the browser history
204
- const flush = () => {
205
- // Do not notify subscribers about this push/replace call
206
- untrack(() => {
207
- if (!next) return;
208
- window.history[next.isPush ? 'pushState' : 'replaceState'](next.state, '', next.href);
209
- // Reset the nextIsPush flag and clear the scheduled update
210
- next = undefined;
211
- scheduled = undefined;
212
- });
213
- };
183
+ // This function flushes the next update to the browser history
184
+ const flush = () => {
185
+ // Do not notify subscribers about this push/replace call
186
+ untrack(() => {
187
+ if (!next) return;
188
+ window.history[next.isPush ? 'pushState' : 'replaceState'](next.state, '', next.href);
189
+ // Reset the nextIsPush flag and clear the scheduled update
190
+ next = undefined;
191
+ scheduled = undefined;
192
+ });
193
+ };
214
194
 
215
- // This function queues up a call to update the browser history
216
- const queueHistoryAction = (type, path, state, onUpdate) => {
217
- const href = createHref(path);
195
+ // This function queues up a call to update the browser history
196
+ const queueHistoryAction = (type, path, state, onUpdate) => {
197
+ const href = createHref(path);
218
198
 
219
- // Update the location in memory
220
- currentLocation = parseLocation(href, state);
199
+ // Update the location in memory
200
+ currentLocation = parseLocation(href, state);
221
201
 
222
- // Keep track of the next location we need to flush to the URL
223
- next = {
224
- href,
225
- state,
226
- isPush: next?.isPush || type === 'push'
227
- };
228
- // Notify subscribers
229
- onUpdate();
230
- if (!scheduled) {
231
- // Schedule an update to the browser history
232
- scheduled = Promise.resolve().then(() => flush());
233
- }
234
- };
235
- return createHistory({
236
- getLocation,
237
- subscriber: onUpdate => {
238
- window.addEventListener(pushStateEvent, () => {
239
- currentLocation = parseLocation(getHref(), window.history.state);
240
- onUpdate();
241
- });
242
- window.addEventListener(popStateEvent, () => {
243
- currentLocation = parseLocation(getHref(), window.history.state);
244
- onUpdate();
245
- });
246
- var pushState = window.history.pushState;
247
- window.history.pushState = function () {
248
- let res = pushState.apply(history, arguments);
249
- if (tracking) onUpdate();
250
- return res;
251
- };
252
- var replaceState = window.history.replaceState;
253
- window.history.replaceState = function () {
254
- let res = replaceState.apply(history, arguments);
255
- if (tracking) onUpdate();
256
- return res;
257
- };
258
- return () => {
259
- window.history.pushState = pushState;
260
- window.history.replaceState = replaceState;
261
- window.removeEventListener(pushStateEvent, onUpdate);
262
- window.removeEventListener(popStateEvent, onUpdate);
263
- };
264
- },
265
- pushState: (path, state, onUpdate) => queueHistoryAction('push', path, state, onUpdate),
266
- replaceState: (path, state, onUpdate) => queueHistoryAction('replace', path, state, onUpdate),
267
- back: () => window.history.back(),
268
- forward: () => window.history.forward(),
269
- go: n => window.history.go(n),
270
- createHref: path => createHref(path),
271
- flush
272
- });
273
- }
274
- function createHashHistory() {
275
- return createBrowserHistory({
276
- getHref: () => window.location.hash.substring(1),
277
- createHref: path => `#${path}`
278
- });
279
- }
280
- function createMemoryHistory(opts = {
281
- initialEntries: ['/']
282
- }) {
283
- const entries = opts.initialEntries;
284
- let index = opts.initialIndex ?? entries.length - 1;
285
- let currentState = {
286
- key: createRandomKey()
287
- };
288
- const getLocation = () => parseLocation(entries[index], currentState);
289
- return createHistory({
290
- getLocation,
291
- subscriber: false,
292
- pushState: (path, state) => {
293
- currentState = state;
294
- entries.push(path);
295
- index++;
296
- },
297
- replaceState: (path, state) => {
298
- currentState = state;
299
- entries[index] = path;
300
- },
301
- back: () => {
302
- index--;
303
- },
304
- forward: () => {
305
- index = Math.min(index + 1, entries.length - 1);
306
- },
307
- go: n => window.history.go(n),
308
- createHref: path => path
309
- });
310
- }
311
- function parseLocation(href, state) {
312
- let hashIndex = href.indexOf('#');
313
- let searchIndex = href.indexOf('?');
314
- return {
202
+ // Keep track of the next location we need to flush to the URL
203
+ next = {
315
204
  href,
316
- pathname: href.substring(0, hashIndex > 0 ? searchIndex > 0 ? Math.min(hashIndex, searchIndex) : hashIndex : searchIndex > 0 ? searchIndex : href.length),
317
- hash: hashIndex > -1 ? href.substring(hashIndex) : '',
318
- search: searchIndex > -1 ? href.slice(searchIndex, hashIndex === -1 ? undefined : hashIndex) : '',
319
- state: state || {}
205
+ state,
206
+ isPush: next?.isPush || type === 'push'
320
207
  };
321
- }
322
-
323
- // Thanks co-pilot!
324
- function createRandomKey() {
325
- return (Math.random() + 1).toString(36).substring(7);
326
- }
327
-
328
- // export type Expand<T> = T
329
-
330
- // type Compute<T> = { [K in keyof T]: T[K] } | never
331
-
332
- // type AllKeys<T> = T extends any ? keyof T : never
333
-
334
- // export type MergeUnion<T, Keys extends keyof T = keyof T> = Compute<
335
- // {
336
- // [K in Keys]: T[Keys]
337
- // } & {
338
- // [K in AllKeys<T>]?: T extends any
339
- // ? K extends keyof T
340
- // ? T[K]
341
- // : never
342
- // : never
343
- // }
344
- // >
345
- function last(arr) {
346
- return arr[arr.length - 1];
347
- }
348
- function isFunction(d) {
349
- return typeof d === 'function';
350
- }
351
- function functionalUpdate(updater, previous) {
352
- if (isFunction(updater)) {
353
- return updater(previous);
208
+ // Notify subscribers
209
+ onUpdate();
210
+ if (!scheduled) {
211
+ // Schedule an update to the browser history
212
+ scheduled = Promise.resolve().then(() => flush());
354
213
  }
355
- return updater;
356
- }
357
- function pick(parent, keys) {
358
- return keys.reduce((obj, key) => {
359
- obj[key] = parent[key];
360
- return obj;
361
- }, {});
362
- }
214
+ };
215
+ return createHistory({
216
+ getLocation,
217
+ subscriber: onUpdate => {
218
+ window.addEventListener(pushStateEvent, () => {
219
+ currentLocation = parseLocation(getHref(), window.history.state);
220
+ onUpdate();
221
+ });
222
+ window.addEventListener(popStateEvent, () => {
223
+ currentLocation = parseLocation(getHref(), window.history.state);
224
+ onUpdate();
225
+ });
226
+ var pushState = window.history.pushState;
227
+ window.history.pushState = function () {
228
+ let res = pushState.apply(history, arguments);
229
+ if (tracking) onUpdate();
230
+ return res;
231
+ };
232
+ var replaceState = window.history.replaceState;
233
+ window.history.replaceState = function () {
234
+ let res = replaceState.apply(history, arguments);
235
+ if (tracking) onUpdate();
236
+ return res;
237
+ };
238
+ return () => {
239
+ window.history.pushState = pushState;
240
+ window.history.replaceState = replaceState;
241
+ window.removeEventListener(pushStateEvent, onUpdate);
242
+ window.removeEventListener(popStateEvent, onUpdate);
243
+ };
244
+ },
245
+ pushState: (path, state, onUpdate) => queueHistoryAction('push', path, state, onUpdate),
246
+ replaceState: (path, state, onUpdate) => queueHistoryAction('replace', path, state, onUpdate),
247
+ back: () => window.history.back(),
248
+ forward: () => window.history.forward(),
249
+ go: n => window.history.go(n),
250
+ createHref: path => createHref(path),
251
+ flush
252
+ });
253
+ }
254
+ function createHashHistory() {
255
+ return createBrowserHistory({
256
+ getHref: () => window.location.hash.substring(1),
257
+ createHref: path => `#${path}`
258
+ });
259
+ }
260
+ function createMemoryHistory(opts = {
261
+ initialEntries: ['/']
262
+ }) {
263
+ const entries = opts.initialEntries;
264
+ let index = opts.initialIndex ?? entries.length - 1;
265
+ let currentState = {
266
+ key: createRandomKey()
267
+ };
268
+ const getLocation = () => parseLocation(entries[index], currentState);
269
+ return createHistory({
270
+ getLocation,
271
+ subscriber: false,
272
+ pushState: (path, state) => {
273
+ currentState = state;
274
+ entries.push(path);
275
+ index++;
276
+ },
277
+ replaceState: (path, state) => {
278
+ currentState = state;
279
+ entries[index] = path;
280
+ },
281
+ back: () => {
282
+ index--;
283
+ },
284
+ forward: () => {
285
+ index = Math.min(index + 1, entries.length - 1);
286
+ },
287
+ go: n => window.history.go(n),
288
+ createHref: path => path
289
+ });
290
+ }
291
+ function parseLocation(href, state) {
292
+ let hashIndex = href.indexOf('#');
293
+ let searchIndex = href.indexOf('?');
294
+ return {
295
+ href,
296
+ pathname: href.substring(0, hashIndex > 0 ? searchIndex > 0 ? Math.min(hashIndex, searchIndex) : hashIndex : searchIndex > 0 ? searchIndex : href.length),
297
+ hash: hashIndex > -1 ? href.substring(hashIndex) : '',
298
+ search: searchIndex > -1 ? href.slice(searchIndex, hashIndex === -1 ? undefined : hashIndex) : '',
299
+ state: state || {}
300
+ };
301
+ }
363
302
 
364
- /**
365
- * This function returns `a` if `b` is deeply equal.
366
- * If not, it will replace any deeply equal children of `b` with those of `a`.
367
- * This can be used for structural sharing between immutable JSON values for example.
368
- * Do not use this with signals
369
- */
370
- function replaceEqualDeep(prev, _next) {
371
- if (prev === _next) {
372
- return prev;
303
+ // Thanks co-pilot!
304
+ function createRandomKey() {
305
+ return (Math.random() + 1).toString(36).substring(7);
306
+ }
307
+
308
+ var prefix = 'Invariant failed';
309
+ function invariant(condition, message) {
310
+ if (condition) {
311
+ return;
373
312
  }
374
- const next = _next;
375
- const array = Array.isArray(prev) && Array.isArray(next);
376
- if (array || isPlainObject(prev) && isPlainObject(next)) {
377
- const prevSize = array ? prev.length : Object.keys(prev).length;
378
- const nextItems = array ? next : Object.keys(next);
379
- const nextSize = nextItems.length;
380
- const copy = array ? [] : {};
381
- let equalItems = 0;
382
- for (let i = 0; i < nextSize; i++) {
383
- const key = array ? i : nextItems[i];
384
- copy[key] = replaceEqualDeep(prev[key], next[key]);
385
- if (copy[key] === prev[key]) {
386
- equalItems++;
387
- }
388
- }
389
- return prevSize === nextSize && equalItems === prevSize ? prev : copy;
313
+ var provided = typeof message === 'function' ? message() : message;
314
+ var value = provided ? "".concat(prefix, ": ").concat(provided) : prefix;
315
+ throw new Error(value);
316
+ }
317
+
318
+ function warning(condition, message) {
319
+ {
320
+ if (condition) {
321
+ return;
390
322
  }
391
- return next;
392
- }
393
323
 
394
- // Copied from: https://github.com/jonschlinkert/is-plain-object
395
- function isPlainObject(o) {
396
- if (!hasObjectPrototype(o)) {
397
- return false;
398
- }
324
+ var text = "Warning: " + message;
399
325
 
400
- // If has modified constructor
401
- const ctor = o.constructor;
402
- if (typeof ctor === 'undefined') {
403
- return true;
326
+ if (typeof console !== 'undefined') {
327
+ console.warn(text);
404
328
  }
405
329
 
406
- // If has modified prototype
407
- const prot = ctor.prototype;
408
- if (!hasObjectPrototype(prot)) {
409
- return false;
330
+ try {
331
+ throw Error(text);
332
+ } catch (x) {}
333
+ }
334
+ }
335
+
336
+ // export type Expand<T> = T
337
+
338
+ // type Compute<T> = { [K in keyof T]: T[K] } | never
339
+
340
+ // type AllKeys<T> = T extends any ? keyof T : never
341
+
342
+ // export type MergeUnion<T, Keys extends keyof T = keyof T> = Compute<
343
+ // {
344
+ // [K in Keys]: T[Keys]
345
+ // } & {
346
+ // [K in AllKeys<T>]?: T extends any
347
+ // ? K extends keyof T
348
+ // ? T[K]
349
+ // : never
350
+ // : never
351
+ // }
352
+ // >
353
+ function last(arr) {
354
+ return arr[arr.length - 1];
355
+ }
356
+ function isFunction(d) {
357
+ return typeof d === 'function';
358
+ }
359
+ function functionalUpdate(updater, previous) {
360
+ if (isFunction(updater)) {
361
+ return updater(previous);
362
+ }
363
+ return updater;
364
+ }
365
+ function pick(parent, keys) {
366
+ return keys.reduce((obj, key) => {
367
+ obj[key] = parent[key];
368
+ return obj;
369
+ }, {});
370
+ }
371
+
372
+ /**
373
+ * This function returns `a` if `b` is deeply equal.
374
+ * If not, it will replace any deeply equal children of `b` with those of `a`.
375
+ * This can be used for structural sharing between immutable JSON values for example.
376
+ * Do not use this with signals
377
+ */
378
+ function replaceEqualDeep(prev, _next) {
379
+ if (prev === _next) {
380
+ return prev;
381
+ }
382
+ const next = _next;
383
+ const array = Array.isArray(prev) && Array.isArray(next);
384
+ if (array || isPlainObject(prev) && isPlainObject(next)) {
385
+ const prevSize = array ? prev.length : Object.keys(prev).length;
386
+ const nextItems = array ? next : Object.keys(next);
387
+ const nextSize = nextItems.length;
388
+ const copy = array ? [] : {};
389
+ let equalItems = 0;
390
+ for (let i = 0; i < nextSize; i++) {
391
+ const key = array ? i : nextItems[i];
392
+ copy[key] = replaceEqualDeep(prev[key], next[key]);
393
+ if (copy[key] === prev[key]) {
394
+ equalItems++;
395
+ }
410
396
  }
397
+ return prevSize === nextSize && equalItems === prevSize ? prev : copy;
398
+ }
399
+ return next;
400
+ }
411
401
 
412
- // If constructor does not have an Object-specific method
413
- if (!prot.hasOwnProperty('isPrototypeOf')) {
414
- return false;
415
- }
402
+ // Copied from: https://github.com/jonschlinkert/is-plain-object
403
+ function isPlainObject(o) {
404
+ if (!hasObjectPrototype(o)) {
405
+ return false;
406
+ }
416
407
 
417
- // Most likely a plain Object
408
+ // If has modified constructor
409
+ const ctor = o.constructor;
410
+ if (typeof ctor === 'undefined') {
418
411
  return true;
419
412
  }
420
- function hasObjectPrototype(o) {
421
- return Object.prototype.toString.call(o) === '[object Object]';
422
- }
423
- function partialDeepEqual(a, b) {
424
- if (a === b) {
425
- return true;
426
- }
427
- if (typeof a !== typeof b) {
428
- return false;
429
- }
430
- if (isPlainObject(a) && isPlainObject(b)) {
431
- return !Object.keys(b).some(key => !partialDeepEqual(a[key], b[key]));
432
- }
433
- if (Array.isArray(a) && Array.isArray(b)) {
434
- return a.length === b.length && a.every((item, index) => partialDeepEqual(item, b[index]));
435
- }
413
+
414
+ // If has modified prototype
415
+ const prot = ctor.prototype;
416
+ if (!hasObjectPrototype(prot)) {
436
417
  return false;
437
418
  }
438
419
 
439
- function joinPaths(paths) {
440
- return cleanPath(paths.filter(Boolean).join('/'));
420
+ // If constructor does not have an Object-specific method
421
+ if (!prot.hasOwnProperty('isPrototypeOf')) {
422
+ return false;
441
423
  }
442
- function cleanPath(path) {
443
- // remove double slashes
444
- return path.replace(/\/{2,}/g, '/');
424
+
425
+ // Most likely a plain Object
426
+ return true;
427
+ }
428
+ function hasObjectPrototype(o) {
429
+ return Object.prototype.toString.call(o) === '[object Object]';
430
+ }
431
+ function partialDeepEqual(a, b) {
432
+ if (a === b) {
433
+ return true;
445
434
  }
446
- function trimPathLeft(path) {
447
- return path === '/' ? path : path.replace(/^\/{1,}/, '');
435
+ if (typeof a !== typeof b) {
436
+ return false;
448
437
  }
449
- function trimPathRight(path) {
450
- return path === '/' ? path : path.replace(/\/{1,}$/, '');
438
+ if (isPlainObject(a) && isPlainObject(b)) {
439
+ return !Object.keys(b).some(key => !partialDeepEqual(a[key], b[key]));
451
440
  }
452
- function trimPath(path) {
453
- return trimPathRight(trimPathLeft(path));
441
+ if (Array.isArray(a) && Array.isArray(b)) {
442
+ return a.length === b.length && a.every((item, index) => partialDeepEqual(item, b[index]));
454
443
  }
455
- function resolvePath(basepath, base, to) {
456
- base = base.replace(new RegExp(`^${basepath}`), '/');
457
- to = to.replace(new RegExp(`^${basepath}`), '/');
458
- let baseSegments = parsePathname(base);
459
- const toSegments = parsePathname(to);
460
- toSegments.forEach((toSegment, index) => {
461
- if (toSegment.value === '/') {
462
- if (!index) {
463
- // Leading slash
464
- baseSegments = [toSegment];
465
- } else if (index === toSegments.length - 1) {
466
- // Trailing Slash
467
- baseSegments.push(toSegment);
468
- } else ;
469
- } else if (toSegment.value === '..') {
470
- // Extra trailing slash? pop it off
471
- if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
472
- baseSegments.pop();
473
- }
474
- baseSegments.pop();
475
- } else if (toSegment.value === '.') {
476
- return;
477
- } else {
444
+ return false;
445
+ }
446
+
447
+ function joinPaths(paths) {
448
+ return cleanPath(paths.filter(Boolean).join('/'));
449
+ }
450
+ function cleanPath(path) {
451
+ // remove double slashes
452
+ return path.replace(/\/{2,}/g, '/');
453
+ }
454
+ function trimPathLeft(path) {
455
+ return path === '/' ? path : path.replace(/^\/{1,}/, '');
456
+ }
457
+ function trimPathRight(path) {
458
+ return path === '/' ? path : path.replace(/\/{1,}$/, '');
459
+ }
460
+ function trimPath(path) {
461
+ return trimPathRight(trimPathLeft(path));
462
+ }
463
+ function resolvePath(basepath, base, to) {
464
+ base = base.replace(new RegExp(`^${basepath}`), '/');
465
+ to = to.replace(new RegExp(`^${basepath}`), '/');
466
+ let baseSegments = parsePathname(base);
467
+ const toSegments = parsePathname(to);
468
+ toSegments.forEach((toSegment, index) => {
469
+ if (toSegment.value === '/') {
470
+ if (!index) {
471
+ // Leading slash
472
+ baseSegments = [toSegment];
473
+ } else if (index === toSegments.length - 1) {
474
+ // Trailing Slash
478
475
  baseSegments.push(toSegment);
476
+ } else ;
477
+ } else if (toSegment.value === '..') {
478
+ // Extra trailing slash? pop it off
479
+ if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
480
+ baseSegments.pop();
479
481
  }
482
+ baseSegments.pop();
483
+ } else if (toSegment.value === '.') {
484
+ return;
485
+ } else {
486
+ baseSegments.push(toSegment);
487
+ }
488
+ });
489
+ const joined = joinPaths([basepath, ...baseSegments.map(d => d.value)]);
490
+ return cleanPath(joined);
491
+ }
492
+ function parsePathname(pathname) {
493
+ if (!pathname) {
494
+ return [];
495
+ }
496
+ pathname = cleanPath(pathname);
497
+ const segments = [];
498
+ if (pathname.slice(0, 1) === '/') {
499
+ pathname = pathname.substring(1);
500
+ segments.push({
501
+ type: 'pathname',
502
+ value: '/'
480
503
  });
481
- const joined = joinPaths([basepath, ...baseSegments.map(d => d.value)]);
482
- return cleanPath(joined);
483
504
  }
484
- function parsePathname(pathname) {
485
- if (!pathname) {
486
- return [];
487
- }
488
- pathname = cleanPath(pathname);
489
- const segments = [];
490
- if (pathname.slice(0, 1) === '/') {
491
- pathname = pathname.substring(1);
492
- segments.push({
493
- type: 'pathname',
494
- value: '/'
495
- });
496
- }
497
- if (!pathname) {
498
- return segments;
499
- }
505
+ if (!pathname) {
506
+ return segments;
507
+ }
500
508
 
501
- // Remove empty segments and '.' segments
502
- const split = pathname.split('/').filter(Boolean);
503
- segments.push(...split.map(part => {
504
- if (part === '$' || part === '*') {
505
- return {
506
- type: 'wildcard',
507
- value: part
508
- };
509
- }
510
- if (part.charAt(0) === '$') {
511
- return {
512
- type: 'param',
513
- value: part
514
- };
515
- }
509
+ // Remove empty segments and '.' segments
510
+ const split = pathname.split('/').filter(Boolean);
511
+ segments.push(...split.map(part => {
512
+ if (part === '$' || part === '*') {
516
513
  return {
517
- type: 'pathname',
514
+ type: 'wildcard',
518
515
  value: part
519
516
  };
520
- }));
521
- if (pathname.slice(-1) === '/') {
522
- pathname = pathname.substring(1);
523
- segments.push({
524
- type: 'pathname',
525
- value: '/'
526
- });
527
517
  }
528
- return segments;
529
- }
530
- function interpolatePath(path, params, leaveWildcards = false) {
531
- const interpolatedPathSegments = parsePathname(path);
532
- return joinPaths(interpolatedPathSegments.map(segment => {
533
- if (segment.type === 'wildcard') {
534
- const value = params[segment.value];
535
- if (leaveWildcards) return `${segment.value}${value ?? ''}`;
536
- return value;
537
- }
538
- if (segment.type === 'param') {
539
- return params[segment.value.substring(1)] ?? '';
540
- }
541
- return segment.value;
542
- }));
543
- }
544
- function matchPathname(basepath, currentPathname, matchLocation) {
545
- const pathParams = matchByPath(basepath, currentPathname, matchLocation);
546
- // const searchMatched = matchBySearch(location.search, matchLocation)
547
-
548
- if (matchLocation.to && !pathParams) {
549
- return;
518
+ if (part.charAt(0) === '$') {
519
+ return {
520
+ type: 'param',
521
+ value: part
522
+ };
550
523
  }
551
- return pathParams ?? {};
524
+ return {
525
+ type: 'pathname',
526
+ value: part
527
+ };
528
+ }));
529
+ if (pathname.slice(-1) === '/') {
530
+ pathname = pathname.substring(1);
531
+ segments.push({
532
+ type: 'pathname',
533
+ value: '/'
534
+ });
552
535
  }
553
- function matchByPath(basepath, from, matchLocation) {
554
- // Remove the base path from the pathname
555
- from = basepath != '/' ? from.substring(basepath.length) : from;
556
- // Default to to $ (wildcard)
557
- const to = `${matchLocation.to ?? '$'}`;
558
- // Parse the from and to
559
- const baseSegments = parsePathname(from);
560
- const routeSegments = parsePathname(to);
561
- if (!from.startsWith('/')) {
562
- baseSegments.unshift({
563
- type: 'pathname',
564
- value: '/'
565
- });
536
+ return segments;
537
+ }
538
+ function interpolatePath(path, params, leaveWildcards = false) {
539
+ const interpolatedPathSegments = parsePathname(path);
540
+ return joinPaths(interpolatedPathSegments.map(segment => {
541
+ if (segment.type === 'wildcard') {
542
+ const value = params[segment.value];
543
+ if (leaveWildcards) return `${segment.value}${value ?? ''}`;
544
+ return value;
566
545
  }
567
- if (!to.startsWith('/')) {
568
- routeSegments.unshift({
569
- type: 'pathname',
570
- value: '/'
571
- });
546
+ if (segment.type === 'param') {
547
+ return params[segment.value.substring(1)] ?? '';
572
548
  }
573
- const params = {};
574
- let isMatch = (() => {
575
- for (let i = 0; i < Math.max(baseSegments.length, routeSegments.length); i++) {
576
- const baseSegment = baseSegments[i];
577
- const routeSegment = routeSegments[i];
578
- const isLastBaseSegment = i >= baseSegments.length - 1;
579
- const isLastRouteSegment = i >= routeSegments.length - 1;
580
- if (routeSegment) {
581
- if (routeSegment.type === 'wildcard') {
582
- if (baseSegment?.value) {
583
- params['*'] = joinPaths(baseSegments.slice(i).map(d => d.value));
584
- return true;
585
- }
586
- return false;
549
+ return segment.value;
550
+ }));
551
+ }
552
+ function matchPathname(basepath, currentPathname, matchLocation) {
553
+ const pathParams = matchByPath(basepath, currentPathname, matchLocation);
554
+ // const searchMatched = matchBySearch(location.search, matchLocation)
555
+
556
+ if (matchLocation.to && !pathParams) {
557
+ return;
558
+ }
559
+ return pathParams ?? {};
560
+ }
561
+ function matchByPath(basepath, from, matchLocation) {
562
+ // Remove the base path from the pathname
563
+ from = basepath != '/' ? from.substring(basepath.length) : from;
564
+ // Default to to $ (wildcard)
565
+ const to = `${matchLocation.to ?? '$'}`;
566
+ // Parse the from and to
567
+ const baseSegments = parsePathname(from);
568
+ const routeSegments = parsePathname(to);
569
+ if (!from.startsWith('/')) {
570
+ baseSegments.unshift({
571
+ type: 'pathname',
572
+ value: '/'
573
+ });
574
+ }
575
+ if (!to.startsWith('/')) {
576
+ routeSegments.unshift({
577
+ type: 'pathname',
578
+ value: '/'
579
+ });
580
+ }
581
+ const params = {};
582
+ let isMatch = (() => {
583
+ for (let i = 0; i < Math.max(baseSegments.length, routeSegments.length); i++) {
584
+ const baseSegment = baseSegments[i];
585
+ const routeSegment = routeSegments[i];
586
+ const isLastBaseSegment = i >= baseSegments.length - 1;
587
+ const isLastRouteSegment = i >= routeSegments.length - 1;
588
+ if (routeSegment) {
589
+ if (routeSegment.type === 'wildcard') {
590
+ if (baseSegment?.value) {
591
+ params['*'] = joinPaths(baseSegments.slice(i).map(d => d.value));
592
+ return true;
587
593
  }
588
- if (routeSegment.type === 'pathname') {
589
- if (routeSegment.value === '/' && !baseSegment?.value) {
590
- return true;
591
- }
592
- if (baseSegment) {
593
- if (matchLocation.caseSensitive) {
594
- if (routeSegment.value !== baseSegment.value) {
595
- return false;
596
- }
597
- } else if (routeSegment.value.toLowerCase() !== baseSegment.value.toLowerCase()) {
594
+ return false;
595
+ }
596
+ if (routeSegment.type === 'pathname') {
597
+ if (routeSegment.value === '/' && !baseSegment?.value) {
598
+ return true;
599
+ }
600
+ if (baseSegment) {
601
+ if (matchLocation.caseSensitive) {
602
+ if (routeSegment.value !== baseSegment.value) {
598
603
  return false;
599
604
  }
605
+ } else if (routeSegment.value.toLowerCase() !== baseSegment.value.toLowerCase()) {
606
+ return false;
600
607
  }
601
608
  }
602
- if (!baseSegment) {
609
+ }
610
+ if (!baseSegment) {
611
+ return false;
612
+ }
613
+ if (routeSegment.type === 'param') {
614
+ if (baseSegment?.value === '/') {
603
615
  return false;
604
616
  }
605
- if (routeSegment.type === 'param') {
606
- if (baseSegment?.value === '/') {
607
- return false;
608
- }
609
- if (baseSegment.value.charAt(0) !== '$') {
610
- params[routeSegment.value.substring(1)] = baseSegment.value;
611
- }
617
+ if (baseSegment.value.charAt(0) !== '$') {
618
+ params[routeSegment.value.substring(1)] = baseSegment.value;
612
619
  }
613
620
  }
614
- if (!isLastBaseSegment && isLastRouteSegment) {
615
- return !!matchLocation.fuzzy;
616
- }
617
621
  }
618
- return true;
619
- })();
620
- return isMatch ? params : undefined;
621
- }
622
-
623
- // @ts-nocheck
624
-
625
- // 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.
626
-
627
- function encode(obj, pfx) {
628
- var k,
629
- i,
630
- tmp,
631
- str = '';
632
- for (k in obj) {
633
- if ((tmp = obj[k]) !== void 0) {
634
- if (Array.isArray(tmp)) {
635
- for (i = 0; i < tmp.length; i++) {
636
- str && (str += '&');
637
- str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp[i]);
638
- }
639
- } else {
622
+ if (!isLastBaseSegment && isLastRouteSegment) {
623
+ return !!matchLocation.fuzzy;
624
+ }
625
+ }
626
+ return true;
627
+ })();
628
+ return isMatch ? params : undefined;
629
+ }
630
+
631
+ // @ts-nocheck
632
+
633
+ // 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.
634
+
635
+ function encode(obj, pfx) {
636
+ var k,
637
+ i,
638
+ tmp,
639
+ str = '';
640
+ for (k in obj) {
641
+ if ((tmp = obj[k]) !== void 0) {
642
+ if (Array.isArray(tmp)) {
643
+ for (i = 0; i < tmp.length; i++) {
640
644
  str && (str += '&');
641
- str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp);
645
+ str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp[i]);
642
646
  }
647
+ } else {
648
+ str && (str += '&');
649
+ str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp);
643
650
  }
644
651
  }
645
- return (pfx || '') + str;
646
- }
647
- function toValue(mix) {
648
- if (!mix) return '';
649
- var str = decodeURIComponent(mix);
650
- if (str === 'false') return false;
651
- if (str === 'true') return true;
652
- return +str * 0 === 0 && +str + '' === str ? +str : str;
653
652
  }
654
- function decode(str) {
655
- var tmp,
656
- k,
657
- out = {},
658
- arr = str.split('&');
659
- while (tmp = arr.shift()) {
660
- tmp = tmp.split('=');
661
- k = tmp.shift();
662
- if (out[k] !== void 0) {
663
- out[k] = [].concat(out[k], toValue(tmp.shift()));
664
- } else {
665
- out[k] = toValue(tmp.shift());
666
- }
653
+ return (pfx || '') + str;
654
+ }
655
+ function toValue(mix) {
656
+ if (!mix) return '';
657
+ var str = decodeURIComponent(mix);
658
+ if (str === 'false') return false;
659
+ if (str === 'true') return true;
660
+ return +str * 0 === 0 && +str + '' === str ? +str : str;
661
+ }
662
+ function decode(str) {
663
+ var tmp,
664
+ k,
665
+ out = {},
666
+ arr = str.split('&');
667
+ while (tmp = arr.shift()) {
668
+ tmp = tmp.split('=');
669
+ k = tmp.shift();
670
+ if (out[k] !== void 0) {
671
+ out[k] = [].concat(out[k], toValue(tmp.shift()));
672
+ } else {
673
+ out[k] = toValue(tmp.shift());
667
674
  }
668
- return out;
669
675
  }
676
+ return out;
677
+ }
670
678
 
671
- const rootRouteId = '__root__';
679
+ const rootRouteId = '__root__';
672
680
 
673
- // The parse type here allows a zod schema to be passed directly to the validator
681
+ // The parse type here allows a zod schema to be passed directly to the validator
674
682
 
675
- // T extends Record<PropertyKey, infer U>
676
- // ? {
677
- // [K in keyof T]: UseLoaderResultPromise<T[K]>
678
- // }
679
- // : UseLoaderResultPromise<T>
683
+ // T extends Record<PropertyKey, infer U>
684
+ // ? {
685
+ // [K in keyof T]: UseLoaderResultPromise<T[K]>
686
+ // }
687
+ // : UseLoaderResultPromise<T>
680
688
 
681
- // export type UseLoaderResultPromise<T> = T extends Promise<infer U>
682
- // ? StreamedPromise<U>
683
- // : T
684
- class Route {
685
- // Set up in this.init()
689
+ // export type UseLoaderResultPromise<T> = T extends Promise<infer U>
690
+ // ? StreamedPromise<U>
691
+ // : T
692
+ class Route {
693
+ // Set up in this.init()
686
694
 
687
- // customId!: TCustomId
695
+ // customId!: TCustomId
688
696
 
689
- // Optional
697
+ // Optional
690
698
 
691
- constructor(options) {
692
- this.options = options || {};
693
- this.isRoot = !options?.getParentRoute;
694
- Route.__onInit(this);
699
+ constructor(options) {
700
+ this.options = options || {};
701
+ this.isRoot = !options?.getParentRoute;
702
+ Route.__onInit(this);
703
+ }
704
+ init = opts => {
705
+ this.originalIndex = opts.originalIndex;
706
+ this.router = opts.router;
707
+ const options = this.options;
708
+ const isRoot = !options?.path && !options?.id;
709
+ this.parentRoute = this.options?.getParentRoute?.();
710
+ if (isRoot) {
711
+ this.path = rootRouteId;
712
+ } else {
713
+ invariant(this.parentRoute, `Child Route instances must pass a 'getParentRoute: () => ParentRoute' option that returns a Route instance.`);
695
714
  }
696
- init = opts => {
697
- this.originalIndex = opts.originalIndex;
698
- this.router = opts.router;
699
- const options = this.options;
700
- const isRoot = !options?.path && !options?.id;
701
- this.parentRoute = this.options?.getParentRoute?.();
702
- if (isRoot) {
703
- this.path = rootRouteId;
704
- } else {
705
- invariant(this.parentRoute, `Child Route instances must pass a 'getParentRoute: () => ParentRoute' option that returns a Route instance.`);
706
- }
707
- let path = isRoot ? rootRouteId : options.path;
715
+ let path = isRoot ? rootRouteId : options.path;
708
716
 
709
- // If the path is anything other than an index path, trim it up
710
- if (path && path !== '/') {
711
- path = trimPath(path);
712
- }
713
- const customId = options?.id || path;
717
+ // If the path is anything other than an index path, trim it up
718
+ if (path && path !== '/') {
719
+ path = trimPath(path);
720
+ }
721
+ const customId = options?.id || path;
714
722
 
715
- // Strip the parentId prefix from the first level of children
716
- let id = isRoot ? rootRouteId : joinPaths([this.parentRoute.id === rootRouteId ? '' : this.parentRoute.id, customId]);
717
- if (path === rootRouteId) {
718
- path = '/';
719
- }
720
- if (id !== rootRouteId) {
721
- id = joinPaths(['/', id]);
722
- }
723
- const fullPath = id === rootRouteId ? '/' : joinPaths([this.parentRoute.fullPath, path]);
724
- this.path = path;
725
- this.id = id;
726
- // this.customId = customId as TCustomId
727
- this.fullPath = fullPath;
728
- this.to = fullPath;
729
- };
730
- addChildren = children => {
731
- this.children = children;
732
- return this;
733
- };
734
- update = options => {
735
- Object.assign(this.options, options);
736
- return this;
737
- };
738
- static __onInit = route => {
739
- // This is a dummy static method that should get
740
- // replaced by a framework specific implementation if necessary
741
- };
742
- }
743
- class RouterContext {
744
- constructor() {}
745
- createRootRoute = options => {
746
- return new RootRoute(options);
747
- };
748
- }
749
- class RootRoute extends Route {
750
- constructor(options) {
751
- super(options);
723
+ // Strip the parentId prefix from the first level of children
724
+ let id = isRoot ? rootRouteId : joinPaths([this.parentRoute.id === rootRouteId ? '' : this.parentRoute.id, customId]);
725
+ if (path === rootRouteId) {
726
+ path = '/';
752
727
  }
728
+ if (id !== rootRouteId) {
729
+ id = joinPaths(['/', id]);
730
+ }
731
+ const fullPath = id === rootRouteId ? '/' : joinPaths([this.parentRoute.fullPath, path]);
732
+ this.path = path;
733
+ this.id = id;
734
+ // this.customId = customId as TCustomId
735
+ this.fullPath = fullPath;
736
+ this.to = fullPath;
737
+ };
738
+ addChildren = children => {
739
+ this.children = children;
740
+ return this;
741
+ };
742
+ update = options => {
743
+ Object.assign(this.options, options);
744
+ return this;
745
+ };
746
+ static __onInit = route => {
747
+ // This is a dummy static method that should get
748
+ // replaced by a framework specific implementation if necessary
749
+ };
750
+ }
751
+ class RouterContext {
752
+ constructor() {}
753
+ createRootRoute = options => {
754
+ return new RootRoute(options);
755
+ };
756
+ }
757
+ class RootRoute extends Route {
758
+ constructor(options) {
759
+ super(options);
753
760
  }
754
- function createRouteMask(opts) {
755
- return opts;
761
+ }
762
+ function createRouteMask(opts) {
763
+ return opts;
764
+ }
765
+
766
+ class FileRoute {
767
+ constructor(path) {
768
+ this.path = path;
756
769
  }
757
-
758
- class FileRoute {
759
- constructor(path) {
760
- this.path = path;
761
- }
762
- createRoute = options => {
763
- const route = new Route(options);
764
- route.isRoot = false;
765
- return route;
766
- };
770
+ createRoute = options => {
771
+ const route = new Route(options);
772
+ route.isRoot = false;
773
+ return route;
774
+ };
775
+ }
776
+
777
+ /**
778
+ * @tanstack/store/src/index.ts
779
+ *
780
+ * Copyright (c) TanStack
781
+ *
782
+ * This source code is licensed under the MIT license found in the
783
+ * LICENSE.md file in the root directory of this source tree.
784
+ *
785
+ * @license MIT
786
+ */
787
+ class Store {
788
+ listeners = new Set();
789
+ _batching = false;
790
+ _flushing = 0;
791
+ _nextPriority = null;
792
+ constructor(initialState, options) {
793
+ this.state = initialState;
794
+ this.options = options;
767
795
  }
768
-
769
- /**
770
- * @tanstack/store/src/index.ts
771
- *
772
- * Copyright (c) TanStack
773
- *
774
- * This source code is licensed under the MIT license found in the
775
- * LICENSE.md file in the root directory of this source tree.
776
- *
777
- * @license MIT
778
- */
779
- class Store {
780
- listeners = new Set();
781
- _batching = false;
782
- _flushing = 0;
783
- _nextPriority = null;
784
- constructor(initialState, options) {
785
- this.state = initialState;
786
- this.options = options;
787
- }
788
- subscribe = listener => {
789
- this.listeners.add(listener);
790
- const unsub = this.options?.onSubscribe?.(listener, this);
791
- return () => {
792
- this.listeners.delete(listener);
793
- unsub?.();
794
- };
796
+ subscribe = listener => {
797
+ this.listeners.add(listener);
798
+ const unsub = this.options?.onSubscribe?.(listener, this);
799
+ return () => {
800
+ this.listeners.delete(listener);
801
+ unsub?.();
795
802
  };
796
- setState = (updater, opts) => {
797
- const previous = this.state;
798
- this.state = this.options?.updateFn ? this.options.updateFn(previous)(updater) : updater(previous);
799
- const priority = opts?.priority ?? this.options?.defaultPriority ?? 'high';
800
- if (this._nextPriority === null) {
801
- this._nextPriority = priority;
802
- } else if (this._nextPriority === 'high') {
803
- this._nextPriority = priority;
804
- } else {
805
- this._nextPriority = this.options?.defaultPriority ?? 'high';
806
- }
803
+ };
804
+ setState = (updater, opts) => {
805
+ const previous = this.state;
806
+ this.state = this.options?.updateFn ? this.options.updateFn(previous)(updater) : updater(previous);
807
+ const priority = opts?.priority ?? this.options?.defaultPriority ?? 'high';
808
+ if (this._nextPriority === null) {
809
+ this._nextPriority = priority;
810
+ } else if (this._nextPriority === 'high') {
811
+ this._nextPriority = priority;
812
+ } else {
813
+ this._nextPriority = this.options?.defaultPriority ?? 'high';
814
+ }
807
815
 
808
- // Always run onUpdate, regardless of batching
809
- this.options?.onUpdate?.({
810
- priority: this._nextPriority
811
- });
816
+ // Always run onUpdate, regardless of batching
817
+ this.options?.onUpdate?.({
818
+ priority: this._nextPriority
819
+ });
812
820
 
813
- // Attempt to flush
814
- this._flush();
815
- };
816
- _flush = () => {
817
- if (this._batching) return;
818
- const flushId = ++this._flushing;
819
- this.listeners.forEach(listener => {
820
- if (this._flushing !== flushId) return;
821
- listener({
822
- priority: this._nextPriority ?? 'high'
823
- });
821
+ // Attempt to flush
822
+ this._flush();
823
+ };
824
+ _flush = () => {
825
+ if (this._batching) return;
826
+ const flushId = ++this._flushing;
827
+ this.listeners.forEach(listener => {
828
+ if (this._flushing !== flushId) return;
829
+ listener({
830
+ priority: this._nextPriority ?? 'high'
824
831
  });
825
- };
826
- batch = cb => {
827
- if (this._batching) return cb();
828
- this._batching = true;
829
- cb();
830
- this._batching = false;
831
- this._flush();
832
- };
833
- }
832
+ });
833
+ };
834
+ batch = cb => {
835
+ if (this._batching) return cb();
836
+ this._batching = true;
837
+ cb();
838
+ this._batching = false;
839
+ this._flush();
840
+ };
841
+ }
842
+
843
+ const defaultParseSearch = parseSearchWith(JSON.parse);
844
+ const defaultStringifySearch = stringifySearchWith(JSON.stringify, JSON.parse);
845
+ function parseSearchWith(parser) {
846
+ return searchStr => {
847
+ if (searchStr.substring(0, 1) === '?') {
848
+ searchStr = searchStr.substring(1);
849
+ }
850
+ let query = decode(searchStr);
834
851
 
835
- const defaultParseSearch = parseSearchWith(JSON.parse);
836
- const defaultStringifySearch = stringifySearchWith(JSON.stringify, JSON.parse);
837
- function parseSearchWith(parser) {
838
- return searchStr => {
839
- if (searchStr.substring(0, 1) === '?') {
840
- searchStr = searchStr.substring(1);
852
+ // Try to parse any query params that might be json
853
+ for (let key in query) {
854
+ const value = query[key];
855
+ if (typeof value === 'string') {
856
+ try {
857
+ query[key] = parser(value);
858
+ } catch (err) {
859
+ //
860
+ }
841
861
  }
842
- let query = decode(searchStr);
843
-
844
- // Try to parse any query params that might be json
845
- for (let key in query) {
846
- const value = query[key];
847
- if (typeof value === 'string') {
848
- try {
849
- query[key] = parser(value);
850
- } catch (err) {
851
- //
852
- }
853
- }
854
- }
855
- return query;
856
- };
857
- }
858
- function stringifySearchWith(stringify, parser) {
859
- function stringifyValue(val) {
860
- if (typeof val === 'object' && val !== null) {
861
- try {
862
- return stringify(val);
863
- } catch (err) {
864
- // silent
865
- }
866
- } else if (typeof val === 'string' && typeof parser === 'function') {
867
- try {
868
- // Check if it's a valid parseable string.
869
- // If it is, then stringify it again.
870
- parser(val);
871
- return stringify(val);
872
- } catch (err) {
873
- // silent
874
- }
875
- }
876
- return val;
877
862
  }
878
- return search => {
879
- search = {
880
- ...search
881
- };
882
- if (search) {
883
- Object.keys(search).forEach(key => {
884
- const val = search[key];
885
- if (typeof val === 'undefined' || val === undefined) {
886
- delete search[key];
887
- } else {
888
- search[key] = stringifyValue(val);
889
- }
890
- });
863
+ return query;
864
+ };
865
+ }
866
+ function stringifySearchWith(stringify, parser) {
867
+ function stringifyValue(val) {
868
+ if (typeof val === 'object' && val !== null) {
869
+ try {
870
+ return stringify(val);
871
+ } catch (err) {
872
+ // silent
891
873
  }
892
- const searchStr = encode(search).toString();
893
- return searchStr ? `?${searchStr}` : '';
894
- };
895
- }
896
-
897
- //
898
-
899
- const componentTypes = ['component', 'errorComponent', 'pendingComponent'];
900
- const visibilityChangeEvent = 'visibilitychange';
901
- const focusEvent = 'focus';
902
- const preloadWarning = 'Error preloading route! ☝️';
903
- class Router {
904
- #unsubHistory;
905
- resetNextScroll = false;
906
- tempLocationKey = `${Math.round(Math.random() * 10000000)}`;
907
- // nextTemporaryLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
908
-
909
- constructor(options) {
910
- this.options = {
911
- defaultPreloadDelay: 50,
912
- context: undefined,
913
- ...options,
914
- stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
915
- parseSearch: options?.parseSearch ?? defaultParseSearch
916
- // fetchServerDataFn: options?.fetchServerDataFn ?? defaultFetchServerDataFn,
917
- };
918
-
919
- this.__store = new Store(getInitialRouterState(), {
920
- onUpdate: () => {
921
- const prev = this.state;
922
- const next = this.__store.state;
923
- const matchesByIdChanged = prev.matchesById !== next.matchesById;
924
- let matchesChanged;
925
- let pendingMatchesChanged;
926
- if (!matchesByIdChanged) {
927
- matchesChanged = prev.matchIds.length !== next.matchIds.length || prev.matchIds.some((d, i) => d !== next.matchIds[i]);
928
- pendingMatchesChanged = prev.pendingMatchIds.length !== next.pendingMatchIds.length || prev.pendingMatchIds.some((d, i) => d !== next.pendingMatchIds[i]);
929
- }
930
- if (matchesByIdChanged || matchesChanged) {
931
- next.matches = next.matchIds.map(id => {
932
- return next.matchesById[id];
933
- });
934
- }
935
- if (matchesByIdChanged || pendingMatchesChanged) {
936
- next.pendingMatches = next.pendingMatchIds.map(id => {
937
- return next.matchesById[id];
938
- });
939
- }
940
- if (matchesByIdChanged || matchesChanged || pendingMatchesChanged) {
941
- const hasPendingComponent = next.pendingMatches.some(d => {
942
- const route = this.getRoute(d.routeId);
943
- return !!route?.options.pendingComponent;
944
- });
945
- next.renderedMatchIds = hasPendingComponent ? next.pendingMatchIds : next.matchIds;
946
- next.renderedMatches = next.renderedMatchIds.map(id => {
947
- return next.matchesById[id];
948
- });
949
- }
950
- next.isFetching = [...next.matches, ...next.pendingMatches].some(d => d.isFetching);
951
- this.state = next;
952
- },
953
- defaultPriority: 'low'
954
- });
955
- this.state = this.__store.state;
956
- this.update(options);
957
- const nextLocation = this.buildLocation({
958
- search: true,
959
- params: true,
960
- hash: true,
961
- state: true
962
- });
963
- if (this.state.location.href !== nextLocation.href) {
964
- this.#commitLocation({
965
- ...nextLocation,
966
- replace: true
967
- });
874
+ } else if (typeof val === 'string' && typeof parser === 'function') {
875
+ try {
876
+ // Check if it's a valid parseable string.
877
+ // If it is, then stringify it again.
878
+ parser(val);
879
+ return stringify(val);
880
+ } catch (err) {
881
+ // silent
968
882
  }
969
883
  }
970
- subscribers = new Set();
971
- subscribe = (eventType, fn) => {
972
- const listener = {
973
- eventType,
974
- fn
975
- };
976
- this.subscribers.add(listener);
977
- return () => {
978
- this.subscribers.delete(listener);
979
- };
884
+ return val;
885
+ }
886
+ return search => {
887
+ search = {
888
+ ...search
980
889
  };
981
- #emit = routerEvent => {
982
- this.subscribers.forEach(listener => {
983
- if (listener.eventType === routerEvent.type) {
984
- listener.fn(routerEvent);
890
+ if (search) {
891
+ Object.keys(search).forEach(key => {
892
+ const val = search[key];
893
+ if (typeof val === 'undefined' || val === undefined) {
894
+ delete search[key];
895
+ } else {
896
+ search[key] = stringifyValue(val);
985
897
  }
986
898
  });
899
+ }
900
+ const searchStr = encode(search).toString();
901
+ return searchStr ? `?${searchStr}` : '';
902
+ };
903
+ }
904
+
905
+ //
906
+
907
+ const componentTypes = ['component', 'errorComponent', 'pendingComponent'];
908
+ const visibilityChangeEvent = 'visibilitychange';
909
+ const focusEvent = 'focus';
910
+ const preloadWarning = 'Error preloading route! ☝️';
911
+ class Router {
912
+ #unsubHistory;
913
+ resetNextScroll = false;
914
+ tempLocationKey = `${Math.round(Math.random() * 10000000)}`;
915
+ // nextTemporaryLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
916
+
917
+ constructor(options) {
918
+ this.options = {
919
+ defaultPreloadDelay: 50,
920
+ context: undefined,
921
+ ...options,
922
+ stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
923
+ parseSearch: options?.parseSearch ?? defaultParseSearch
924
+ // fetchServerDataFn: options?.fetchServerDataFn ?? defaultFetchServerDataFn,
987
925
  };
988
- reset = () => {
989
- this.__store.setState(s => Object.assign(s, getInitialRouterState()));
990
- };
991
- mount = () => {
992
- // addEventListener does not exist in React Native, but window does
993
- // In the future, we might need to invert control here for more adapters
994
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
995
- if (typeof window !== 'undefined' && window.addEventListener) {
996
- window.addEventListener(visibilityChangeEvent, this.#onFocus, false);
997
- window.addEventListener(focusEvent, this.#onFocus, false);
998
- }
999
- this.safeLoad();
1000
- return () => {
1001
- if (typeof window !== 'undefined' && window.removeEventListener) {
1002
- window.removeEventListener(visibilityChangeEvent, this.#onFocus);
1003
- window.removeEventListener(focusEvent, this.#onFocus);
926
+
927
+ this.__store = new Store(getInitialRouterState(), {
928
+ onUpdate: () => {
929
+ const prev = this.state;
930
+ const next = this.__store.state;
931
+ const matchesByIdChanged = prev.matchesById !== next.matchesById;
932
+ let matchesChanged;
933
+ let pendingMatchesChanged;
934
+ if (!matchesByIdChanged) {
935
+ matchesChanged = prev.matchIds.length !== next.matchIds.length || prev.matchIds.some((d, i) => d !== next.matchIds[i]);
936
+ pendingMatchesChanged = prev.pendingMatchIds.length !== next.pendingMatchIds.length || prev.pendingMatchIds.some((d, i) => d !== next.pendingMatchIds[i]);
1004
937
  }
1005
- };
1006
- };
1007
- #onFocus = () => {
1008
- if (this.options.reloadOnWindowFocus ?? true) {
1009
- this.invalidate({
1010
- __fromFocus: true
1011
- });
1012
- }
1013
- };
1014
- update = opts => {
1015
- this.options = {
1016
- ...this.options,
1017
- ...opts,
1018
- context: {
1019
- ...this.options.context,
1020
- ...opts?.context
938
+ if (matchesByIdChanged || matchesChanged) {
939
+ next.matches = next.matchIds.map(id => {
940
+ return next.matchesById[id];
941
+ });
1021
942
  }
1022
- };
1023
- if (!this.history || this.options.history && this.options.history !== this.history) {
1024
- if (this.#unsubHistory) {
1025
- this.#unsubHistory();
943
+ if (matchesByIdChanged || pendingMatchesChanged) {
944
+ next.pendingMatches = next.pendingMatchIds.map(id => {
945
+ return next.matchesById[id];
946
+ });
1026
947
  }
1027
- this.history = this.options.history ?? (isServer ? createMemoryHistory() : createBrowserHistory());
1028
- const parsedLocation = this.#parseLocation();
1029
- this.__store.setState(s => ({
1030
- ...s,
1031
- resolvedLocation: parsedLocation,
1032
- location: parsedLocation
1033
- }));
1034
- this.#unsubHistory = this.history.subscribe(() => {
1035
- this.safeLoad({
1036
- next: this.#parseLocation(this.state.location)
948
+ if (matchesByIdChanged || matchesChanged || pendingMatchesChanged) {
949
+ const hasPendingComponent = next.pendingMatches.some(d => {
950
+ const route = this.getRoute(d.routeId);
951
+ return !!route?.options.pendingComponent;
1037
952
  });
1038
- });
953
+ next.renderedMatchIds = hasPendingComponent ? next.pendingMatchIds : next.matchIds;
954
+ next.renderedMatches = next.renderedMatchIds.map(id => {
955
+ return next.matchesById[id];
956
+ });
957
+ }
958
+ next.isFetching = [...next.matches, ...next.pendingMatches].some(d => d.isFetching);
959
+ this.state = next;
960
+ },
961
+ defaultPriority: 'low'
962
+ });
963
+ this.state = this.__store.state;
964
+ this.update(options);
965
+ const nextLocation = this.buildLocation({
966
+ search: true,
967
+ params: true,
968
+ hash: true,
969
+ state: true
970
+ });
971
+ if (this.state.location.href !== nextLocation.href) {
972
+ this.#commitLocation({
973
+ ...nextLocation,
974
+ replace: true
975
+ });
976
+ }
977
+ }
978
+ subscribers = new Set();
979
+ subscribe = (eventType, fn) => {
980
+ const listener = {
981
+ eventType,
982
+ fn
983
+ };
984
+ this.subscribers.add(listener);
985
+ return () => {
986
+ this.subscribers.delete(listener);
987
+ };
988
+ };
989
+ #emit = routerEvent => {
990
+ this.subscribers.forEach(listener => {
991
+ if (listener.eventType === routerEvent.type) {
992
+ listener.fn(routerEvent);
1039
993
  }
1040
- const {
1041
- basepath,
1042
- routeTree
1043
- } = this.options;
1044
- this.basepath = `/${trimPath(basepath ?? '') ?? ''}`;
1045
- if (routeTree && routeTree !== this.routeTree) {
1046
- this.#processRoutes(routeTree);
994
+ });
995
+ };
996
+ reset = () => {
997
+ this.__store.setState(s => Object.assign(s, getInitialRouterState()));
998
+ };
999
+ mount = () => {
1000
+ // addEventListener does not exist in React Native, but window does
1001
+ // In the future, we might need to invert control here for more adapters
1002
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1003
+ if (typeof window !== 'undefined' && window.addEventListener) {
1004
+ window.addEventListener(visibilityChangeEvent, this.#onFocus, false);
1005
+ window.addEventListener(focusEvent, this.#onFocus, false);
1006
+ }
1007
+ this.safeLoad();
1008
+ return () => {
1009
+ if (typeof window !== 'undefined' && window.removeEventListener) {
1010
+ window.removeEventListener(visibilityChangeEvent, this.#onFocus);
1011
+ window.removeEventListener(focusEvent, this.#onFocus);
1047
1012
  }
1048
- return this;
1049
1013
  };
1050
- cancelMatches = () => {
1051
- this.state.matches.forEach(match => {
1052
- this.cancelMatch(match.id);
1014
+ };
1015
+ #onFocus = () => {
1016
+ if (this.options.reloadOnWindowFocus ?? true) {
1017
+ this.invalidate({
1018
+ __fromFocus: true
1053
1019
  });
1054
- };
1055
- cancelMatch = id => {
1056
- this.getRouteMatch(id)?.abortController?.abort();
1057
- };
1058
- safeLoad = async opts => {
1059
- try {
1060
- return this.load(opts);
1061
- } catch (err) {
1062
- // Don't do anything
1020
+ }
1021
+ };
1022
+ update = opts => {
1023
+ this.options = {
1024
+ ...this.options,
1025
+ ...opts,
1026
+ context: {
1027
+ ...this.options.context,
1028
+ ...opts?.context
1063
1029
  }
1064
1030
  };
1065
- latestLoadPromise = Promise.resolve();
1066
- load = async opts => {
1067
- const promise = new Promise(async (resolve, reject) => {
1068
- const prevLocation = this.state.resolvedLocation;
1069
- const pathDidChange = !!(opts?.next && prevLocation.href !== opts.next.href);
1070
- let latestPromise;
1071
- const checkLatest = () => {
1072
- return this.latestLoadPromise !== promise ? this.latestLoadPromise : undefined;
1073
- };
1074
-
1075
- // Cancel any pending matches
1076
-
1077
- let pendingMatches;
1078
- this.#emit({
1079
- type: 'onBeforeLoad',
1080
- from: prevLocation,
1081
- to: opts?.next ?? this.state.location,
1082
- pathChanged: pathDidChange
1031
+ if (!this.history || this.options.history && this.options.history !== this.history) {
1032
+ if (this.#unsubHistory) {
1033
+ this.#unsubHistory();
1034
+ }
1035
+ this.history = this.options.history ?? (isServer ? createMemoryHistory() : createBrowserHistory());
1036
+ const parsedLocation = this.#parseLocation();
1037
+ this.__store.setState(s => ({
1038
+ ...s,
1039
+ resolvedLocation: parsedLocation,
1040
+ location: parsedLocation
1041
+ }));
1042
+ this.#unsubHistory = this.history.subscribe(() => {
1043
+ this.safeLoad({
1044
+ next: this.#parseLocation(this.state.location)
1083
1045
  });
1084
- this.__store.batch(() => {
1085
- if (opts?.next) {
1086
- // Ingest the new location
1087
- this.__store.setState(s => ({
1088
- ...s,
1089
- location: opts.next
1090
- }));
1091
- }
1046
+ });
1047
+ }
1048
+ const {
1049
+ basepath,
1050
+ routeTree
1051
+ } = this.options;
1052
+ this.basepath = `/${trimPath(basepath ?? '') ?? ''}`;
1053
+ if (routeTree && routeTree !== this.routeTree) {
1054
+ this.#processRoutes(routeTree);
1055
+ }
1056
+ return this;
1057
+ };
1058
+ cancelMatches = () => {
1059
+ this.state.matches.forEach(match => {
1060
+ this.cancelMatch(match.id);
1061
+ });
1062
+ };
1063
+ cancelMatch = id => {
1064
+ this.getRouteMatch(id)?.abortController?.abort();
1065
+ };
1066
+ safeLoad = async opts => {
1067
+ try {
1068
+ return this.load(opts);
1069
+ } catch (err) {
1070
+ // Don't do anything
1071
+ }
1072
+ };
1073
+ latestLoadPromise = Promise.resolve();
1074
+ load = async opts => {
1075
+ const promise = new Promise(async (resolve, reject) => {
1076
+ const prevLocation = this.state.resolvedLocation;
1077
+ const pathDidChange = !!(opts?.next && prevLocation.href !== opts.next.href);
1078
+ let latestPromise;
1079
+ const checkLatest = () => {
1080
+ return this.latestLoadPromise !== promise ? this.latestLoadPromise : undefined;
1081
+ };
1092
1082
 
1093
- // Match the routes
1094
- pendingMatches = this.matchRoutes(this.state.location.pathname, this.state.location.search, {
1095
- throwOnError: opts?.throwOnError,
1096
- debug: true
1097
- });
1083
+ // Cancel any pending matches
1084
+
1085
+ let pendingMatches;
1086
+ this.#emit({
1087
+ type: 'onBeforeLoad',
1088
+ from: prevLocation,
1089
+ to: opts?.next ?? this.state.location,
1090
+ pathChanged: pathDidChange
1091
+ });
1092
+ this.__store.batch(() => {
1093
+ if (opts?.next) {
1094
+ // Ingest the new location
1098
1095
  this.__store.setState(s => ({
1099
1096
  ...s,
1100
- status: 'pending',
1101
- pendingMatchIds: pendingMatches.map(d => d.id),
1102
- matchesById: this.#mergeMatches(s.matchesById, pendingMatches)
1097
+ location: opts.next
1103
1098
  }));
1099
+ }
1100
+
1101
+ // Match the routes
1102
+ pendingMatches = this.matchRoutes(this.state.location.pathname, this.state.location.search, {
1103
+ throwOnError: opts?.throwOnError,
1104
+ debug: true
1104
1105
  });
1106
+ this.__store.setState(s => ({
1107
+ ...s,
1108
+ status: 'pending',
1109
+ pendingMatchIds: pendingMatches.map(d => d.id),
1110
+ matchesById: this.#mergeMatches(s.matchesById, pendingMatches)
1111
+ }));
1112
+ });
1113
+ try {
1114
+ // Load the matches
1105
1115
  try {
1106
- // Load the matches
1107
- try {
1108
- await this.loadMatches(pendingMatches.map(d => d.id));
1109
- } catch (err) {
1110
- // swallow this error, since we'll display the
1111
- // errors on the route components
1112
- }
1116
+ await this.loadMatches(pendingMatches.map(d => d.id));
1117
+ } catch (err) {
1118
+ // swallow this error, since we'll display the
1119
+ // errors on the route components
1120
+ }
1113
1121
 
1114
- // Only apply the latest transition
1115
- if (latestPromise = checkLatest()) {
1116
- return latestPromise;
1117
- }
1118
- const exitingMatchIds = this.state.matchIds.filter(id => !this.state.pendingMatchIds.includes(id));
1119
- const enteringMatchIds = this.state.pendingMatchIds.filter(id => !this.state.matchIds.includes(id));
1120
- const stayingMatchIds = this.state.matchIds.filter(id => this.state.pendingMatchIds.includes(id));
1121
- this.__store.setState(s => ({
1122
- ...s,
1123
- status: 'idle',
1124
- resolvedLocation: s.location,
1125
- matchIds: s.pendingMatchIds,
1126
- pendingMatchIds: []
1127
- }));
1128
- [[exitingMatchIds, 'onLeave'], [enteringMatchIds, 'onEnter'], [stayingMatchIds, 'onTransition']].forEach(([matchIds, hook]) => {
1129
- matchIds.forEach(id => {
1130
- const match = this.getRouteMatch(id);
1131
- const route = this.getRoute(match.routeId);
1132
- route.options[hook]?.(match);
1133
- });
1134
- });
1135
- this.#emit({
1136
- type: 'onLoad',
1137
- from: prevLocation,
1138
- to: this.state.location,
1139
- pathChanged: pathDidChange
1122
+ // Only apply the latest transition
1123
+ if (latestPromise = checkLatest()) {
1124
+ return latestPromise;
1125
+ }
1126
+ const exitingMatchIds = this.state.matchIds.filter(id => !this.state.pendingMatchIds.includes(id));
1127
+ const enteringMatchIds = this.state.pendingMatchIds.filter(id => !this.state.matchIds.includes(id));
1128
+ const stayingMatchIds = this.state.matchIds.filter(id => this.state.pendingMatchIds.includes(id));
1129
+ this.__store.setState(s => ({
1130
+ ...s,
1131
+ status: 'idle',
1132
+ resolvedLocation: s.location,
1133
+ matchIds: s.pendingMatchIds,
1134
+ pendingMatchIds: []
1135
+ }));
1136
+ [[exitingMatchIds, 'onLeave'], [enteringMatchIds, 'onEnter'], [stayingMatchIds, 'onTransition']].forEach(([matchIds, hook]) => {
1137
+ matchIds.forEach(id => {
1138
+ const match = this.getRouteMatch(id);
1139
+ const route = this.getRoute(match.routeId);
1140
+ route.options[hook]?.(match);
1140
1141
  });
1141
- resolve();
1142
- } catch (err) {
1143
- // Only apply the latest transition
1144
- if (latestPromise = checkLatest()) {
1145
- return latestPromise;
1146
- }
1147
- reject(err);
1142
+ });
1143
+ this.#emit({
1144
+ type: 'onLoad',
1145
+ from: prevLocation,
1146
+ to: this.state.location,
1147
+ pathChanged: pathDidChange
1148
+ });
1149
+ resolve();
1150
+ } catch (err) {
1151
+ // Only apply the latest transition
1152
+ if (latestPromise = checkLatest()) {
1153
+ return latestPromise;
1148
1154
  }
1149
- });
1150
- this.latestLoadPromise = promise;
1151
- this.latestLoadPromise.then(() => {
1152
- this.cleanMatches();
1153
- });
1154
- return this.latestLoadPromise;
1155
+ reject(err);
1156
+ }
1157
+ });
1158
+ this.latestLoadPromise = promise;
1159
+ this.latestLoadPromise.then(() => {
1160
+ this.cleanMatches();
1161
+ });
1162
+ return this.latestLoadPromise;
1163
+ };
1164
+ #mergeMatches = (prevMatchesById, nextMatches) => {
1165
+ let matchesById = {
1166
+ ...prevMatchesById
1155
1167
  };
1156
- #mergeMatches = (prevMatchesById, nextMatches) => {
1157
- let matchesById = {
1158
- ...prevMatchesById
1168
+ nextMatches.forEach(match => {
1169
+ if (!matchesById[match.id]) {
1170
+ matchesById[match.id] = match;
1171
+ }
1172
+ matchesById[match.id] = {
1173
+ ...matchesById[match.id],
1174
+ ...match
1159
1175
  };
1160
- nextMatches.forEach(match => {
1161
- if (!matchesById[match.id]) {
1162
- matchesById[match.id] = match;
1163
- }
1164
- matchesById[match.id] = {
1165
- ...matchesById[match.id],
1166
- ...match
1167
- };
1168
- });
1169
- return matchesById;
1170
- };
1171
- getRoute = id => {
1172
- const route = this.routesById[id];
1173
- invariant(route, `Route with id "${id}" not found`);
1174
- return route;
1175
- };
1176
- preloadRoute = async (navigateOpts = this.state.location) => {
1177
- let next = this.buildLocation(navigateOpts);
1178
- const matches = this.matchRoutes(next.pathname, next.search, {
1179
- throwOnError: true
1180
- });
1176
+ });
1177
+ return matchesById;
1178
+ };
1179
+ getRoute = id => {
1180
+ const route = this.routesById[id];
1181
+ invariant(route, `Route with id "${id}" not found`);
1182
+ return route;
1183
+ };
1184
+ preloadRoute = async (navigateOpts = this.state.location) => {
1185
+ let next = this.buildLocation(navigateOpts);
1186
+ const matches = this.matchRoutes(next.pathname, next.search, {
1187
+ throwOnError: true
1188
+ });
1189
+ this.__store.setState(s => {
1190
+ return {
1191
+ ...s,
1192
+ matchesById: this.#mergeMatches(s.matchesById, matches)
1193
+ };
1194
+ });
1195
+ await this.loadMatches(matches.map(d => d.id), {
1196
+ preload: true,
1197
+ maxAge: navigateOpts.maxAge
1198
+ });
1199
+ return [last(matches), matches];
1200
+ };
1201
+ cleanMatches = () => {
1202
+ const now = Date.now();
1203
+ const outdatedMatchIds = Object.values(this.state.matchesById).filter(match => {
1204
+ const route = this.getRoute(match.routeId);
1205
+ return !this.state.matchIds.includes(match.id) && !this.state.pendingMatchIds.includes(match.id) && (match.preloadMaxAge > -1 ? match.updatedAt + match.preloadMaxAge < now : true) && (route.options.gcMaxAge ? match.updatedAt + route.options.gcMaxAge < now : true);
1206
+ }).map(d => d.id);
1207
+ if (outdatedMatchIds.length) {
1181
1208
  this.__store.setState(s => {
1209
+ const matchesById = {
1210
+ ...s.matchesById
1211
+ };
1212
+ outdatedMatchIds.forEach(id => {
1213
+ delete matchesById[id];
1214
+ });
1182
1215
  return {
1183
1216
  ...s,
1184
- matchesById: this.#mergeMatches(s.matchesById, matches)
1217
+ matchesById
1185
1218
  };
1186
1219
  });
1187
- await this.loadMatches(matches.map(d => d.id), {
1188
- preload: true,
1189
- maxAge: navigateOpts.maxAge
1190
- });
1191
- return [last(matches), matches];
1192
- };
1193
- cleanMatches = () => {
1194
- const now = Date.now();
1195
- const outdatedMatchIds = Object.values(this.state.matchesById).filter(match => {
1196
- const route = this.getRoute(match.routeId);
1197
- return !this.state.matchIds.includes(match.id) && !this.state.pendingMatchIds.includes(match.id) && (match.preloadMaxAge > -1 ? match.updatedAt + match.preloadMaxAge < now : true) && (route.options.gcMaxAge ? match.updatedAt + route.options.gcMaxAge < now : true);
1198
- }).map(d => d.id);
1199
- if (outdatedMatchIds.length) {
1200
- this.__store.setState(s => {
1201
- const matchesById = {
1202
- ...s.matchesById
1203
- };
1204
- outdatedMatchIds.forEach(id => {
1205
- delete matchesById[id];
1206
- });
1207
- return {
1208
- ...s,
1209
- matchesById
1210
- };
1211
- });
1212
- }
1213
- };
1214
- matchRoutes = (pathname, locationSearch, opts) => {
1215
- let routeParams = {};
1216
- let foundRoute = this.flatRoutes.find(route => {
1217
- const matchedParams = matchPathname(this.basepath, trimPathRight(pathname), {
1218
- to: route.fullPath,
1219
- caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive
1220
- });
1221
- if (matchedParams) {
1222
- routeParams = matchedParams;
1223
- return true;
1224
- }
1225
- return false;
1220
+ }
1221
+ };
1222
+ matchRoutes = (pathname, locationSearch, opts) => {
1223
+ let routeParams = {};
1224
+ let foundRoute = this.flatRoutes.find(route => {
1225
+ const matchedParams = matchPathname(this.basepath, trimPathRight(pathname), {
1226
+ to: route.fullPath,
1227
+ caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive
1226
1228
  });
1227
- let routeCursor = foundRoute || this.routesById['__root__'];
1228
- let matchedRoutes = [routeCursor];
1229
- // let includingLayouts = true
1230
- while (routeCursor?.parentRoute) {
1231
- routeCursor = routeCursor.parentRoute;
1232
- if (routeCursor) matchedRoutes.unshift(routeCursor);
1229
+ if (matchedParams) {
1230
+ routeParams = matchedParams;
1231
+ return true;
1233
1232
  }
1233
+ return false;
1234
+ });
1235
+ let routeCursor = foundRoute || this.routesById['__root__'];
1236
+ let matchedRoutes = [routeCursor];
1237
+ // let includingLayouts = true
1238
+ while (routeCursor?.parentRoute) {
1239
+ routeCursor = routeCursor.parentRoute;
1240
+ if (routeCursor) matchedRoutes.unshift(routeCursor);
1241
+ }
1234
1242
 
1235
- // Existing matches are matches that are already loaded along with
1236
- // pending matches that are still loading
1243
+ // Existing matches are matches that are already loaded along with
1244
+ // pending matches that are still loading
1237
1245
 
1238
- const parseErrors = matchedRoutes.map(route => {
1239
- let parsedParamsError;
1240
- if (route.options.parseParams) {
1241
- try {
1242
- const parsedParams = route.options.parseParams(routeParams);
1243
- // Add the parsed params to the accumulated params bag
1244
- Object.assign(routeParams, parsedParams);
1245
- } catch (err) {
1246
- parsedParamsError = new PathParamError(err.message, {
1247
- cause: err
1248
- });
1249
- if (opts?.throwOnError) {
1250
- throw parsedParamsError;
1251
- }
1252
- return parsedParamsError;
1246
+ const parseErrors = matchedRoutes.map(route => {
1247
+ let parsedParamsError;
1248
+ if (route.options.parseParams) {
1249
+ try {
1250
+ const parsedParams = route.options.parseParams(routeParams);
1251
+ // Add the parsed params to the accumulated params bag
1252
+ Object.assign(routeParams, parsedParams);
1253
+ } catch (err) {
1254
+ parsedParamsError = new PathParamError(err.message, {
1255
+ cause: err
1256
+ });
1257
+ if (opts?.throwOnError) {
1258
+ throw parsedParamsError;
1253
1259
  }
1260
+ return parsedParamsError;
1254
1261
  }
1255
- return;
1262
+ }
1263
+ return;
1264
+ });
1265
+ const matches = matchedRoutes.map((route, index) => {
1266
+ const interpolatedPath = interpolatePath(route.path, routeParams);
1267
+ const loaderContext = route.options.loaderContext ? route.options.loaderContext({
1268
+ search: locationSearch
1269
+ }) : undefined;
1270
+ const matchId = JSON.stringify([interpolatePath(route.id, routeParams, true), loaderContext].filter(d => d !== undefined), (key, value) => {
1271
+ if (typeof value === 'function') {
1272
+ console.info(route);
1273
+ invariant(false, `Cannot return functions and other non-serializable values from routeOptions.loaderContext! Please use routeOptions.beforeLoad to do this. Route info is logged above 👆`);
1274
+ }
1275
+ if (typeof value === 'object' && value !== null) {
1276
+ return Object.fromEntries(Object.keys(value).sort().map(key => [key, value[key]]));
1277
+ }
1278
+ return value;
1256
1279
  });
1257
- const matches = matchedRoutes.map((route, index) => {
1258
- const interpolatedPath = interpolatePath(route.path, routeParams);
1259
- const loaderContext = route.options.loaderContext ? route.options.loaderContext({
1260
- search: locationSearch
1261
- }) : undefined;
1262
- const matchId = JSON.stringify([interpolatePath(route.id, routeParams, true), loaderContext].filter(d => d !== undefined), (key, value) => {
1263
- if (typeof value === 'function') {
1264
- console.info(route);
1265
- invariant(false, `Cannot return functions and other non-serializable values from routeOptions.loaderContext! Please use routeOptions.beforeLoad to do this. Route info is logged above 👆`);
1266
- }
1267
- if (typeof value === 'object' && value !== null) {
1268
- return Object.fromEntries(Object.keys(value).sort().map(key => [key, value[key]]));
1269
- }
1270
- return value;
1271
- });
1272
1280
 
1273
- // Waste not, want not. If we already have a match for this route,
1274
- // reuse it. This is important for layout routes, which might stick
1275
- // around between navigation actions that only change leaf routes.
1276
- const existingMatch = this.getRouteMatch(matchId);
1277
- if (existingMatch) {
1281
+ // Waste not, want not. If we already have a match for this route,
1282
+ // reuse it. This is important for layout routes, which might stick
1283
+ // around between navigation actions that only change leaf routes.
1284
+ const existingMatch = this.getRouteMatch(matchId);
1285
+ if (existingMatch) {
1286
+ return {
1287
+ ...existingMatch
1288
+ };
1289
+ }
1290
+
1291
+ // Create a fresh route match
1292
+ const hasLoaders = !!(route.options.loader || componentTypes.some(d => route.options[d]?.preload));
1293
+ const routeMatch = {
1294
+ id: matchId,
1295
+ loaderContext,
1296
+ routeId: route.id,
1297
+ params: routeParams,
1298
+ pathname: joinPaths([this.basepath, interpolatedPath]),
1299
+ updatedAt: Date.now(),
1300
+ maxAge: -1,
1301
+ preloadMaxAge: -1,
1302
+ routeSearch: {},
1303
+ search: {},
1304
+ status: hasLoaders ? 'pending' : 'success',
1305
+ isFetching: false,
1306
+ invalid: false,
1307
+ error: undefined,
1308
+ paramsError: parseErrors[index],
1309
+ searchError: undefined,
1310
+ loaderData: undefined,
1311
+ loadPromise: Promise.resolve(),
1312
+ context: undefined,
1313
+ abortController: new AbortController(),
1314
+ fetchedAt: 0
1315
+ };
1316
+ return routeMatch;
1317
+ });
1318
+
1319
+ // Take each match and resolve its search params and context
1320
+ // This has to happen after the matches are created or found
1321
+ // so that we can use the parent match's search params and context
1322
+ matches.forEach((match, i) => {
1323
+ const parentMatch = matches[i - 1];
1324
+ const route = this.getRoute(match.routeId);
1325
+ const searchInfo = (() => {
1326
+ // Validate the search params and stabilize them
1327
+ const parentSearchInfo = {
1328
+ search: parentMatch?.search ?? locationSearch,
1329
+ routeSearch: parentMatch?.routeSearch ?? locationSearch
1330
+ };
1331
+ try {
1332
+ const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
1333
+ let routeSearch = validator?.(parentSearchInfo.search) ?? {};
1334
+ let search = {
1335
+ ...parentSearchInfo.search,
1336
+ ...routeSearch
1337
+ };
1338
+ routeSearch = replaceEqualDeep(match.routeSearch, routeSearch);
1339
+ search = replaceEqualDeep(match.search, search);
1278
1340
  return {
1279
- ...existingMatch
1341
+ routeSearch,
1342
+ search,
1343
+ searchDidChange: match.routeSearch !== routeSearch
1280
1344
  };
1345
+ } catch (err) {
1346
+ match.searchError = new SearchParamError(err.message, {
1347
+ cause: err
1348
+ });
1349
+ if (opts?.throwOnError) {
1350
+ throw match.searchError;
1351
+ }
1352
+ return parentSearchInfo;
1281
1353
  }
1282
-
1283
- // Create a fresh route match
1284
- const hasLoaders = !!(route.options.loader || componentTypes.some(d => route.options[d]?.preload));
1285
- const routeMatch = {
1286
- id: matchId,
1287
- loaderContext,
1288
- routeId: route.id,
1289
- params: routeParams,
1290
- pathname: joinPaths([this.basepath, interpolatedPath]),
1291
- updatedAt: Date.now(),
1292
- maxAge: -1,
1293
- preloadMaxAge: -1,
1294
- routeSearch: {},
1295
- search: {},
1296
- status: hasLoaders ? 'pending' : 'success',
1297
- isFetching: false,
1298
- invalid: false,
1299
- error: undefined,
1300
- paramsError: parseErrors[index],
1301
- searchError: undefined,
1302
- loaderData: undefined,
1303
- loadPromise: Promise.resolve(),
1304
- context: undefined,
1305
- abortController: new AbortController(),
1306
- fetchedAt: 0
1307
- };
1308
- return routeMatch;
1354
+ })();
1355
+ Object.assign(match, searchInfo);
1356
+ });
1357
+ return matches;
1358
+ };
1359
+ loadMatches = async (matchIds, opts) => {
1360
+ const getFreshMatches = () => matchIds.map(d => this.getRouteMatch(d));
1361
+ if (!opts?.preload) {
1362
+ getFreshMatches().forEach(match => {
1363
+ // Update each match with its latest route data
1364
+ this.setRouteMatch(match.id, s => ({
1365
+ ...s,
1366
+ routeSearch: match.routeSearch,
1367
+ search: match.search,
1368
+ context: match.context,
1369
+ error: match.error,
1370
+ paramsError: match.paramsError,
1371
+ searchError: match.searchError,
1372
+ params: match.params,
1373
+ preloadMaxAge: 0
1374
+ }));
1309
1375
  });
1376
+ }
1377
+ let firstBadMatchIndex;
1310
1378
 
1311
- // Take each match and resolve its search params and context
1312
- // This has to happen after the matches are created or found
1313
- // so that we can use the parent match's search params and context
1314
- matches.forEach((match, i) => {
1315
- const parentMatch = matches[i - 1];
1379
+ // Check each match middleware to see if the route can be accessed
1380
+ try {
1381
+ for (const [index, match] of getFreshMatches().entries()) {
1382
+ const parentMatch = getFreshMatches()[index - 1];
1316
1383
  const route = this.getRoute(match.routeId);
1317
- const searchInfo = (() => {
1318
- // Validate the search params and stabilize them
1319
- const parentSearchInfo = {
1320
- search: parentMatch?.search ?? locationSearch,
1321
- routeSearch: parentMatch?.routeSearch ?? locationSearch
1322
- };
1384
+ const handleError = (err, code) => {
1385
+ err.routerCode = code;
1386
+ firstBadMatchIndex = firstBadMatchIndex ?? index;
1387
+ if (isRedirect(err)) {
1388
+ throw err;
1389
+ }
1323
1390
  try {
1324
- const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
1325
- let routeSearch = validator?.(parentSearchInfo.search) ?? {};
1326
- let search = {
1327
- ...parentSearchInfo.search,
1328
- ...routeSearch
1329
- };
1330
- routeSearch = replaceEqualDeep(match.routeSearch, routeSearch);
1331
- search = replaceEqualDeep(match.search, search);
1332
- return {
1333
- routeSearch,
1334
- search,
1335
- searchDidChange: match.routeSearch !== routeSearch
1336
- };
1337
- } catch (err) {
1338
- match.searchError = new SearchParamError(err.message, {
1339
- cause: err
1340
- });
1341
- if (opts?.throwOnError) {
1342
- throw match.searchError;
1391
+ route.options.onError?.(err);
1392
+ } catch (errorHandlerErr) {
1393
+ err = errorHandlerErr;
1394
+ if (isRedirect(errorHandlerErr)) {
1395
+ throw errorHandlerErr;
1343
1396
  }
1344
- return parentSearchInfo;
1345
1397
  }
1346
- })();
1347
- Object.assign(match, searchInfo);
1348
- });
1349
- return matches;
1350
- };
1351
- loadMatches = async (matchIds, opts) => {
1352
- const getFreshMatches = () => matchIds.map(d => this.getRouteMatch(d));
1353
- if (!opts?.preload) {
1354
- getFreshMatches().forEach(match => {
1355
- // Update each match with its latest route data
1356
1398
  this.setRouteMatch(match.id, s => ({
1357
1399
  ...s,
1358
- routeSearch: match.routeSearch,
1359
- search: match.search,
1360
- context: match.context,
1361
- error: match.error,
1362
- paramsError: match.paramsError,
1363
- searchError: match.searchError,
1364
- params: match.params,
1365
- preloadMaxAge: 0
1400
+ error: err,
1401
+ status: 'error',
1402
+ updatedAt: Date.now()
1366
1403
  }));
1367
- });
1368
- }
1369
- let firstBadMatchIndex;
1370
-
1371
- // Check each match middleware to see if the route can be accessed
1372
- try {
1373
- for (const [index, match] of getFreshMatches().entries()) {
1374
- const parentMatch = getFreshMatches()[index - 1];
1375
- const route = this.getRoute(match.routeId);
1376
- const handleError = (err, code) => {
1377
- err.routerCode = code;
1378
- firstBadMatchIndex = firstBadMatchIndex ?? index;
1379
- if (isRedirect(err)) {
1380
- throw err;
1381
- }
1382
- try {
1383
- route.options.onError?.(err);
1384
- } catch (errorHandlerErr) {
1385
- err = errorHandlerErr;
1386
- if (isRedirect(errorHandlerErr)) {
1387
- throw errorHandlerErr;
1388
- }
1404
+ };
1405
+ if (match.paramsError) {
1406
+ handleError(match.paramsError, 'PARSE_PARAMS');
1407
+ }
1408
+ if (match.searchError) {
1409
+ handleError(match.searchError, 'VALIDATE_SEARCH');
1410
+ }
1411
+ let didError = false;
1412
+ const parentContext = parentMatch?.context ?? this?.options.context ?? {};
1413
+ try {
1414
+ const beforeLoadContext = (await route.options.beforeLoad?.({
1415
+ abortController: match.abortController,
1416
+ params: match.params,
1417
+ preload: !!opts?.preload,
1418
+ context: {
1419
+ ...parentContext,
1420
+ ...match.loaderContext
1389
1421
  }
1390
- this.setRouteMatch(match.id, s => ({
1391
- ...s,
1392
- error: err,
1393
- status: 'error',
1394
- updatedAt: Date.now()
1395
- }));
1422
+ })) ?? {};
1423
+ const context = {
1424
+ ...parentContext,
1425
+ ...match.loaderContext,
1426
+ ...beforeLoadContext
1396
1427
  };
1397
- if (match.paramsError) {
1398
- handleError(match.paramsError, 'PARSE_PARAMS');
1399
- }
1400
- if (match.searchError) {
1401
- handleError(match.searchError, 'VALIDATE_SEARCH');
1402
- }
1403
- let didError = false;
1404
- const parentContext = parentMatch?.context ?? this?.options.context ?? {};
1405
- try {
1406
- const beforeLoadContext = (await route.options.beforeLoad?.({
1407
- abortController: match.abortController,
1408
- params: match.params,
1409
- preload: !!opts?.preload,
1410
- context: {
1411
- ...parentContext,
1412
- ...match.loaderContext
1413
- }
1414
- })) ?? {};
1415
- const context = {
1416
- ...parentContext,
1417
- ...match.loaderContext,
1418
- ...beforeLoadContext
1419
- };
1420
- this.setRouteMatch(match.id, s => ({
1421
- ...s,
1422
- context: replaceEqualDeep(s.context, context)
1423
- }));
1424
- } catch (err) {
1425
- handleError(err, 'BEFORE_LOAD');
1426
- didError = true;
1427
- }
1428
-
1429
- // If we errored, do not run the next matches' middleware
1430
- if (didError) {
1431
- break;
1432
- }
1428
+ this.setRouteMatch(match.id, s => ({
1429
+ ...s,
1430
+ context: replaceEqualDeep(s.context, context)
1431
+ }));
1432
+ } catch (err) {
1433
+ handleError(err, 'BEFORE_LOAD');
1434
+ didError = true;
1433
1435
  }
1434
- } catch (err) {
1435
- if (isRedirect(err)) {
1436
- if (!opts?.preload) this.navigate(err);
1437
- return;
1436
+
1437
+ // If we errored, do not run the next matches' middleware
1438
+ if (didError) {
1439
+ break;
1438
1440
  }
1439
- throw err;
1440
1441
  }
1441
- const validResolvedMatches = getFreshMatches().slice(0, firstBadMatchIndex);
1442
- const matchPromises = [];
1443
- validResolvedMatches.forEach((match, index) => {
1444
- matchPromises.push((async () => {
1445
- const parentMatchPromise = matchPromises[index - 1];
1446
- const route = this.getRoute(match.routeId);
1447
- if (match.isFetching || match.status === 'success' && !isMatchInvalid(match, {
1448
- preload: opts?.preload
1449
- })) {
1450
- return this.getRouteMatch(match.id)?.loadPromise;
1442
+ } catch (err) {
1443
+ if (isRedirect(err)) {
1444
+ if (!opts?.preload) this.navigate(err);
1445
+ return;
1446
+ }
1447
+ throw err;
1448
+ }
1449
+ const validResolvedMatches = getFreshMatches().slice(0, firstBadMatchIndex);
1450
+ const matchPromises = [];
1451
+ validResolvedMatches.forEach((match, index) => {
1452
+ matchPromises.push((async () => {
1453
+ const parentMatchPromise = matchPromises[index - 1];
1454
+ const route = this.getRoute(match.routeId);
1455
+ if (match.isFetching || match.status === 'success' && !isMatchInvalid(match, {
1456
+ preload: opts?.preload
1457
+ })) {
1458
+ return this.getRouteMatch(match.id)?.loadPromise;
1459
+ }
1460
+ const fetchedAt = Date.now();
1461
+ const checkLatest = () => {
1462
+ const latest = this.getRouteMatch(match.id);
1463
+ return latest && latest.fetchedAt !== fetchedAt ? latest.loadPromise : undefined;
1464
+ };
1465
+ const handleIfRedirect = err => {
1466
+ if (isRedirect(err)) {
1467
+ if (!opts?.preload) {
1468
+ this.navigate(err);
1469
+ }
1470
+ return true;
1451
1471
  }
1452
- const fetchedAt = Date.now();
1453
- const checkLatest = () => {
1454
- const latest = this.getRouteMatch(match.id);
1455
- return latest && latest.fetchedAt !== fetchedAt ? latest.loadPromise : undefined;
1456
- };
1457
- const handleIfRedirect = err => {
1458
- if (isRedirect(err)) {
1459
- if (!opts?.preload) {
1460
- this.navigate(err);
1472
+ return false;
1473
+ };
1474
+ const load = async () => {
1475
+ let latestPromise;
1476
+ try {
1477
+ const componentsPromise = Promise.all(componentTypes.map(async type => {
1478
+ const component = route.options[type];
1479
+ if (component?.preload) {
1480
+ await component.preload();
1461
1481
  }
1462
- return true;
1463
- }
1464
- return false;
1465
- };
1466
- const load = async () => {
1467
- let latestPromise;
1482
+ }));
1483
+ const loaderPromise = route.options.loader?.({
1484
+ params: match.params,
1485
+ preload: !!opts?.preload,
1486
+ parentMatchPromise,
1487
+ abortController: match.abortController,
1488
+ context: match.context
1489
+ });
1490
+ const [_, loader] = await Promise.all([componentsPromise, loaderPromise]);
1491
+ if (latestPromise = checkLatest()) return await latestPromise;
1492
+ this.setRouteMatchData(match.id, () => loader, opts);
1493
+ } catch (error) {
1494
+ if (latestPromise = checkLatest()) return await latestPromise;
1495
+ if (handleIfRedirect(error)) return;
1468
1496
  try {
1469
- const componentsPromise = Promise.all(componentTypes.map(async type => {
1470
- const component = route.options[type];
1471
- if (component?.preload) {
1472
- await component.preload();
1473
- }
1474
- }));
1475
- const loaderPromise = route.options.loader?.({
1476
- params: match.params,
1477
- preload: !!opts?.preload,
1478
- parentMatchPromise,
1479
- abortController: match.abortController,
1480
- context: match.context
1481
- });
1482
- const [_, loader] = await Promise.all([componentsPromise, loaderPromise]);
1483
- if (latestPromise = checkLatest()) return await latestPromise;
1484
- this.setRouteMatchData(match.id, () => loader, opts);
1485
- } catch (error) {
1486
- if (latestPromise = checkLatest()) return await latestPromise;
1487
- if (handleIfRedirect(error)) return;
1488
- try {
1489
- route.options.onError?.(error);
1490
- } catch (onErrorError) {
1491
- error = onErrorError;
1492
- if (handleIfRedirect(onErrorError)) return;
1493
- }
1494
- this.setRouteMatch(match.id, s => ({
1495
- ...s,
1496
- error,
1497
- status: 'error',
1498
- isFetching: false,
1499
- updatedAt: Date.now()
1500
- }));
1497
+ route.options.onError?.(error);
1498
+ } catch (onErrorError) {
1499
+ error = onErrorError;
1500
+ if (handleIfRedirect(onErrorError)) return;
1501
1501
  }
1502
- };
1503
- let loadPromise;
1504
- this.__store.batch(() => {
1505
1502
  this.setRouteMatch(match.id, s => ({
1506
1503
  ...s,
1507
- // status: s.status !== 'success' ? 'pending' : s.status,
1508
- isFetching: true,
1509
- fetchedAt,
1510
- invalid: false
1511
- }));
1512
- loadPromise = load();
1513
- this.setRouteMatch(match.id, s => ({
1514
- ...s,
1515
- loadPromise
1504
+ error,
1505
+ status: 'error',
1506
+ isFetching: false,
1507
+ updatedAt: Date.now()
1516
1508
  }));
1517
- });
1518
- await loadPromise;
1519
- })());
1520
- });
1521
- await Promise.all(matchPromises);
1522
- };
1523
- resolvePath = (from, path) => {
1524
- return resolvePath(this.basepath, from, cleanPath(path));
1525
- };
1526
- navigate = async ({
1527
- from,
1528
- to = '',
1529
- ...rest
1530
- }) => {
1531
- // If this link simply reloads the current route,
1532
- // make sure it has a new key so it will trigger a data refresh
1533
-
1534
- // If this `to` is a valid external URL, return
1535
- // null for LinkUtils
1536
- const toString = String(to);
1537
- const fromString = typeof from === 'undefined' ? from : String(from);
1538
- let isExternal;
1539
- try {
1540
- new URL(`${toString}`);
1541
- isExternal = true;
1542
- } catch (e) {}
1543
- invariant(!isExternal, 'Attempting to navigate to external url with this.navigate!');
1544
- return this.#buildAndCommitLocation({
1545
- ...rest,
1546
- from: fromString,
1547
- to: toString
1548
- });
1509
+ }
1510
+ };
1511
+ let loadPromise;
1512
+ this.__store.batch(() => {
1513
+ this.setRouteMatch(match.id, s => ({
1514
+ ...s,
1515
+ // status: s.status !== 'success' ? 'pending' : s.status,
1516
+ isFetching: true,
1517
+ fetchedAt,
1518
+ invalid: false
1519
+ }));
1520
+ loadPromise = load();
1521
+ this.setRouteMatch(match.id, s => ({
1522
+ ...s,
1523
+ loadPromise
1524
+ }));
1525
+ });
1526
+ await loadPromise;
1527
+ })());
1528
+ });
1529
+ await Promise.all(matchPromises);
1530
+ };
1531
+ resolvePath = (from, path) => {
1532
+ return resolvePath(this.basepath, from, cleanPath(path));
1533
+ };
1534
+ navigate = async ({
1535
+ from,
1536
+ to = '',
1537
+ ...rest
1538
+ }) => {
1539
+ // If this link simply reloads the current route,
1540
+ // make sure it has a new key so it will trigger a data refresh
1541
+
1542
+ // If this `to` is a valid external URL, return
1543
+ // null for LinkUtils
1544
+ const toString = String(to);
1545
+ const fromString = typeof from === 'undefined' ? from : String(from);
1546
+ let isExternal;
1547
+ try {
1548
+ new URL(`${toString}`);
1549
+ isExternal = true;
1550
+ } catch (e) {}
1551
+ invariant(!isExternal, 'Attempting to navigate to external url with this.navigate!');
1552
+ return this.#buildAndCommitLocation({
1553
+ ...rest,
1554
+ from: fromString,
1555
+ to: toString
1556
+ });
1557
+ };
1558
+ matchRoute = (location, opts) => {
1559
+ location = {
1560
+ ...location,
1561
+ to: location.to ? this.resolvePath(location.from ?? '', location.to) : undefined
1549
1562
  };
1550
- matchRoute = (location, opts) => {
1551
- location = {
1552
- ...location,
1553
- to: location.to ? this.resolvePath(location.from ?? '', location.to) : undefined
1554
- };
1555
- const next = this.buildLocation(location);
1556
- if (opts?.pending && this.state.status !== 'pending') {
1557
- return false;
1558
- }
1559
- const baseLocation = opts?.pending ? this.state.location : this.state.resolvedLocation;
1560
- if (!baseLocation) {
1561
- return false;
1562
- }
1563
- const match = matchPathname(this.basepath, baseLocation.pathname, {
1564
- ...opts,
1565
- to: next.pathname
1566
- });
1567
- if (!match) {
1568
- return false;
1569
- }
1570
- if (opts?.includeSearch ?? true) {
1571
- return partialDeepEqual(baseLocation.search, next.search) ? match : false;
1563
+ const next = this.buildLocation(location);
1564
+ if (opts?.pending && this.state.status !== 'pending') {
1565
+ return false;
1566
+ }
1567
+ const baseLocation = opts?.pending ? this.state.location : this.state.resolvedLocation;
1568
+ if (!baseLocation) {
1569
+ return false;
1570
+ }
1571
+ const match = matchPathname(this.basepath, baseLocation.pathname, {
1572
+ ...opts,
1573
+ to: next.pathname
1574
+ });
1575
+ if (!match) {
1576
+ return false;
1577
+ }
1578
+ if (opts?.includeSearch ?? true) {
1579
+ return partialDeepEqual(baseLocation.search, next.search) ? match : false;
1580
+ }
1581
+ return match;
1582
+ };
1583
+ buildLink = dest => {
1584
+ // If this link simply reloads the current route,
1585
+ // make sure it has a new key so it will trigger a data refresh
1586
+
1587
+ // If this `to` is a valid external URL, return
1588
+ // null for LinkUtils
1589
+
1590
+ const {
1591
+ to,
1592
+ preload: userPreload,
1593
+ preloadDelay: userPreloadDelay,
1594
+ activeOptions,
1595
+ disabled,
1596
+ target,
1597
+ replace,
1598
+ resetScroll
1599
+ } = dest;
1600
+ try {
1601
+ new URL(`${to}`);
1602
+ return {
1603
+ type: 'external',
1604
+ href: to
1605
+ };
1606
+ } catch (e) {}
1607
+ const nextOpts = dest;
1608
+ const next = this.buildLocation(nextOpts);
1609
+ const preload = userPreload ?? this.options.defaultPreload;
1610
+ const preloadDelay = userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0;
1611
+
1612
+ // Compare path/hash for matches
1613
+ const currentPathSplit = this.state.location.pathname.split('/');
1614
+ const nextPathSplit = next.pathname.split('/');
1615
+ const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
1616
+ // Combine the matches based on user options
1617
+ const pathTest = activeOptions?.exact ? this.state.location.pathname === next.pathname : pathIsFuzzyEqual;
1618
+ const hashTest = activeOptions?.includeHash ? this.state.location.hash === next.hash : true;
1619
+ const searchTest = activeOptions?.includeSearch ?? true ? partialDeepEqual(this.state.location.search, next.search) : true;
1620
+
1621
+ // The final "active" test
1622
+ const isActive = pathTest && hashTest && searchTest;
1623
+
1624
+ // The click handler
1625
+ const handleClick = e => {
1626
+ if (!disabled && !isCtrlEvent(e) && !e.defaultPrevented && (!target || target === '_self') && e.button === 0) {
1627
+ e.preventDefault();
1628
+
1629
+ // All is well? Navigate!
1630
+ this.#commitLocation({
1631
+ ...next,
1632
+ replace,
1633
+ resetScroll
1634
+ });
1572
1635
  }
1573
- return match;
1574
1636
  };
1575
- buildLink = dest => {
1576
- // If this link simply reloads the current route,
1577
- // make sure it has a new key so it will trigger a data refresh
1578
-
1579
- // If this `to` is a valid external URL, return
1580
- // null for LinkUtils
1581
-
1582
- const {
1583
- to,
1584
- preload: userPreload,
1585
- preloadDelay: userPreloadDelay,
1586
- activeOptions,
1587
- disabled,
1588
- target,
1589
- replace,
1590
- resetScroll
1591
- } = dest;
1592
- try {
1593
- new URL(`${to}`);
1594
- return {
1595
- type: 'external',
1596
- href: to
1597
- };
1598
- } catch (e) {}
1599
- const nextOpts = dest;
1600
- const next = this.buildLocation(nextOpts);
1601
- const preload = userPreload ?? this.options.defaultPreload;
1602
- const preloadDelay = userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0;
1603
-
1604
- // Compare path/hash for matches
1605
- const currentPathSplit = this.state.location.pathname.split('/');
1606
- const nextPathSplit = next.pathname.split('/');
1607
- const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
1608
- // Combine the matches based on user options
1609
- const pathTest = activeOptions?.exact ? this.state.location.pathname === next.pathname : pathIsFuzzyEqual;
1610
- const hashTest = activeOptions?.includeHash ? this.state.location.hash === next.hash : true;
1611
- const searchTest = activeOptions?.includeSearch ?? true ? partialDeepEqual(this.state.location.search, next.search) : true;
1612
-
1613
- // The final "active" test
1614
- const isActive = pathTest && hashTest && searchTest;
1615
-
1616
- // The click handler
1617
- const handleClick = e => {
1618
- if (!disabled && !isCtrlEvent(e) && !e.defaultPrevented && (!target || target === '_self') && e.button === 0) {
1619
- e.preventDefault();
1620
-
1621
- // All is well? Navigate!
1622
- this.#commitLocation({
1623
- ...next,
1624
- replace,
1625
- resetScroll
1626
- });
1627
- }
1628
- };
1629
1637
 
1630
- // The click handler
1631
- const handleFocus = e => {
1632
- if (preload) {
1633
- this.preloadRoute(nextOpts).catch(err => {
1634
- console.warn(err);
1635
- console.warn(preloadWarning);
1636
- });
1637
- }
1638
- };
1639
- const handleTouchStart = e => {
1638
+ // The click handler
1639
+ const handleFocus = e => {
1640
+ if (preload) {
1640
1641
  this.preloadRoute(nextOpts).catch(err => {
1641
1642
  console.warn(err);
1642
1643
  console.warn(preloadWarning);
1643
1644
  });
1644
- };
1645
- const handleEnter = e => {
1646
- const target = e.target || {};
1647
- if (preload) {
1648
- if (target.preloadTimeout) {
1649
- return;
1650
- }
1651
- target.preloadTimeout = setTimeout(() => {
1652
- target.preloadTimeout = null;
1653
- this.preloadRoute(nextOpts).catch(err => {
1654
- console.warn(err);
1655
- console.warn(preloadWarning);
1656
- });
1657
- }, preloadDelay);
1658
- }
1659
- };
1660
- const handleLeave = e => {
1661
- const target = e.target || {};
1645
+ }
1646
+ };
1647
+ const handleTouchStart = e => {
1648
+ this.preloadRoute(nextOpts).catch(err => {
1649
+ console.warn(err);
1650
+ console.warn(preloadWarning);
1651
+ });
1652
+ };
1653
+ const handleEnter = e => {
1654
+ const target = e.target || {};
1655
+ if (preload) {
1662
1656
  if (target.preloadTimeout) {
1663
- clearTimeout(target.preloadTimeout);
1664
- target.preloadTimeout = null;
1657
+ return;
1665
1658
  }
1666
- };
1667
- return {
1668
- type: 'internal',
1669
- next,
1670
- handleFocus,
1671
- handleClick,
1672
- handleEnter,
1673
- handleLeave,
1674
- handleTouchStart,
1675
- isActive,
1676
- disabled
1677
- };
1659
+ target.preloadTimeout = setTimeout(() => {
1660
+ target.preloadTimeout = null;
1661
+ this.preloadRoute(nextOpts).catch(err => {
1662
+ console.warn(err);
1663
+ console.warn(preloadWarning);
1664
+ });
1665
+ }, preloadDelay);
1666
+ }
1678
1667
  };
1679
- dehydrate = () => {
1680
- return {
1681
- state: {
1682
- matchIds: this.state.matchIds,
1683
- dehydratedMatches: this.state.matches.map(d => pick(d, ['fetchedAt', 'invalid', 'preloadMaxAge', 'maxAge', 'id', 'loaderData', 'status', 'updatedAt']))
1684
- }
1685
- };
1668
+ const handleLeave = e => {
1669
+ const target = e.target || {};
1670
+ if (target.preloadTimeout) {
1671
+ clearTimeout(target.preloadTimeout);
1672
+ target.preloadTimeout = null;
1673
+ }
1674
+ };
1675
+ return {
1676
+ type: 'internal',
1677
+ next,
1678
+ handleFocus,
1679
+ handleClick,
1680
+ handleEnter,
1681
+ handleLeave,
1682
+ handleTouchStart,
1683
+ isActive,
1684
+ disabled
1686
1685
  };
1687
- hydrate = async __do_not_use_server_ctx => {
1688
- let _ctx = __do_not_use_server_ctx;
1689
- // Client hydrates from window
1690
- if (typeof document !== 'undefined') {
1691
- _ctx = window.__TSR_DEHYDRATED__;
1686
+ };
1687
+ dehydrate = () => {
1688
+ return {
1689
+ state: {
1690
+ matchIds: this.state.matchIds,
1691
+ dehydratedMatches: this.state.matches.map(d => pick(d, ['fetchedAt', 'invalid', 'preloadMaxAge', 'maxAge', 'id', 'loaderData', 'status', 'updatedAt']))
1692
1692
  }
1693
- invariant(_ctx, 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?');
1694
- const ctx = _ctx;
1695
- this.dehydratedData = ctx.payload;
1696
- this.options.hydrate?.(ctx.payload);
1697
- const dehydratedState = ctx.router.state;
1698
- let matches = this.matchRoutes(this.state.location.pathname, this.state.location.search).map(match => {
1699
- const dehydratedMatch = dehydratedState.dehydratedMatches.find(d => d.id === match.id);
1700
- invariant(dehydratedMatch, `Could not find a client-side match for dehydrated match with id: ${match.id}!`);
1701
- if (dehydratedMatch) {
1702
- return {
1703
- ...match,
1704
- ...dehydratedMatch
1705
- };
1706
- }
1707
- return match;
1708
- });
1709
- this.__store.setState(s => {
1693
+ };
1694
+ };
1695
+ hydrate = async __do_not_use_server_ctx => {
1696
+ let _ctx = __do_not_use_server_ctx;
1697
+ // Client hydrates from window
1698
+ if (typeof document !== 'undefined') {
1699
+ _ctx = window.__TSR_DEHYDRATED__;
1700
+ }
1701
+ invariant(_ctx, 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?');
1702
+ const ctx = _ctx;
1703
+ this.dehydratedData = ctx.payload;
1704
+ this.options.hydrate?.(ctx.payload);
1705
+ const dehydratedState = ctx.router.state;
1706
+ let matches = this.matchRoutes(this.state.location.pathname, this.state.location.search).map(match => {
1707
+ const dehydratedMatch = dehydratedState.dehydratedMatches.find(d => d.id === match.id);
1708
+ invariant(dehydratedMatch, `Could not find a client-side match for dehydrated match with id: ${match.id}!`);
1709
+ if (dehydratedMatch) {
1710
1710
  return {
1711
- ...s,
1712
- matchIds: dehydratedState.matchIds,
1713
- matches,
1714
- matchesById: this.#mergeMatches(s.matchesById, matches)
1711
+ ...match,
1712
+ ...dehydratedMatch
1715
1713
  };
1716
- });
1717
- };
1718
- injectedHtml = [];
1719
- injectHtml = async html => {
1720
- this.injectedHtml.push(html);
1721
- };
1722
- dehydrateData = (key, getData) => {
1723
- if (typeof document === 'undefined') {
1724
- const strKey = typeof key === 'string' ? key : JSON.stringify(key);
1725
- this.injectHtml(async () => {
1726
- const id = `__TSR_DEHYDRATED__${strKey}`;
1727
- const data = typeof getData === 'function' ? await getData() : getData;
1728
- return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(strKey)}"] = ${JSON.stringify(data)}
1714
+ }
1715
+ return match;
1716
+ });
1717
+ this.__store.setState(s => {
1718
+ return {
1719
+ ...s,
1720
+ matchIds: dehydratedState.matchIds,
1721
+ matches,
1722
+ matchesById: this.#mergeMatches(s.matchesById, matches)
1723
+ };
1724
+ });
1725
+ };
1726
+ injectedHtml = [];
1727
+ injectHtml = async html => {
1728
+ this.injectedHtml.push(html);
1729
+ };
1730
+ dehydrateData = (key, getData) => {
1731
+ if (typeof document === 'undefined') {
1732
+ const strKey = typeof key === 'string' ? key : JSON.stringify(key);
1733
+ this.injectHtml(async () => {
1734
+ const id = `__TSR_DEHYDRATED__${strKey}`;
1735
+ const data = typeof getData === 'function' ? await getData() : getData;
1736
+ return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(strKey)}"] = ${JSON.stringify(data)}
1729
1737
  ;(() => {
1730
1738
  var el = document.getElementById('${id}')
1731
1739
  el.parentElement.removeChild(el)
1732
1740
  })()
1733
1741
  </script>`;
1742
+ });
1743
+ return () => this.hydrateData(key);
1744
+ }
1745
+ return () => undefined;
1746
+ };
1747
+ hydrateData = key => {
1748
+ if (typeof document !== 'undefined') {
1749
+ const strKey = typeof key === 'string' ? key : JSON.stringify(key);
1750
+ return window[`__TSR_DEHYDRATED__${strKey}`];
1751
+ }
1752
+ return undefined;
1753
+ };
1754
+
1755
+ // resolveMatchPromise = (matchId: string, key: string, value: any) => {
1756
+ // this.state.matches
1757
+ // .find((d) => d.id === matchId)
1758
+ // ?.__promisesByKey[key]?.resolve(value)
1759
+ // }
1760
+
1761
+ #processRoutes = routeTree => {
1762
+ this.routeTree = routeTree;
1763
+ this.routesById = {};
1764
+ this.routesByPath = {};
1765
+ this.flatRoutes = [];
1766
+ const recurseRoutes = routes => {
1767
+ routes.forEach((route, i) => {
1768
+ route.init({
1769
+ originalIndex: i,
1770
+ router: this
1734
1771
  });
1735
- return () => this.hydrateData(key);
1736
- }
1737
- return () => undefined;
1772
+ const existingRoute = this.routesById[route.id];
1773
+ invariant(!existingRoute, `Duplicate routes found with id: ${String(route.id)}`);
1774
+ this.routesById[route.id] = route;
1775
+ if (!route.isRoot && route.path) {
1776
+ const trimmedFullPath = trimPathRight(route.fullPath);
1777
+ if (!this.routesByPath[trimmedFullPath] || route.fullPath.endsWith('/')) {
1778
+ this.routesByPath[trimmedFullPath] = route;
1779
+ }
1780
+ }
1781
+ const children = route.children;
1782
+ if (children?.length) {
1783
+ recurseRoutes(children);
1784
+ }
1785
+ });
1738
1786
  };
1739
- hydrateData = key => {
1740
- if (typeof document !== 'undefined') {
1741
- const strKey = typeof key === 'string' ? key : JSON.stringify(key);
1742
- return window[`__TSR_DEHYDRATED__${strKey}`];
1787
+ recurseRoutes([routeTree]);
1788
+ this.flatRoutes = Object.values(this.routesByPath).map((d, i) => {
1789
+ const trimmed = trimPath(d.fullPath);
1790
+ const parsed = parsePathname(trimmed);
1791
+ while (parsed.length > 1 && parsed[0]?.value === '/') {
1792
+ parsed.shift();
1743
1793
  }
1744
- return undefined;
1745
- };
1746
-
1747
- // resolveMatchPromise = (matchId: string, key: string, value: any) => {
1748
- // this.state.matches
1749
- // .find((d) => d.id === matchId)
1750
- // ?.__promisesByKey[key]?.resolve(value)
1751
- // }
1752
-
1753
- #processRoutes = routeTree => {
1754
- this.routeTree = routeTree;
1755
- this.routesById = {};
1756
- this.routesByPath = {};
1757
- this.flatRoutes = [];
1758
- const recurseRoutes = routes => {
1759
- routes.forEach((route, i) => {
1760
- route.init({
1761
- originalIndex: i,
1762
- router: this
1763
- });
1764
- const existingRoute = this.routesById[route.id];
1765
- invariant(!existingRoute, `Duplicate routes found with id: ${String(route.id)}`);
1766
- this.routesById[route.id] = route;
1767
- if (!route.isRoot && route.path) {
1768
- const trimmedFullPath = trimPathRight(route.fullPath);
1769
- if (!this.routesByPath[trimmedFullPath] || route.fullPath.endsWith('/')) {
1770
- this.routesByPath[trimmedFullPath] = route;
1771
- }
1772
- }
1773
- const children = route.children;
1774
- if (children?.length) {
1775
- recurseRoutes(children);
1776
- }
1777
- });
1778
- };
1779
- recurseRoutes([routeTree]);
1780
- this.flatRoutes = Object.values(this.routesByPath).map((d, i) => {
1781
- const trimmed = trimPath(d.fullPath);
1782
- const parsed = parsePathname(trimmed);
1783
- while (parsed.length > 1 && parsed[0]?.value === '/') {
1784
- parsed.shift();
1794
+ const score = parsed.map(d => {
1795
+ if (d.type === 'param') {
1796
+ return 0.5;
1785
1797
  }
1786
- const score = parsed.map(d => {
1787
- if (d.type === 'param') {
1788
- return 0.5;
1789
- }
1790
- if (d.type === 'wildcard') {
1791
- return 0.25;
1792
- }
1793
- return 1;
1794
- });
1795
- return {
1796
- child: d,
1797
- trimmed,
1798
- parsed,
1799
- index: i,
1800
- score
1801
- };
1802
- }).sort((a, b) => {
1803
- let isIndex = a.trimmed === '/' ? 1 : b.trimmed === '/' ? -1 : 0;
1804
- if (isIndex !== 0) return isIndex;
1805
- const length = Math.min(a.score.length, b.score.length);
1806
-
1807
- // Sort by length of score
1808
- if (a.score.length !== b.score.length) {
1809
- return b.score.length - a.score.length;
1798
+ if (d.type === 'wildcard') {
1799
+ return 0.25;
1810
1800
  }
1801
+ return 1;
1802
+ });
1803
+ return {
1804
+ child: d,
1805
+ trimmed,
1806
+ parsed,
1807
+ index: i,
1808
+ score
1809
+ };
1810
+ }).sort((a, b) => {
1811
+ let isIndex = a.trimmed === '/' ? 1 : b.trimmed === '/' ? -1 : 0;
1812
+ if (isIndex !== 0) return isIndex;
1813
+ const length = Math.min(a.score.length, b.score.length);
1814
+
1815
+ // Sort by length of score
1816
+ if (a.score.length !== b.score.length) {
1817
+ return b.score.length - a.score.length;
1818
+ }
1811
1819
 
1812
- // Sort by min available score
1813
- for (let i = 0; i < length; i++) {
1814
- if (a.score[i] !== b.score[i]) {
1815
- return b.score[i] - a.score[i];
1816
- }
1820
+ // Sort by min available score
1821
+ for (let i = 0; i < length; i++) {
1822
+ if (a.score[i] !== b.score[i]) {
1823
+ return b.score[i] - a.score[i];
1817
1824
  }
1825
+ }
1818
1826
 
1819
- // Sort by min available parsed value
1820
- for (let i = 0; i < length; i++) {
1821
- if (a.parsed[i].value !== b.parsed[i].value) {
1822
- return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
1823
- }
1827
+ // Sort by min available parsed value
1828
+ for (let i = 0; i < length; i++) {
1829
+ if (a.parsed[i].value !== b.parsed[i].value) {
1830
+ return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
1824
1831
  }
1832
+ }
1825
1833
 
1826
- // Sort by length of trimmed full path
1827
- if (a.trimmed !== b.trimmed) {
1828
- return a.trimmed > b.trimmed ? 1 : -1;
1829
- }
1834
+ // Sort by length of trimmed full path
1835
+ if (a.trimmed !== b.trimmed) {
1836
+ return a.trimmed > b.trimmed ? 1 : -1;
1837
+ }
1830
1838
 
1831
- // Sort by original index
1832
- return a.index - b.index;
1833
- }).map((d, i) => {
1834
- d.child.rank = i;
1835
- return d.child;
1836
- });
1839
+ // Sort by original index
1840
+ return a.index - b.index;
1841
+ }).map((d, i) => {
1842
+ d.child.rank = i;
1843
+ return d.child;
1844
+ });
1845
+ };
1846
+ #parseLocation = previousLocation => {
1847
+ const parse = ({
1848
+ pathname,
1849
+ search,
1850
+ hash,
1851
+ state
1852
+ }) => {
1853
+ const parsedSearch = this.options.parseSearch(search);
1854
+ return {
1855
+ pathname: pathname,
1856
+ searchStr: search,
1857
+ search: replaceEqualDeep(previousLocation?.search, parsedSearch),
1858
+ hash: hash.split('#').reverse()[0] ?? '',
1859
+ href: `${pathname}${search}${hash}`,
1860
+ state: replaceEqualDeep(previousLocation?.state, state)
1861
+ };
1837
1862
  };
1838
- #parseLocation = previousLocation => {
1839
- const parse = ({
1863
+ const location = parse(this.history.location);
1864
+ let {
1865
+ __tempLocation,
1866
+ __tempKey
1867
+ } = location.state;
1868
+ if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
1869
+ // Sync up the location keys
1870
+ const parsedTempLocation = parse(__tempLocation);
1871
+ parsedTempLocation.state.key = location.state.key;
1872
+ delete parsedTempLocation.state.__tempLocation;
1873
+ return {
1874
+ ...parsedTempLocation,
1875
+ maskedLocation: location
1876
+ };
1877
+ }
1878
+ return location;
1879
+ };
1880
+ buildLocation = (opts = {}) => {
1881
+ const build = (dest = {}, matches) => {
1882
+ const from = this.state.location;
1883
+ const fromPathname = dest.from ?? from.pathname;
1884
+ let pathname = resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? ''}`);
1885
+ const fromMatches = this.matchRoutes(fromPathname, from.search);
1886
+ const prevParams = {
1887
+ ...last(fromMatches)?.params
1888
+ };
1889
+ let nextParams = (dest.params ?? true) === true ? prevParams : functionalUpdate(dest.params, prevParams);
1890
+ if (nextParams) {
1891
+ matches?.map(d => this.getRoute(d.routeId).options.stringifyParams).filter(Boolean).forEach(fn => {
1892
+ nextParams = {
1893
+ ...nextParams,
1894
+ ...fn(nextParams)
1895
+ };
1896
+ });
1897
+ }
1898
+ pathname = interpolatePath(pathname, nextParams ?? {});
1899
+ const preSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
1900
+ const postSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
1901
+
1902
+ // Pre filters first
1903
+ const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), from.search) : from.search;
1904
+
1905
+ // Then the link/navigate function
1906
+ const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
1907
+ : dest.search ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1908
+ : preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
1909
+ : {};
1910
+
1911
+ // Then post filters
1912
+ const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
1913
+ const search = replaceEqualDeep(from.search, postFilteredSearch);
1914
+ const searchStr = this.options.stringifySearch(search);
1915
+ const hash = dest.hash === true ? from.hash : dest.hash ? functionalUpdate(dest.hash, from.hash) : from.hash;
1916
+ const hashStr = hash ? `#${hash}` : '';
1917
+ let nextState = dest.state === true ? from.state : dest.state ? functionalUpdate(dest.state, from.state) : from.state;
1918
+ nextState = replaceEqualDeep(from.state, nextState);
1919
+ return {
1840
1920
  pathname,
1841
1921
  search,
1922
+ searchStr,
1923
+ state: nextState,
1842
1924
  hash,
1843
- state
1844
- }) => {
1845
- const parsedSearch = this.options.parseSearch(search);
1846
- return {
1847
- pathname: pathname,
1848
- searchStr: search,
1849
- search: replaceEqualDeep(previousLocation?.search, parsedSearch),
1850
- hash: hash.split('#').reverse()[0] ?? '',
1851
- href: `${pathname}${search}${hash}`,
1852
- state: replaceEqualDeep(previousLocation?.state, state)
1853
- };
1925
+ href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
1926
+ unmaskOnReload: dest.unmaskOnReload
1854
1927
  };
1855
- const location = parse(this.history.location);
1856
- let {
1857
- __tempLocation,
1858
- __tempKey
1859
- } = location.state;
1860
- if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
1861
- // Sync up the location keys
1862
- const parsedTempLocation = parse(__tempLocation);
1863
- parsedTempLocation.state.key = location.state.key;
1864
- delete parsedTempLocation.state.__tempLocation;
1865
- return {
1866
- ...parsedTempLocation,
1867
- maskedLocation: location
1868
- };
1869
- }
1870
- return location;
1871
1928
  };
1872
- buildLocation = (opts = {}) => {
1873
- const build = (dest = {}, matches) => {
1874
- const from = this.state.location;
1875
- const fromPathname = dest.from ?? from.pathname;
1876
- let pathname = resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? ''}`);
1877
- const fromMatches = this.matchRoutes(fromPathname, from.search);
1878
- const prevParams = {
1879
- ...last(fromMatches)?.params
1880
- };
1881
- let nextParams = (dest.params ?? true) === true ? prevParams : functionalUpdate(dest.params, prevParams);
1882
- if (nextParams) {
1883
- matches?.map(d => this.getRoute(d.routeId).options.stringifyParams).filter(Boolean).forEach(fn => {
1884
- nextParams = {
1885
- ...nextParams,
1886
- ...fn(nextParams)
1887
- };
1888
- });
1889
- }
1890
- pathname = interpolatePath(pathname, nextParams ?? {});
1891
- const preSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
1892
- const postSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
1893
-
1894
- // Pre filters first
1895
- const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), from.search) : from.search;
1896
-
1897
- // Then the link/navigate function
1898
- const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
1899
- : dest.search ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1900
- : preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
1901
- : {};
1902
-
1903
- // Then post filters
1904
- const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
1905
- const search = replaceEqualDeep(from.search, postFilteredSearch);
1906
- const searchStr = this.options.stringifySearch(search);
1907
- const hash = dest.hash === true ? from.hash : dest.hash ? functionalUpdate(dest.hash, from.hash) : from.hash;
1908
- const hashStr = hash ? `#${hash}` : '';
1909
- let nextState = dest.state === true ? from.state : dest.state ? functionalUpdate(dest.state, from.state) : from.state;
1910
- nextState = replaceEqualDeep(from.state, nextState);
1911
- return {
1912
- pathname,
1913
- search,
1914
- searchStr,
1915
- state: nextState,
1916
- hash,
1917
- href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
1918
- unmaskOnReload: dest.unmaskOnReload
1919
- };
1920
- };
1921
- const buildWithMatches = (dest = {}, maskedDest) => {
1922
- let next = build(dest);
1923
- let maskedNext = maskedDest ? build(maskedDest) : undefined;
1924
- if (!maskedNext) {
1925
- let params = {};
1926
- let foundMask = this.options.routeMasks?.find(d => {
1927
- const match = matchPathname(this.basepath, next.pathname, {
1928
- to: d.from,
1929
- fuzzy: false
1930
- });
1931
- if (match) {
1932
- params = match;
1933
- return true;
1934
- }
1935
- return false;
1929
+ const buildWithMatches = (dest = {}, maskedDest) => {
1930
+ let next = build(dest);
1931
+ let maskedNext = maskedDest ? build(maskedDest) : undefined;
1932
+ if (!maskedNext) {
1933
+ let params = {};
1934
+ let foundMask = this.options.routeMasks?.find(d => {
1935
+ const match = matchPathname(this.basepath, next.pathname, {
1936
+ to: d.from,
1937
+ fuzzy: false
1936
1938
  });
1937
- if (foundMask) {
1938
- foundMask = {
1939
- ...foundMask,
1940
- from: interpolatePath(foundMask.from, params)
1941
- };
1942
- maskedDest = foundMask;
1943
- maskedNext = build(maskedDest);
1939
+ if (match) {
1940
+ params = match;
1941
+ return true;
1944
1942
  }
1945
- }
1946
- const nextMatches = this.matchRoutes(next.pathname, next.search);
1947
- const maskedMatches = maskedNext ? this.matchRoutes(maskedNext.pathname, maskedNext.search) : undefined;
1948
- const maskedFinal = maskedNext ? build(maskedDest, maskedMatches) : undefined;
1949
- const final = build(dest, nextMatches);
1950
- if (maskedFinal) {
1951
- final.maskedLocation = maskedFinal;
1952
- }
1953
- return final;
1954
- };
1955
- if (opts.mask) {
1956
- return buildWithMatches(opts, {
1957
- ...pick(opts, ['from']),
1958
- ...opts.mask
1943
+ return false;
1959
1944
  });
1945
+ if (foundMask) {
1946
+ foundMask = {
1947
+ ...foundMask,
1948
+ from: interpolatePath(foundMask.from, params)
1949
+ };
1950
+ maskedDest = foundMask;
1951
+ maskedNext = build(maskedDest);
1952
+ }
1953
+ }
1954
+ const nextMatches = this.matchRoutes(next.pathname, next.search);
1955
+ const maskedMatches = maskedNext ? this.matchRoutes(maskedNext.pathname, maskedNext.search) : undefined;
1956
+ const maskedFinal = maskedNext ? build(maskedDest, maskedMatches) : undefined;
1957
+ const final = build(dest, nextMatches);
1958
+ if (maskedFinal) {
1959
+ final.maskedLocation = maskedFinal;
1960
1960
  }
1961
- return buildWithMatches(opts);
1961
+ return final;
1962
1962
  };
1963
- #buildAndCommitLocation = ({
1964
- replace,
1965
- resetScroll,
1966
- ...rest
1967
- } = {}) => {
1968
- const location = this.buildLocation(rest);
1969
- return this.#commitLocation({
1970
- ...location,
1971
- replace,
1972
- resetScroll
1963
+ if (opts.mask) {
1964
+ return buildWithMatches(opts, {
1965
+ ...pick(opts, ['from']),
1966
+ ...opts.mask
1973
1967
  });
1974
- };
1975
- #commitLocation = async next => {
1976
- if (this.navigateTimeout) clearTimeout(this.navigateTimeout);
1977
- let nextAction = 'replace';
1978
- if (!next.replace) {
1979
- nextAction = 'push';
1980
- }
1981
- const isSameUrl = this.state.location.href === next.href;
1982
- if (isSameUrl) {
1983
- nextAction = 'replace';
1984
- }
1985
- let {
1986
- maskedLocation,
1987
- ...nextHistory
1988
- } = next;
1989
- if (maskedLocation) {
1990
- nextHistory = {
1991
- ...maskedLocation,
1992
- state: {
1993
- ...maskedLocation.state,
1994
- __tempKey: undefined,
1995
- __tempLocation: {
1996
- ...nextHistory,
1997
- search: nextHistory.searchStr,
1998
- state: {
1999
- ...nextHistory.state,
2000
- __tempKey: undefined,
2001
- __tempLocation: undefined,
2002
- key: undefined
2003
- }
1968
+ }
1969
+ return buildWithMatches(opts);
1970
+ };
1971
+ #buildAndCommitLocation = ({
1972
+ replace,
1973
+ resetScroll,
1974
+ ...rest
1975
+ } = {}) => {
1976
+ const location = this.buildLocation(rest);
1977
+ return this.#commitLocation({
1978
+ ...location,
1979
+ replace,
1980
+ resetScroll
1981
+ });
1982
+ };
1983
+ #commitLocation = async next => {
1984
+ if (this.navigateTimeout) clearTimeout(this.navigateTimeout);
1985
+ let nextAction = 'replace';
1986
+ if (!next.replace) {
1987
+ nextAction = 'push';
1988
+ }
1989
+ const isSameUrl = this.state.location.href === next.href;
1990
+ if (isSameUrl) {
1991
+ nextAction = 'replace';
1992
+ }
1993
+ let {
1994
+ maskedLocation,
1995
+ ...nextHistory
1996
+ } = next;
1997
+ if (maskedLocation) {
1998
+ nextHistory = {
1999
+ ...maskedLocation,
2000
+ state: {
2001
+ ...maskedLocation.state,
2002
+ __tempKey: undefined,
2003
+ __tempLocation: {
2004
+ ...nextHistory,
2005
+ search: nextHistory.searchStr,
2006
+ state: {
2007
+ ...nextHistory.state,
2008
+ __tempKey: undefined,
2009
+ __tempLocation: undefined,
2010
+ key: undefined
2004
2011
  }
2005
2012
  }
2006
- };
2007
- if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
2008
- nextHistory.state.__tempKey = this.tempLocationKey;
2009
2013
  }
2014
+ };
2015
+ if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
2016
+ nextHistory.state.__tempKey = this.tempLocationKey;
2010
2017
  }
2011
- this.history[nextAction === 'push' ? 'push' : 'replace'](nextHistory.href, nextHistory.state);
2012
- this.resetNextScroll = next.resetScroll ?? true;
2013
- return this.latestLoadPromise;
2014
- };
2015
- getRouteMatch = id => {
2016
- return this.state.matchesById[id];
2017
- };
2018
- setRouteMatch = (id, updater) => {
2019
- this.__store.setState(prev => {
2020
- if (!prev.matchesById[id]) {
2021
- return prev;
2018
+ }
2019
+ this.history[nextAction === 'push' ? 'push' : 'replace'](nextHistory.href, nextHistory.state);
2020
+ this.resetNextScroll = next.resetScroll ?? true;
2021
+ return this.latestLoadPromise;
2022
+ };
2023
+ getRouteMatch = id => {
2024
+ return this.state.matchesById[id];
2025
+ };
2026
+ setRouteMatch = (id, updater) => {
2027
+ this.__store.setState(prev => {
2028
+ if (!prev.matchesById[id]) {
2029
+ return prev;
2030
+ }
2031
+ return {
2032
+ ...prev,
2033
+ matchesById: {
2034
+ ...prev.matchesById,
2035
+ [id]: updater(prev.matchesById[id])
2022
2036
  }
2023
- return {
2024
- ...prev,
2025
- matchesById: {
2026
- ...prev.matchesById,
2027
- [id]: updater(prev.matchesById[id])
2028
- }
2029
- };
2030
- });
2031
- };
2032
- setRouteMatchData = (id, updater, opts) => {
2033
- const match = this.getRouteMatch(id);
2034
- if (!match) return;
2035
- const route = this.getRoute(match.routeId);
2036
- const updatedAt = opts?.updatedAt ?? Date.now();
2037
- const preloadMaxAge = opts?.maxAge ?? route.options.preloadMaxAge ?? this.options.defaultPreloadMaxAge ?? 5000;
2038
- const maxAge = opts?.maxAge ?? route.options.maxAge ?? this.options.defaultMaxAge ?? -1;
2039
- this.setRouteMatch(id, s => ({
2037
+ };
2038
+ });
2039
+ };
2040
+ setRouteMatchData = (id, updater, opts) => {
2041
+ const match = this.getRouteMatch(id);
2042
+ if (!match) return;
2043
+ const route = this.getRoute(match.routeId);
2044
+ const updatedAt = opts?.updatedAt ?? Date.now();
2045
+ const preloadMaxAge = opts?.maxAge ?? route.options.preloadMaxAge ?? this.options.defaultPreloadMaxAge ?? 5000;
2046
+ const maxAge = opts?.maxAge ?? route.options.maxAge ?? this.options.defaultMaxAge ?? -1;
2047
+ this.setRouteMatch(id, s => ({
2048
+ ...s,
2049
+ error: undefined,
2050
+ status: 'success',
2051
+ isFetching: false,
2052
+ updatedAt: updatedAt,
2053
+ loaderData: functionalUpdate(updater, s.loaderData),
2054
+ preloadMaxAge,
2055
+ maxAge
2056
+ }));
2057
+ };
2058
+ invalidate = async opts => {
2059
+ if (opts?.matchId) {
2060
+ this.setRouteMatch(opts.matchId, s => ({
2040
2061
  ...s,
2041
- error: undefined,
2042
- status: 'success',
2043
- isFetching: false,
2044
- updatedAt: updatedAt,
2045
- loaderData: functionalUpdate(updater, s.loaderData),
2046
- preloadMaxAge,
2047
- maxAge
2062
+ invalid: true
2048
2063
  }));
2049
- };
2050
- invalidate = async opts => {
2051
- if (opts?.matchId) {
2052
- this.setRouteMatch(opts.matchId, s => ({
2053
- ...s,
2054
- invalid: true
2055
- }));
2056
- const matchIndex = this.state.matches.findIndex(d => d.id === opts.matchId);
2057
- const childMatch = this.state.matches[matchIndex + 1];
2058
- if (childMatch) {
2059
- return this.invalidate({
2060
- matchId: childMatch.id,
2061
- reload: false,
2062
- __fromFocus: opts.__fromFocus
2063
- });
2064
- }
2065
- } else {
2066
- this.__store.batch(() => {
2067
- Object.values(this.state.matchesById).forEach(match => {
2068
- const route = this.getRoute(match.routeId);
2069
- const shouldInvalidate = opts?.__fromFocus ? route.options.reloadOnWindowFocus ?? true : true;
2070
- if (shouldInvalidate) {
2071
- this.setRouteMatch(match.id, s => ({
2072
- ...s,
2073
- invalid: true
2074
- }));
2075
- }
2076
- });
2064
+ const matchIndex = this.state.matches.findIndex(d => d.id === opts.matchId);
2065
+ const childMatch = this.state.matches[matchIndex + 1];
2066
+ if (childMatch) {
2067
+ return this.invalidate({
2068
+ matchId: childMatch.id,
2069
+ reload: false,
2070
+ __fromFocus: opts.__fromFocus
2077
2071
  });
2078
2072
  }
2079
- if (opts?.reload ?? true) {
2080
- return this.load();
2081
- }
2082
- };
2083
- }
2084
-
2085
- // Detect if we're in the DOM
2086
- const isServer = typeof window === 'undefined' || !window.document.createElement;
2087
- function getInitialRouterState() {
2088
- return {
2089
- status: 'idle',
2090
- isFetching: false,
2091
- resolvedLocation: null,
2092
- location: null,
2093
- matchesById: {},
2094
- matchIds: [],
2095
- pendingMatchIds: [],
2096
- matches: [],
2097
- pendingMatches: [],
2098
- renderedMatchIds: [],
2099
- renderedMatches: [],
2100
- lastUpdated: Date.now()
2101
- };
2102
- }
2103
- function isCtrlEvent(e) {
2104
- return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
2105
- }
2106
- function redirect(opts) {
2107
- opts.isRedirect = true;
2108
- return opts;
2073
+ } else {
2074
+ this.__store.batch(() => {
2075
+ Object.values(this.state.matchesById).forEach(match => {
2076
+ const route = this.getRoute(match.routeId);
2077
+ const shouldInvalidate = opts?.__fromFocus ? route.options.reloadOnWindowFocus ?? true : true;
2078
+ if (shouldInvalidate) {
2079
+ this.setRouteMatch(match.id, s => ({
2080
+ ...s,
2081
+ invalid: true
2082
+ }));
2083
+ }
2084
+ });
2085
+ });
2086
+ }
2087
+ if (opts?.reload ?? true) {
2088
+ return this.load();
2089
+ }
2090
+ };
2091
+ }
2092
+
2093
+ // Detect if we're in the DOM
2094
+ const isServer = typeof window === 'undefined' || !window.document.createElement;
2095
+ function getInitialRouterState() {
2096
+ return {
2097
+ status: 'idle',
2098
+ isFetching: false,
2099
+ resolvedLocation: null,
2100
+ location: null,
2101
+ matchesById: {},
2102
+ matchIds: [],
2103
+ pendingMatchIds: [],
2104
+ matches: [],
2105
+ pendingMatches: [],
2106
+ renderedMatchIds: [],
2107
+ renderedMatches: [],
2108
+ lastUpdated: Date.now()
2109
+ };
2110
+ }
2111
+ function isCtrlEvent(e) {
2112
+ return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
2113
+ }
2114
+ function redirect(opts) {
2115
+ opts.isRedirect = true;
2116
+ return opts;
2117
+ }
2118
+ function isRedirect(obj) {
2119
+ return !!obj?.isRedirect;
2120
+ }
2121
+ class SearchParamError extends Error {}
2122
+ class PathParamError extends Error {}
2123
+ function escapeJSON(jsonString) {
2124
+ return jsonString.replace(/\\/g, '\\\\') // Escape backslashes
2125
+ .replace(/'/g, "\\'") // Escape single quotes
2126
+ .replace(/"/g, '\\"'); // Escape double quotes
2127
+ }
2128
+
2129
+ // A function that takes an import() argument which is a function and returns a new function that will
2130
+ // proxy arguments from the caller to the imported function, retaining all type
2131
+ // information along the way
2132
+ function lazyFn(fn, key) {
2133
+ return async (...args) => {
2134
+ const imported = await fn();
2135
+ return imported[key || 'default'](...args);
2136
+ };
2137
+ }
2138
+ function isMatchInvalid(match, opts) {
2139
+ const now = Date.now();
2140
+ if (match.invalid) {
2141
+ return true;
2109
2142
  }
2110
- function isRedirect(obj) {
2111
- return !!obj?.isRedirect;
2143
+ if (opts?.preload) {
2144
+ return match.preloadMaxAge < 0 ? false : match.updatedAt + match.preloadMaxAge < now;
2112
2145
  }
2113
- class SearchParamError extends Error {}
2114
- class PathParamError extends Error {}
2115
- function escapeJSON(jsonString) {
2116
- return jsonString.replace(/\\/g, '\\\\') // Escape backslashes
2117
- .replace(/'/g, "\\'") // Escape single quotes
2118
- .replace(/"/g, '\\"'); // Escape double quotes
2146
+ return match.maxAge < 0 ? false : match.updatedAt + match.maxAge < now;
2147
+ }
2148
+
2149
+ const windowKey = 'window';
2150
+ const delimiter = '___';
2151
+ let weakScrolledElementsByRestoreKey = {};
2152
+ let cache;
2153
+ let pathDidChange = false;
2154
+ const sessionsStorage = typeof window !== 'undefined' && window.sessionStorage;
2155
+ const defaultGetKey = location => location.state.key;
2156
+ function watchScrollPositions(router, opts) {
2157
+ const getKey = opts?.getKey || defaultGetKey;
2158
+ if (sessionsStorage) {
2159
+ if (!cache) {
2160
+ cache = (() => {
2161
+ const storageKey = 'tsr-scroll-restoration-v1';
2162
+ const current = JSON.parse(window.sessionStorage.getItem(storageKey) || '{}');
2163
+ return {
2164
+ current,
2165
+ set: (key, value) => {
2166
+ current[key] = value;
2167
+ window.sessionStorage.setItem(storageKey, JSON.stringify(cache));
2168
+ }
2169
+ };
2170
+ })();
2171
+ }
2119
2172
  }
2120
-
2121
- // A function that takes an import() argument which is a function and returns a new function that will
2122
- // proxy arguments from the caller to the imported function, retaining all type
2123
- // information along the way
2124
- function lazyFn(fn, key) {
2125
- return async (...args) => {
2126
- const imported = await fn();
2127
- return imported[key || 'default'](...args);
2128
- };
2173
+ const {
2174
+ history
2175
+ } = window;
2176
+ if (history.scrollRestoration) {
2177
+ history.scrollRestoration = 'manual';
2129
2178
  }
2130
- function isMatchInvalid(match, opts) {
2131
- const now = Date.now();
2132
- if (match.invalid) {
2133
- return true;
2134
- }
2135
- if (opts?.preload) {
2136
- return match.preloadMaxAge < 0 ? false : match.updatedAt + match.preloadMaxAge < now;
2179
+ const onScroll = event => {
2180
+ const restoreKey = getKey(router.state.resolvedLocation);
2181
+ if (!weakScrolledElementsByRestoreKey[restoreKey]) {
2182
+ weakScrolledElementsByRestoreKey[restoreKey] = new WeakSet();
2137
2183
  }
2138
- return match.maxAge < 0 ? false : match.updatedAt + match.maxAge < now;
2139
- }
2140
-
2141
- const windowKey = 'window';
2142
- const delimiter = '___';
2143
- let weakScrolledElementsByRestoreKey = {};
2144
- let cache;
2145
- let pathDidChange = false;
2146
- const sessionsStorage = typeof window !== 'undefined' && window.sessionStorage;
2147
- const defaultGetKey = location => location.state.key;
2148
- function watchScrollPositions(router, opts) {
2149
- const getKey = opts?.getKey || defaultGetKey;
2150
- if (sessionsStorage) {
2151
- if (!cache) {
2152
- cache = (() => {
2153
- const storageKey = 'tsr-scroll-restoration-v1';
2154
- const current = JSON.parse(window.sessionStorage.getItem(storageKey) || '{}');
2155
- return {
2156
- current,
2157
- set: (key, value) => {
2158
- current[key] = value;
2159
- window.sessionStorage.setItem(storageKey, JSON.stringify(cache));
2160
- }
2161
- };
2162
- })();
2163
- }
2184
+ const set = weakScrolledElementsByRestoreKey[restoreKey];
2185
+ if (set.has(event.target)) return;
2186
+ set.add(event.target);
2187
+ const cacheKey = [restoreKey, event.target === document || event.target === window ? windowKey : getCssSelector(event.target)].join(delimiter);
2188
+ if (!cache.current[cacheKey]) {
2189
+ cache.set(cacheKey, {
2190
+ scrollX: NaN,
2191
+ scrollY: NaN
2192
+ });
2164
2193
  }
2165
- const {
2166
- history
2167
- } = window;
2168
- if (history.scrollRestoration) {
2169
- history.scrollRestoration = 'manual';
2194
+ };
2195
+ const getCssSelector = el => {
2196
+ let path = [],
2197
+ parent;
2198
+ while (parent = el.parentNode) {
2199
+ path.unshift(`${el.tagName}:nth-child(${[].indexOf.call(parent.children, el) + 1})`);
2200
+ el = parent;
2170
2201
  }
2171
- const onScroll = event => {
2172
- const restoreKey = getKey(router.state.resolvedLocation);
2173
- if (!weakScrolledElementsByRestoreKey[restoreKey]) {
2174
- weakScrolledElementsByRestoreKey[restoreKey] = new WeakSet();
2175
- }
2176
- const set = weakScrolledElementsByRestoreKey[restoreKey];
2177
- if (set.has(event.target)) return;
2178
- set.add(event.target);
2179
- const cacheKey = [restoreKey, event.target === document || event.target === window ? windowKey : getCssSelector(event.target)].join(delimiter);
2180
- if (!cache.current[cacheKey]) {
2181
- cache.set(cacheKey, {
2182
- scrollX: NaN,
2183
- scrollY: NaN
2184
- });
2185
- }
2186
- };
2187
- const getCssSelector = el => {
2188
- let path = [],
2189
- parent;
2190
- while (parent = el.parentNode) {
2191
- path.unshift(`${el.tagName}:nth-child(${[].indexOf.call(parent.children, el) + 1})`);
2192
- el = parent;
2193
- }
2194
- return `${path.join(' > ')}`.toLowerCase();
2195
- };
2196
- const onPathWillChange = from => {
2197
- const restoreKey = getKey(from);
2198
- for (const cacheKey in cache.current) {
2199
- const entry = cache.current[cacheKey];
2200
- const [key, elementSelector] = cacheKey.split(delimiter);
2201
- if (restoreKey === key) {
2202
- if (elementSelector === windowKey) {
2203
- entry.scrollX = window.scrollX || 0;
2204
- entry.scrollY = window.scrollY || 0;
2205
- } else if (elementSelector) {
2206
- const element = document.querySelector(elementSelector);
2207
- entry.scrollX = element?.scrollLeft || 0;
2208
- entry.scrollY = element?.scrollTop || 0;
2209
- }
2210
- cache.set(cacheKey, entry);
2202
+ return `${path.join(' > ')}`.toLowerCase();
2203
+ };
2204
+ const onPathWillChange = from => {
2205
+ const restoreKey = getKey(from);
2206
+ for (const cacheKey in cache.current) {
2207
+ const entry = cache.current[cacheKey];
2208
+ const [key, elementSelector] = cacheKey.split(delimiter);
2209
+ if (restoreKey === key) {
2210
+ if (elementSelector === windowKey) {
2211
+ entry.scrollX = window.scrollX || 0;
2212
+ entry.scrollY = window.scrollY || 0;
2213
+ } else if (elementSelector) {
2214
+ const element = document.querySelector(elementSelector);
2215
+ entry.scrollX = element?.scrollLeft || 0;
2216
+ entry.scrollY = element?.scrollTop || 0;
2211
2217
  }
2218
+ cache.set(cacheKey, entry);
2212
2219
  }
2213
- };
2214
- const onPathChange = () => {
2215
- pathDidChange = true;
2216
- };
2217
- if (typeof document !== 'undefined') {
2218
- document.addEventListener('scroll', onScroll, true);
2219
2220
  }
2220
- const unsubOnBeforeLoad = router.subscribe('onBeforeLoad', event => {
2221
- if (event.pathChanged) onPathWillChange(event.from);
2222
- });
2223
- const unsubOnLoad = router.subscribe('onLoad', event => {
2224
- if (event.pathChanged) onPathChange();
2225
- });
2226
- return () => {
2227
- document.removeEventListener('scroll', onScroll);
2228
- unsubOnBeforeLoad();
2229
- unsubOnLoad();
2230
- };
2221
+ };
2222
+ const onPathChange = () => {
2223
+ pathDidChange = true;
2224
+ };
2225
+ if (typeof document !== 'undefined') {
2226
+ document.addEventListener('scroll', onScroll, true);
2231
2227
  }
2232
- function restoreScrollPositions(router, opts) {
2233
- if (pathDidChange) {
2234
- if (!router.resetNextScroll) {
2235
- return;
2236
- }
2237
- const getKey = opts?.getKey || defaultGetKey;
2238
- pathDidChange = false;
2239
- const restoreKey = getKey(router.state.location);
2240
- let windowRestored = false;
2241
- for (const cacheKey in cache.current) {
2242
- const entry = cache.current[cacheKey];
2243
- const [key, elementSelector] = cacheKey.split(delimiter);
2244
- if (key === restoreKey) {
2245
- if (elementSelector === windowKey) {
2246
- windowRestored = true;
2247
- window.scrollTo(entry.scrollX, entry.scrollY);
2248
- } else if (elementSelector) {
2249
- const element = document.querySelector(elementSelector);
2250
- if (element) {
2251
- element.scrollLeft = entry.scrollX;
2252
- element.scrollTop = entry.scrollY;
2253
- }
2228
+ const unsubOnBeforeLoad = router.subscribe('onBeforeLoad', event => {
2229
+ if (event.pathChanged) onPathWillChange(event.from);
2230
+ });
2231
+ const unsubOnLoad = router.subscribe('onLoad', event => {
2232
+ if (event.pathChanged) onPathChange();
2233
+ });
2234
+ return () => {
2235
+ document.removeEventListener('scroll', onScroll);
2236
+ unsubOnBeforeLoad();
2237
+ unsubOnLoad();
2238
+ };
2239
+ }
2240
+ function restoreScrollPositions(router, opts) {
2241
+ if (pathDidChange) {
2242
+ if (!router.resetNextScroll) {
2243
+ return;
2244
+ }
2245
+ const getKey = opts?.getKey || defaultGetKey;
2246
+ pathDidChange = false;
2247
+ const restoreKey = getKey(router.state.location);
2248
+ let windowRestored = false;
2249
+ for (const cacheKey in cache.current) {
2250
+ const entry = cache.current[cacheKey];
2251
+ const [key, elementSelector] = cacheKey.split(delimiter);
2252
+ if (key === restoreKey) {
2253
+ if (elementSelector === windowKey) {
2254
+ windowRestored = true;
2255
+ window.scrollTo(entry.scrollX, entry.scrollY);
2256
+ } else if (elementSelector) {
2257
+ const element = document.querySelector(elementSelector);
2258
+ if (element) {
2259
+ element.scrollLeft = entry.scrollX;
2260
+ element.scrollTop = entry.scrollY;
2254
2261
  }
2255
2262
  }
2256
2263
  }
2257
- if (!windowRestored) {
2258
- window.scrollTo(0, 0);
2259
- }
2260
2264
  }
2261
- }
2262
-
2263
- function defer(_promise) {
2264
- const promise = _promise;
2265
- if (!promise.__deferredState) {
2266
- promise.__deferredState = {
2267
- uid: Math.random().toString(36).slice(2),
2268
- status: 'pending'
2269
- };
2270
- const state = promise.__deferredState;
2271
- promise.then(data => {
2272
- state.status = 'success';
2273
- state.data = data;
2274
- }).catch(error => {
2275
- state.status = 'error';
2276
- state.error = error;
2277
- });
2265
+ if (!windowRestored) {
2266
+ window.scrollTo(0, 0);
2278
2267
  }
2279
- return promise;
2280
2268
  }
2281
- function isDehydratedDeferred(obj) {
2282
- return typeof obj === 'object' && obj !== null && !(obj instanceof Promise) && !obj.then && '__deferredState' in obj;
2269
+ }
2270
+
2271
+ function defer(_promise) {
2272
+ const promise = _promise;
2273
+ if (!promise.__deferredState) {
2274
+ promise.__deferredState = {
2275
+ uid: Math.random().toString(36).slice(2),
2276
+ status: 'pending'
2277
+ };
2278
+ const state = promise.__deferredState;
2279
+ promise.then(data => {
2280
+ state.status = 'success';
2281
+ state.data = data;
2282
+ }).catch(error => {
2283
+ state.status = 'error';
2284
+ state.error = error;
2285
+ });
2283
2286
  }
2284
-
2285
- exports.FileRoute = FileRoute;
2286
- exports.PathParamError = PathParamError;
2287
- exports.RootRoute = RootRoute;
2288
- exports.Route = Route;
2289
- exports.Router = Router;
2290
- exports.RouterContext = RouterContext;
2291
- exports.SearchParamError = SearchParamError;
2292
- exports.cleanPath = cleanPath;
2293
- exports.componentTypes = componentTypes;
2294
- exports.createBrowserHistory = createBrowserHistory;
2295
- exports.createHashHistory = createHashHistory;
2296
- exports.createMemoryHistory = createMemoryHistory;
2297
- exports.createRouteMask = createRouteMask;
2298
- exports.decode = decode;
2299
- exports.defaultParseSearch = defaultParseSearch;
2300
- exports.defaultStringifySearch = defaultStringifySearch;
2301
- exports.defer = defer;
2302
- exports.encode = encode;
2303
- exports.functionalUpdate = functionalUpdate;
2304
- exports.interpolatePath = interpolatePath;
2305
- exports.invariant = invariant;
2306
- exports.isDehydratedDeferred = isDehydratedDeferred;
2307
- exports.isMatchInvalid = isMatchInvalid;
2308
- exports.isPlainObject = isPlainObject;
2309
- exports.isRedirect = isRedirect;
2310
- exports.joinPaths = joinPaths;
2311
- exports.last = last;
2312
- exports.lazyFn = lazyFn;
2313
- exports.matchByPath = matchByPath;
2314
- exports.matchPathname = matchPathname;
2315
- exports.parsePathname = parsePathname;
2316
- exports.parseSearchWith = parseSearchWith;
2317
- exports.partialDeepEqual = partialDeepEqual;
2318
- exports.pick = pick;
2319
- exports.redirect = redirect;
2320
- exports.replaceEqualDeep = replaceEqualDeep;
2321
- exports.resolvePath = resolvePath;
2322
- exports.restoreScrollPositions = restoreScrollPositions;
2323
- exports.rootRouteId = rootRouteId;
2324
- exports.stringifySearchWith = stringifySearchWith;
2325
- exports.trimPath = trimPath;
2326
- exports.trimPathLeft = trimPathLeft;
2327
- exports.trimPathRight = trimPathRight;
2328
- exports.warning = warning;
2329
- exports.watchScrollPositions = watchScrollPositions;
2330
-
2331
- Object.defineProperty(exports, '__esModule', { value: true });
2287
+ return promise;
2288
+ }
2289
+ function isDehydratedDeferred(obj) {
2290
+ return typeof obj === 'object' && obj !== null && !(obj instanceof Promise) && !obj.then && '__deferredState' in obj;
2291
+ }
2292
+
2293
+ exports.FileRoute = FileRoute;
2294
+ exports.PathParamError = PathParamError;
2295
+ exports.RootRoute = RootRoute;
2296
+ exports.Route = Route;
2297
+ exports.Router = Router;
2298
+ exports.RouterContext = RouterContext;
2299
+ exports.SearchParamError = SearchParamError;
2300
+ exports.cleanPath = cleanPath;
2301
+ exports.componentTypes = componentTypes;
2302
+ exports.createBrowserHistory = createBrowserHistory;
2303
+ exports.createHashHistory = createHashHistory;
2304
+ exports.createMemoryHistory = createMemoryHistory;
2305
+ exports.createRouteMask = createRouteMask;
2306
+ exports.decode = decode;
2307
+ exports.defaultParseSearch = defaultParseSearch;
2308
+ exports.defaultStringifySearch = defaultStringifySearch;
2309
+ exports.defer = defer;
2310
+ exports.encode = encode;
2311
+ exports.functionalUpdate = functionalUpdate;
2312
+ exports.interpolatePath = interpolatePath;
2313
+ exports.invariant = invariant;
2314
+ exports.isDehydratedDeferred = isDehydratedDeferred;
2315
+ exports.isMatchInvalid = isMatchInvalid;
2316
+ exports.isPlainObject = isPlainObject;
2317
+ exports.isRedirect = isRedirect;
2318
+ exports.joinPaths = joinPaths;
2319
+ exports.last = last;
2320
+ exports.lazyFn = lazyFn;
2321
+ exports.matchByPath = matchByPath;
2322
+ exports.matchPathname = matchPathname;
2323
+ exports.parsePathname = parsePathname;
2324
+ exports.parseSearchWith = parseSearchWith;
2325
+ exports.partialDeepEqual = partialDeepEqual;
2326
+ exports.pick = pick;
2327
+ exports.redirect = redirect;
2328
+ exports.replaceEqualDeep = replaceEqualDeep;
2329
+ exports.resolvePath = resolvePath;
2330
+ exports.restoreScrollPositions = restoreScrollPositions;
2331
+ exports.rootRouteId = rootRouteId;
2332
+ exports.stringifySearchWith = stringifySearchWith;
2333
+ exports.trimPath = trimPath;
2334
+ exports.trimPathLeft = trimPathLeft;
2335
+ exports.trimPathRight = trimPathRight;
2336
+ exports.warning = warning;
2337
+ exports.watchScrollPositions = watchScrollPositions;
2338
+
2339
+ Object.defineProperty(exports, '__esModule', { value: true });
2332
2340
 
2333
2341
  }));
2334
2342
  //# sourceMappingURL=index.development.js.map