@tanstack/router-core 0.0.1-beta.5 → 0.0.1-beta.51

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