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

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