@tanstack/router-core 0.0.1-beta.2 → 0.0.1-beta.201

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