@remix-run/router 1.6.2 → 1.6.3

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.6.2
2
+ * @remix-run/router v1.6.3
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -18,14 +18,12 @@
18
18
  _extends = Object.assign ? Object.assign.bind() : function (target) {
19
19
  for (var i = 1; i < arguments.length; i++) {
20
20
  var source = arguments[i];
21
-
22
21
  for (var key in source) {
23
22
  if (Object.prototype.hasOwnProperty.call(source, key)) {
24
23
  target[key] = source[key];
25
24
  }
26
25
  }
27
26
  }
28
-
29
27
  return target;
30
28
  };
31
29
  return _extends.apply(this, arguments);
@@ -39,17 +37,41 @@
39
37
  * Actions represent the type of change to a location value.
40
38
  */
41
39
  exports.Action = void 0;
40
+
42
41
  /**
43
42
  * The pathname, search, and hash values of a URL.
44
43
  */
45
-
46
44
  (function (Action) {
47
45
  Action["Pop"] = "POP";
48
46
  Action["Push"] = "PUSH";
49
47
  Action["Replace"] = "REPLACE";
50
48
  })(exports.Action || (exports.Action = {}));
49
+ /**
50
+ * An entry in a history stack. A location contains information about the
51
+ * URL path, as well as possibly some arbitrary state and a key.
52
+ */
53
+ /**
54
+ * A change to the current location.
55
+ */
56
+ /**
57
+ * A function that receives notifications about location changes.
58
+ */
59
+ /**
60
+ * Describes a location that is the destination of some navigation, either via
61
+ * `history.push` or `history.replace`. May be either a URL or the pieces of a
62
+ * URL path.
63
+ */
64
+ /**
65
+ * A history is an interface to the navigation stack. The history serves as the
66
+ * source of truth for the current location, as well as provides a set of
67
+ * methods that may be used to change it.
68
+ *
69
+ * It is similar to the DOM's `window.history` object, but with a smaller, more
70
+ * focused API.
71
+ */
72
+ const PopStateEventType = "popstate";
73
+ //#endregion
51
74
 
52
- const PopStateEventType = "popstate"; //#endregion
53
75
  ////////////////////////////////////////////////////////////////////////////////
54
76
  //#region Memory History
55
77
  ////////////////////////////////////////////////////////////////////////////////
@@ -58,7 +80,11 @@
58
80
  * A user-supplied object that describes a location. Used when providing
59
81
  * entries to `createMemoryHistory` via its `initialEntries` option.
60
82
  */
61
-
83
+ /**
84
+ * A memory history stores locations in memory. This is useful in stateful
85
+ * environments where there is no web browser, such as node tests or React
86
+ * Native.
87
+ */
62
88
  /**
63
89
  * Memory history stores the current location in memory. It is designed for use
64
90
  * in stateful non-browser environments like tests and React Native.
@@ -67,60 +93,47 @@
67
93
  if (options === void 0) {
68
94
  options = {};
69
95
  }
70
-
71
96
  let {
72
97
  initialEntries = ["/"],
73
98
  initialIndex,
74
99
  v5Compat = false
75
100
  } = options;
76
101
  let entries; // Declare so we can access from createMemoryLocation
77
-
78
102
  entries = initialEntries.map((entry, index) => createMemoryLocation(entry, typeof entry === "string" ? null : entry.state, index === 0 ? "default" : undefined));
79
103
  let index = clampIndex(initialIndex == null ? entries.length - 1 : initialIndex);
80
104
  let action = exports.Action.Pop;
81
105
  let listener = null;
82
-
83
106
  function clampIndex(n) {
84
107
  return Math.min(Math.max(n, 0), entries.length - 1);
85
108
  }
86
-
87
109
  function getCurrentLocation() {
88
110
  return entries[index];
89
111
  }
90
-
91
112
  function createMemoryLocation(to, state, key) {
92
113
  if (state === void 0) {
93
114
  state = null;
94
115
  }
95
-
96
116
  let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key);
97
117
  warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
98
118
  return location;
99
119
  }
100
-
101
120
  function createHref(to) {
102
121
  return typeof to === "string" ? to : createPath(to);
103
122
  }
104
-
105
123
  let history = {
106
124
  get index() {
107
125
  return index;
108
126
  },
109
-
110
127
  get action() {
111
128
  return action;
112
129
  },
113
-
114
130
  get location() {
115
131
  return getCurrentLocation();
116
132
  },
117
-
118
133
  createHref,
119
-
120
134
  createURL(to) {
121
135
  return new URL(createHref(to), "http://localhost");
122
136
  },
123
-
124
137
  encodeLocation(to) {
125
138
  let path = typeof to === "string" ? parsePath(to) : to;
126
139
  return {
@@ -129,13 +142,11 @@
129
142
  hash: path.hash || ""
130
143
  };
131
144
  },
132
-
133
145
  push(to, state) {
134
146
  action = exports.Action.Push;
135
147
  let nextLocation = createMemoryLocation(to, state);
136
148
  index += 1;
137
149
  entries.splice(index, entries.length, nextLocation);
138
-
139
150
  if (v5Compat && listener) {
140
151
  listener({
141
152
  action,
@@ -144,12 +155,10 @@
144
155
  });
145
156
  }
146
157
  },
147
-
148
158
  replace(to, state) {
149
159
  action = exports.Action.Replace;
150
160
  let nextLocation = createMemoryLocation(to, state);
151
161
  entries[index] = nextLocation;
152
-
153
162
  if (v5Compat && listener) {
154
163
  listener({
155
164
  action,
@@ -158,13 +167,11 @@
158
167
  });
159
168
  }
160
169
  },
161
-
162
170
  go(delta) {
163
171
  action = exports.Action.Pop;
164
172
  let nextIndex = clampIndex(index + delta);
165
173
  let nextLocation = entries[nextIndex];
166
174
  index = nextIndex;
167
-
168
175
  if (listener) {
169
176
  listener({
170
177
  action,
@@ -173,17 +180,17 @@
173
180
  });
174
181
  }
175
182
  },
176
-
177
183
  listen(fn) {
178
184
  listener = fn;
179
185
  return () => {
180
186
  listener = null;
181
187
  };
182
188
  }
183
-
184
189
  };
185
190
  return history;
186
- } //#endregion
191
+ }
192
+ //#endregion
193
+
187
194
  ////////////////////////////////////////////////////////////////////////////////
188
195
  //#region Browser History
189
196
  ////////////////////////////////////////////////////////////////////////////////
@@ -195,7 +202,6 @@
195
202
  *
196
203
  * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#browserhistory
197
204
  */
198
-
199
205
  /**
200
206
  * Browser history stores the location in regular URLs. This is the standard for
201
207
  * most web apps, but it requires some configuration on the server to ensure you
@@ -207,7 +213,6 @@
207
213
  if (options === void 0) {
208
214
  options = {};
209
215
  }
210
-
211
216
  function createBrowserLocation(window, globalHistory) {
212
217
  let {
213
218
  pathname,
@@ -218,16 +223,17 @@
218
223
  pathname,
219
224
  search,
220
225
  hash
221
- }, // state defaults to `null` because `window.history.state` does
226
+ },
227
+ // state defaults to `null` because `window.history.state` does
222
228
  globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
223
229
  }
224
-
225
230
  function createBrowserHref(window, to) {
226
231
  return typeof to === "string" ? to : createPath(to);
227
232
  }
228
-
229
233
  return getUrlBasedHistory(createBrowserLocation, createBrowserHref, null, options);
230
- } //#endregion
234
+ }
235
+ //#endregion
236
+
231
237
  ////////////////////////////////////////////////////////////////////////////////
232
238
  //#region Hash History
233
239
  ////////////////////////////////////////////////////////////////////////////////
@@ -243,7 +249,6 @@
243
249
  *
244
250
  * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#hashhistory
245
251
  */
246
-
247
252
  /**
248
253
  * Hash history stores the location in window.location.hash. This makes it ideal
249
254
  * for situations where you don't want to send the location to the server for
@@ -256,7 +261,6 @@
256
261
  if (options === void 0) {
257
262
  options = {};
258
263
  }
259
-
260
264
  function createHashLocation(window, globalHistory) {
261
265
  let {
262
266
  pathname = "/",
@@ -267,29 +271,27 @@
267
271
  pathname,
268
272
  search,
269
273
  hash
270
- }, // state defaults to `null` because `window.history.state` does
274
+ },
275
+ // state defaults to `null` because `window.history.state` does
271
276
  globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
272
277
  }
273
-
274
278
  function createHashHref(window, to) {
275
279
  let base = window.document.querySelector("base");
276
280
  let href = "";
277
-
278
281
  if (base && base.getAttribute("href")) {
279
282
  let url = window.location.href;
280
283
  let hashIndex = url.indexOf("#");
281
284
  href = hashIndex === -1 ? url : url.slice(0, hashIndex);
282
285
  }
283
-
284
286
  return href + "#" + (typeof to === "string" ? to : createPath(to));
285
287
  }
286
-
287
288
  function validateHashLocation(location, to) {
288
289
  warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
289
290
  }
290
-
291
291
  return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
292
- } //#endregion
292
+ }
293
+ //#endregion
294
+
293
295
  ////////////////////////////////////////////////////////////////////////////////
294
296
  //#region UTILS
295
297
  ////////////////////////////////////////////////////////////////////////////////
@@ -297,7 +299,6 @@
297
299
  /**
298
300
  * @private
299
301
  */
300
-
301
302
  function invariant(value, message) {
302
303
  if (value === false || value === null || typeof value === "undefined") {
303
304
  throw new Error(message);
@@ -307,26 +308,24 @@
307
308
  if (!cond) {
308
309
  // eslint-disable-next-line no-console
309
310
  if (typeof console !== "undefined") console.warn(message);
310
-
311
311
  try {
312
312
  // Welcome to debugging history!
313
313
  //
314
314
  // This error is thrown as a convenience so you can more easily
315
315
  // find the source for a warning that appears in the console by
316
316
  // enabling "pause on exceptions" in your JavaScript debugger.
317
- throw new Error(message); // eslint-disable-next-line no-empty
317
+ throw new Error(message);
318
+ // eslint-disable-next-line no-empty
318
319
  } catch (e) {}
319
320
  }
320
321
  }
321
-
322
322
  function createKey() {
323
323
  return Math.random().toString(36).substr(2, 8);
324
324
  }
325
+
325
326
  /**
326
327
  * For browser-based histories, we combine the state and key into an object
327
328
  */
328
-
329
-
330
329
  function getHistoryState(location, index) {
331
330
  return {
332
331
  usr: location.state,
@@ -334,16 +333,14 @@
334
333
  idx: index
335
334
  };
336
335
  }
336
+
337
337
  /**
338
338
  * Creates a Location object with a unique key from the given Path
339
339
  */
340
-
341
-
342
340
  function createLocation(current, to, state, key) {
343
341
  if (state === void 0) {
344
342
  state = null;
345
343
  }
346
-
347
344
  let location = _extends({
348
345
  pathname: typeof current === "string" ? current : current.pathname,
349
346
  search: "",
@@ -356,13 +353,12 @@
356
353
  // keep as is for the time being and just let any incoming keys take precedence
357
354
  key: to && to.key || key || createKey()
358
355
  });
359
-
360
356
  return location;
361
357
  }
358
+
362
359
  /**
363
360
  * Creates a string URL path from the given pathname, search, and hash components.
364
361
  */
365
-
366
362
  function createPath(_ref) {
367
363
  let {
368
364
  pathname = "/",
@@ -373,41 +369,33 @@
373
369
  if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : "#" + hash;
374
370
  return pathname;
375
371
  }
372
+
376
373
  /**
377
374
  * Parses a string URL path into its separate pathname, search, and hash components.
378
375
  */
379
-
380
376
  function parsePath(path) {
381
377
  let parsedPath = {};
382
-
383
378
  if (path) {
384
379
  let hashIndex = path.indexOf("#");
385
-
386
380
  if (hashIndex >= 0) {
387
381
  parsedPath.hash = path.substr(hashIndex);
388
382
  path = path.substr(0, hashIndex);
389
383
  }
390
-
391
384
  let searchIndex = path.indexOf("?");
392
-
393
385
  if (searchIndex >= 0) {
394
386
  parsedPath.search = path.substr(searchIndex);
395
387
  path = path.substr(0, searchIndex);
396
388
  }
397
-
398
389
  if (path) {
399
390
  parsedPath.pathname = path;
400
391
  }
401
392
  }
402
-
403
393
  return parsedPath;
404
394
  }
405
-
406
395
  function getUrlBasedHistory(getLocation, createHref, validateLocation, options) {
407
396
  if (options === void 0) {
408
397
  options = {};
409
398
  }
410
-
411
399
  let {
412
400
  window = document.defaultView,
413
401
  v5Compat = false
@@ -415,30 +403,27 @@
415
403
  let globalHistory = window.history;
416
404
  let action = exports.Action.Pop;
417
405
  let listener = null;
418
- let index = getIndex(); // Index should only be null when we initialize. If not, it's because the
406
+ let index = getIndex();
407
+ // Index should only be null when we initialize. If not, it's because the
419
408
  // user called history.pushState or history.replaceState directly, in which
420
409
  // case we should log a warning as it will result in bugs.
421
-
422
410
  if (index == null) {
423
411
  index = 0;
424
412
  globalHistory.replaceState(_extends({}, globalHistory.state, {
425
413
  idx: index
426
414
  }), "");
427
415
  }
428
-
429
416
  function getIndex() {
430
417
  let state = globalHistory.state || {
431
418
  idx: null
432
419
  };
433
420
  return state.idx;
434
421
  }
435
-
436
422
  function handlePop() {
437
423
  action = exports.Action.Pop;
438
424
  let nextIndex = getIndex();
439
425
  let delta = nextIndex == null ? null : nextIndex - index;
440
426
  index = nextIndex;
441
-
442
427
  if (listener) {
443
428
  listener({
444
429
  action,
@@ -447,23 +432,29 @@
447
432
  });
448
433
  }
449
434
  }
450
-
451
435
  function push(to, state) {
452
436
  action = exports.Action.Push;
453
437
  let location = createLocation(history.location, to, state);
454
438
  if (validateLocation) validateLocation(location, to);
455
439
  index = getIndex() + 1;
456
440
  let historyState = getHistoryState(location, index);
457
- let url = history.createHref(location); // try...catch because iOS limits us to 100 pushState calls :/
441
+ let url = history.createHref(location);
458
442
 
443
+ // try...catch because iOS limits us to 100 pushState calls :/
459
444
  try {
460
445
  globalHistory.pushState(historyState, "", url);
461
446
  } catch (error) {
447
+ // If the exception is because `state` can't be serialized, let that throw
448
+ // outwards just like a replace call would so the dev knows the cause
449
+ // https://html.spec.whatwg.org/multipage/nav-history-apis.html#shared-history-push/replace-state-steps
450
+ // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
451
+ if (error instanceof DOMException && error.name === "DataCloneError") {
452
+ throw error;
453
+ }
462
454
  // They are going to lose state here, but there is no real
463
455
  // way to warn them about it since the page will refresh...
464
456
  window.location.assign(url);
465
457
  }
466
-
467
458
  if (v5Compat && listener) {
468
459
  listener({
469
460
  action,
@@ -472,7 +463,6 @@
472
463
  });
473
464
  }
474
465
  }
475
-
476
466
  function replace(to, state) {
477
467
  action = exports.Action.Replace;
478
468
  let location = createLocation(history.location, to, state);
@@ -481,7 +471,6 @@
481
471
  let historyState = getHistoryState(location, index);
482
472
  let url = history.createHref(location);
483
473
  globalHistory.replaceState(historyState, "", url);
484
-
485
474
  if (v5Compat && listener) {
486
475
  listener({
487
476
  action,
@@ -490,7 +479,6 @@
490
479
  });
491
480
  }
492
481
  }
493
-
494
482
  function createURL(to) {
495
483
  // window.location.origin is "null" (the literal string value) in Firefox
496
484
  // under certain conditions, notably when serving from a local HTML file
@@ -500,21 +488,17 @@
500
488
  invariant(base, "No window.location.(origin|href) available to create URL for href: " + href);
501
489
  return new URL(href, base);
502
490
  }
503
-
504
491
  let history = {
505
492
  get action() {
506
493
  return action;
507
494
  },
508
-
509
495
  get location() {
510
496
  return getLocation(window, globalHistory);
511
497
  },
512
-
513
498
  listen(fn) {
514
499
  if (listener) {
515
500
  throw new Error("A history only accepts one active listener");
516
501
  }
517
-
518
502
  window.addEventListener(PopStateEventType, handlePop);
519
503
  listener = fn;
520
504
  return () => {
@@ -522,13 +506,10 @@
522
506
  listener = null;
523
507
  };
524
508
  },
525
-
526
509
  createHref(to) {
527
510
  return createHref(window, to);
528
511
  },
529
-
530
512
  createURL,
531
-
532
513
  encodeLocation(to) {
533
514
  // Encode a Location the same way window.location would
534
515
  let url = createURL(to);
@@ -538,66 +519,176 @@
538
519
  hash: url.hash
539
520
  };
540
521
  },
541
-
542
522
  push,
543
523
  replace,
544
-
545
524
  go(n) {
546
525
  return globalHistory.go(n);
547
526
  }
548
-
549
527
  };
550
528
  return history;
551
- } //#endregion
529
+ }
530
+
531
+ //#endregion
552
532
 
553
533
  /**
554
534
  * Map of routeId -> data returned from a loader/action/error
555
535
  */
556
536
 
557
537
  let ResultType;
538
+
558
539
  /**
559
540
  * Successful result from a loader or action
560
541
  */
561
-
562
542
  (function (ResultType) {
563
543
  ResultType["data"] = "data";
564
544
  ResultType["deferred"] = "deferred";
565
545
  ResultType["redirect"] = "redirect";
566
546
  ResultType["error"] = "error";
567
547
  })(ResultType || (ResultType = {}));
568
-
548
+ /**
549
+ * Successful defer() result from a loader or action
550
+ */
551
+ /**
552
+ * Redirect result from a loader or action
553
+ */
554
+ /**
555
+ * Unsuccessful result from a loader or action
556
+ */
557
+ /**
558
+ * Result from a loader or action - potentially successful or unsuccessful
559
+ */
560
+ /**
561
+ * Users can specify either lowercase or uppercase form methods on <Form>,
562
+ * useSubmit(), <fetcher.Form>, etc.
563
+ */
564
+ /**
565
+ * Active navigation/fetcher form methods are exposed in lowercase on the
566
+ * RouterState
567
+ */
568
+ /**
569
+ * In v7, active navigation/fetcher form methods are exposed in uppercase on the
570
+ * RouterState. This is to align with the normalization done via fetch().
571
+ */
572
+ /**
573
+ * @private
574
+ * Internal interface to pass around for action submissions, not intended for
575
+ * external consumption
576
+ */
577
+ /**
578
+ * @private
579
+ * Arguments passed to route loader/action functions. Same for now but we keep
580
+ * this as a private implementation detail in case they diverge in the future.
581
+ */
582
+ /**
583
+ * Arguments passed to loader functions
584
+ */
585
+ /**
586
+ * Arguments passed to action functions
587
+ */
588
+ /**
589
+ * Loaders and actions can return anything except `undefined` (`null` is a
590
+ * valid return value if there is no data to return). Responses are preferred
591
+ * and will ease any future migration to Remix
592
+ */
593
+ /**
594
+ * Route loader function signature
595
+ */
596
+ /**
597
+ * Route action function signature
598
+ */
599
+ /**
600
+ * Route shouldRevalidate function signature. This runs after any submission
601
+ * (navigation or fetcher), so we flatten the navigation/fetcher submission
602
+ * onto the arguments. It shouldn't matter whether it came from a navigation
603
+ * or a fetcher, what really matters is the URLs and the formData since loaders
604
+ * have to re-run based on the data models that were potentially mutated.
605
+ */
606
+ /**
607
+ * Function provided by the framework-aware layers to set `hasErrorBoundary`
608
+ * from the framework-aware `errorElement` prop
609
+ *
610
+ * @deprecated Use `mapRouteProperties` instead
611
+ */
612
+ /**
613
+ * Function provided by the framework-aware layers to set any framework-specific
614
+ * properties from framework-agnostic properties
615
+ */
616
+ /**
617
+ * Keys we cannot change from within a lazy() function. We spread all other keys
618
+ * onto the route. Either they're meaningful to the router, or they'll get
619
+ * ignored.
620
+ */
569
621
  const immutableRouteKeys = new Set(["lazy", "caseSensitive", "path", "id", "index", "children"]);
622
+
570
623
  /**
571
624
  * lazy() function to load a route definition, which can add non-matching
572
625
  * related properties to a route
573
626
  */
574
627
 
628
+ /**
629
+ * Base RouteObject with common props shared by all types of routes
630
+ */
631
+
632
+ /**
633
+ * Index routes must not have children
634
+ */
635
+
636
+ /**
637
+ * Non-index routes may have children, but cannot have index
638
+ */
639
+
640
+ /**
641
+ * A route object represents a logical route, with (optionally) its child
642
+ * routes organized in a tree-like structure.
643
+ */
644
+
645
+ /**
646
+ * A data route object, which is just a RouteObject with a required unique ID
647
+ */
648
+
649
+ // Recursive helper for finding path parameters in the absence of wildcards
650
+
651
+ /**
652
+ * Examples:
653
+ * "/a/b/*" -> "*"
654
+ * ":a" -> "a"
655
+ * "/a/:b" -> "b"
656
+ * "/a/blahblahblah:b" -> "b"
657
+ * "/:a/:b" -> "a" | "b"
658
+ * "/:a/b/:c/*" -> "a" | "c" | "*"
659
+ */
660
+
661
+ // Attempt to parse the given string segment. If it fails, then just return the
662
+ // plain string type as a default fallback. Otherwise return the union of the
663
+ // parsed string literals that were referenced as dynamic segments in the route.
664
+ /**
665
+ * The parameters that were parsed from the URL path.
666
+ */
667
+ /**
668
+ * A RouteMatch contains info about how a route matched a URL.
669
+ */
575
670
  function isIndexRoute(route) {
576
671
  return route.index === true;
577
- } // Walk the route tree generating unique IDs where necessary so we are working
578
- // solely with AgnosticDataRouteObject's within the Router
579
-
672
+ }
580
673
 
674
+ // Walk the route tree generating unique IDs where necessary so we are working
675
+ // solely with AgnosticDataRouteObject's within the Router
581
676
  function convertRoutesToDataRoutes(routes, mapRouteProperties, parentPath, manifest) {
582
677
  if (parentPath === void 0) {
583
678
  parentPath = [];
584
679
  }
585
-
586
680
  if (manifest === void 0) {
587
681
  manifest = {};
588
682
  }
589
-
590
683
  return routes.map((route, index) => {
591
684
  let treePath = [...parentPath, index];
592
685
  let id = typeof route.id === "string" ? route.id : treePath.join("-");
593
686
  invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
594
687
  invariant(!manifest[id], "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
595
-
596
688
  if (isIndexRoute(route)) {
597
689
  let indexRoute = _extends({}, route, mapRouteProperties(route), {
598
690
  id
599
691
  });
600
-
601
692
  manifest[id] = indexRoute;
602
693
  return indexRoute;
603
694
  } else {
@@ -605,41 +696,35 @@
605
696
  id,
606
697
  children: undefined
607
698
  });
608
-
609
699
  manifest[id] = pathOrLayoutRoute;
610
-
611
700
  if (route.children) {
612
701
  pathOrLayoutRoute.children = convertRoutesToDataRoutes(route.children, mapRouteProperties, treePath, manifest);
613
702
  }
614
-
615
703
  return pathOrLayoutRoute;
616
704
  }
617
705
  });
618
706
  }
707
+
619
708
  /**
620
709
  * Matches the given routes to a location and returns the match data.
621
710
  *
622
711
  * @see https://reactrouter.com/utils/match-routes
623
712
  */
624
-
625
713
  function matchRoutes(routes, locationArg, basename) {
626
714
  if (basename === void 0) {
627
715
  basename = "/";
628
716
  }
629
-
630
717
  let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
631
718
  let pathname = stripBasename(location.pathname || "/", basename);
632
-
633
719
  if (pathname == null) {
634
720
  return null;
635
721
  }
636
-
637
722
  let branches = flattenRoutes(routes);
638
723
  rankRouteBranches(branches);
639
724
  let matches = null;
640
-
641
725
  for (let i = 0; matches == null && i < branches.length; ++i) {
642
- matches = matchRouteBranch(branches[i], // Incoming pathnames are generally encoded from either window.location
726
+ matches = matchRouteBranch(branches[i],
727
+ // Incoming pathnames are generally encoded from either window.location
643
728
  // or from router.navigate, but we want to match against the unencoded
644
729
  // paths in the route definitions. Memory router locations won't be
645
730
  // encoded here but there also shouldn't be anything to decode so this
@@ -647,23 +732,18 @@
647
732
  // history-aware.
648
733
  safelyDecodeURI(pathname));
649
734
  }
650
-
651
735
  return matches;
652
736
  }
653
-
654
737
  function flattenRoutes(routes, branches, parentsMeta, parentPath) {
655
738
  if (branches === void 0) {
656
739
  branches = [];
657
740
  }
658
-
659
741
  if (parentsMeta === void 0) {
660
742
  parentsMeta = [];
661
743
  }
662
-
663
744
  if (parentPath === void 0) {
664
745
  parentPath = "";
665
746
  }
666
-
667
747
  let flattenRoute = (route, index, relativePath) => {
668
748
  let meta = {
669
749
  relativePath: relativePath === undefined ? route.path || "" : relativePath,
@@ -671,40 +751,37 @@
671
751
  childrenIndex: index,
672
752
  route
673
753
  };
674
-
675
754
  if (meta.relativePath.startsWith("/")) {
676
755
  invariant(meta.relativePath.startsWith(parentPath), "Absolute route path \"" + meta.relativePath + "\" nested under path " + ("\"" + parentPath + "\" is not valid. An absolute child route path ") + "must start with the combined path of all its parent routes.");
677
756
  meta.relativePath = meta.relativePath.slice(parentPath.length);
678
757
  }
679
-
680
758
  let path = joinPaths([parentPath, meta.relativePath]);
681
- let routesMeta = parentsMeta.concat(meta); // Add the children before adding this route to the array so we traverse the
759
+ let routesMeta = parentsMeta.concat(meta);
760
+
761
+ // Add the children before adding this route to the array so we traverse the
682
762
  // route tree depth-first and child routes appear before their parents in
683
763
  // the "flattened" version.
684
-
685
764
  if (route.children && route.children.length > 0) {
686
- invariant( // Our types know better, but runtime JS may not!
765
+ invariant(
766
+ // Our types know better, but runtime JS may not!
687
767
  // @ts-expect-error
688
768
  route.index !== true, "Index routes must not have child routes. Please remove " + ("all child routes from route path \"" + path + "\"."));
689
769
  flattenRoutes(route.children, branches, routesMeta, path);
690
- } // Routes without a path shouldn't ever match by themselves unless they are
691
- // index routes, so don't add them to the list of possible branches.
692
-
770
+ }
693
771
 
772
+ // Routes without a path shouldn't ever match by themselves unless they are
773
+ // index routes, so don't add them to the list of possible branches.
694
774
  if (route.path == null && !route.index) {
695
775
  return;
696
776
  }
697
-
698
777
  branches.push({
699
778
  path,
700
779
  score: computeScore(path, route.index),
701
780
  routesMeta
702
781
  });
703
782
  };
704
-
705
783
  routes.forEach((route, index) => {
706
784
  var _route$path;
707
-
708
785
  // coarse-grain check for optional params
709
786
  if (route.path === "" || !((_route$path = route.path) != null && _route$path.includes("?"))) {
710
787
  flattenRoute(route, index);
@@ -716,6 +793,7 @@
716
793
  });
717
794
  return branches;
718
795
  }
796
+
719
797
  /**
720
798
  * Computes all combinations of optional path segments for a given path,
721
799
  * excluding combinations that are ambiguous and of lower priority.
@@ -730,82 +808,74 @@
730
808
  * - `/one/three/:four/:five`
731
809
  * - `/one/:two/three/:four/:five`
732
810
  */
733
-
734
-
735
811
  function explodeOptionalSegments(path) {
736
812
  let segments = path.split("/");
737
813
  if (segments.length === 0) return [];
738
- let [first, ...rest] = segments; // Optional path segments are denoted by a trailing `?`
739
-
740
- let isOptional = first.endsWith("?"); // Compute the corresponding required segment: `foo?` -> `foo`
814
+ let [first, ...rest] = segments;
741
815
 
816
+ // Optional path segments are denoted by a trailing `?`
817
+ let isOptional = first.endsWith("?");
818
+ // Compute the corresponding required segment: `foo?` -> `foo`
742
819
  let required = first.replace(/\?$/, "");
743
-
744
820
  if (rest.length === 0) {
745
821
  // Intepret empty string as omitting an optional segment
746
822
  // `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
747
823
  return isOptional ? [required, ""] : [required];
748
824
  }
749
-
750
825
  let restExploded = explodeOptionalSegments(rest.join("/"));
751
- let result = []; // All child paths with the prefix. Do this for all children before the
826
+ let result = [];
827
+
828
+ // All child paths with the prefix. Do this for all children before the
752
829
  // optional version for all children so we get consistent ordering where the
753
830
  // parent optional aspect is preferred as required. Otherwise, we can get
754
831
  // child sections interspersed where deeper optional segments are higher than
755
832
  // parent optional segments, where for example, /:two would explodes _earlier_
756
833
  // then /:one. By always including the parent as required _for all children_
757
834
  // first, we avoid this issue
835
+ result.push(...restExploded.map(subpath => subpath === "" ? required : [required, subpath].join("/")));
758
836
 
759
- result.push(...restExploded.map(subpath => subpath === "" ? required : [required, subpath].join("/"))); // Then if this is an optional value, add all child versions without
760
-
837
+ // Then if this is an optional value, add all child versions without
761
838
  if (isOptional) {
762
839
  result.push(...restExploded);
763
- } // for absolute paths, ensure `/` instead of empty segment
764
-
840
+ }
765
841
 
842
+ // for absolute paths, ensure `/` instead of empty segment
766
843
  return result.map(exploded => path.startsWith("/") && exploded === "" ? "/" : exploded);
767
844
  }
768
-
769
845
  function rankRouteBranches(branches) {
770
846
  branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
771
847
  : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
772
848
  }
773
-
774
849
  const paramRe = /^:\w+$/;
775
850
  const dynamicSegmentValue = 3;
776
851
  const indexRouteValue = 2;
777
852
  const emptySegmentValue = 1;
778
853
  const staticSegmentValue = 10;
779
854
  const splatPenalty = -2;
780
-
781
855
  const isSplat = s => s === "*";
782
-
783
856
  function computeScore(path, index) {
784
857
  let segments = path.split("/");
785
858
  let initialScore = segments.length;
786
-
787
859
  if (segments.some(isSplat)) {
788
860
  initialScore += splatPenalty;
789
861
  }
790
-
791
862
  if (index) {
792
863
  initialScore += indexRouteValue;
793
864
  }
794
-
795
865
  return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
796
866
  }
797
-
798
867
  function compareIndexes(a, b) {
799
868
  let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
800
- return siblings ? // If two routes are siblings, we should try to match the earlier sibling
869
+ return siblings ?
870
+ // If two routes are siblings, we should try to match the earlier sibling
801
871
  // first. This allows people to have fine-grained control over the matching
802
872
  // behavior by simply putting routes with identical paths in the order they
803
873
  // want them tried.
804
- a[a.length - 1] - b[b.length - 1] : // Otherwise, it doesn't really make sense to rank non-siblings by index,
874
+ a[a.length - 1] - b[b.length - 1] :
875
+ // Otherwise, it doesn't really make sense to rank non-siblings by index,
805
876
  // so they sort equally.
806
877
  0;
807
878
  }
808
-
809
879
  function matchRouteBranch(branch, pathname) {
810
880
  let {
811
881
  routesMeta
@@ -813,7 +883,6 @@
813
883
  let matchedParams = {};
814
884
  let matchedPathname = "/";
815
885
  let matches = [];
816
-
817
886
  for (let i = 0; i < routesMeta.length; ++i) {
818
887
  let meta = routesMeta[i];
819
888
  let end = i === routesMeta.length - 1;
@@ -833,72 +902,70 @@
833
902
  pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),
834
903
  route
835
904
  });
836
-
837
905
  if (match.pathnameBase !== "/") {
838
906
  matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
839
907
  }
840
908
  }
841
-
842
909
  return matches;
843
910
  }
911
+
844
912
  /**
845
913
  * Returns a path with params interpolated.
846
914
  *
847
915
  * @see https://reactrouter.com/utils/generate-path
848
916
  */
849
-
850
-
851
917
  function generatePath(originalPath, params) {
852
918
  if (params === void 0) {
853
919
  params = {};
854
920
  }
855
-
856
921
  let path = originalPath;
857
-
858
922
  if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
859
923
  warning(false, "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
860
924
  path = path.replace(/\*$/, "/*");
861
- } // ensure `/` is added at the beginning if the path is absolute
862
-
925
+ }
863
926
 
927
+ // ensure `/` is added at the beginning if the path is absolute
864
928
  const prefix = path.startsWith("/") ? "/" : "";
865
929
  const segments = path.split(/\/+/).map((segment, index, array) => {
866
- const isLastSegment = index === array.length - 1; // only apply the splat if it's the last segment
930
+ const isLastSegment = index === array.length - 1;
867
931
 
932
+ // only apply the splat if it's the last segment
868
933
  if (isLastSegment && segment === "*") {
869
934
  const star = "*";
870
- const starParam = params[star]; // Apply the splat
935
+ const starParam = params[star];
871
936
 
937
+ // Apply the splat
872
938
  return starParam;
873
939
  }
874
-
875
940
  const keyMatch = segment.match(/^:(\w+)(\??)$/);
876
-
877
941
  if (keyMatch) {
878
942
  const [, key, optional] = keyMatch;
879
943
  let param = params[key];
880
-
881
944
  if (optional === "?") {
882
945
  return param == null ? "" : param;
883
946
  }
884
-
885
947
  if (param == null) {
886
948
  invariant(false, "Missing \":" + key + "\" param");
887
949
  }
888
-
889
950
  return param;
890
- } // Remove any optional markers from optional static segments
891
-
951
+ }
892
952
 
953
+ // Remove any optional markers from optional static segments
893
954
  return segment.replace(/\?$/g, "");
894
- }) // Remove empty segments
955
+ })
956
+ // Remove empty segments
895
957
  .filter(segment => !!segment);
896
958
  return prefix + segments.join("/");
897
959
  }
960
+
898
961
  /**
899
962
  * A PathPattern is used to match on some portion of a URL pathname.
900
963
  */
901
964
 
965
+ /**
966
+ * A PathMatch contains info about how a PathPattern matched on a URL pathname.
967
+ */
968
+
902
969
  /**
903
970
  * Performs pattern matching on a URL pathname and returns information about
904
971
  * the match.
@@ -913,7 +980,6 @@
913
980
  end: true
914
981
  };
915
982
  }
916
-
917
983
  let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
918
984
  let match = pathname.match(matcher);
919
985
  if (!match) return null;
@@ -927,7 +993,6 @@
927
993
  let splatValue = captureGroups[index] || "";
928
994
  pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
929
995
  }
930
-
931
996
  memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || "", paramName);
932
997
  return memo;
933
998
  }, {});
@@ -938,16 +1003,13 @@
938
1003
  pattern
939
1004
  };
940
1005
  }
941
-
942
1006
  function compilePath(path, caseSensitive, end) {
943
1007
  if (caseSensitive === void 0) {
944
1008
  caseSensitive = false;
945
1009
  }
946
-
947
1010
  if (end === void 0) {
948
1011
  end = true;
949
1012
  }
950
-
951
1013
  warning(path === "*" || !path.endsWith("*") || path.endsWith("/*"), "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
952
1014
  let paramNames = [];
953
1015
  let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
@@ -957,7 +1019,6 @@
957
1019
  paramNames.push(paramName);
958
1020
  return "/([^\\/]+)";
959
1021
  });
960
-
961
1022
  if (path.endsWith("*")) {
962
1023
  paramNames.push("*");
963
1024
  regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
@@ -975,11 +1036,9 @@
975
1036
  // /user-preferences since `-` counts as a word boundary.
976
1037
  regexpSource += "(?:(?=\\/|$))";
977
1038
  } else ;
978
-
979
1039
  let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
980
1040
  return [matcher, paramNames];
981
1041
  }
982
-
983
1042
  function safelyDecodeURI(value) {
984
1043
  try {
985
1044
  return decodeURI(value);
@@ -988,7 +1047,6 @@
988
1047
  return value;
989
1048
  }
990
1049
  }
991
-
992
1050
  function safelyDecodeURIComponent(value, paramName) {
993
1051
  try {
994
1052
  return decodeURIComponent(value);
@@ -997,41 +1055,36 @@
997
1055
  return value;
998
1056
  }
999
1057
  }
1058
+
1000
1059
  /**
1001
1060
  * @private
1002
1061
  */
1003
-
1004
-
1005
1062
  function stripBasename(pathname, basename) {
1006
1063
  if (basename === "/") return pathname;
1007
-
1008
1064
  if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
1009
1065
  return null;
1010
- } // We want to leave trailing slash behavior in the user's control, so if they
1011
- // specify a basename with a trailing slash, we should support it
1012
-
1066
+ }
1013
1067
 
1068
+ // We want to leave trailing slash behavior in the user's control, so if they
1069
+ // specify a basename with a trailing slash, we should support it
1014
1070
  let startIndex = basename.endsWith("/") ? basename.length - 1 : basename.length;
1015
1071
  let nextChar = pathname.charAt(startIndex);
1016
-
1017
1072
  if (nextChar && nextChar !== "/") {
1018
1073
  // pathname does not start with basename/
1019
1074
  return null;
1020
1075
  }
1021
-
1022
1076
  return pathname.slice(startIndex) || "/";
1023
1077
  }
1078
+
1024
1079
  /**
1025
1080
  * Returns a resolved path object relative to the given pathname.
1026
1081
  *
1027
1082
  * @see https://reactrouter.com/utils/resolve-path
1028
1083
  */
1029
-
1030
1084
  function resolvePath(to, fromPathname) {
1031
1085
  if (fromPathname === void 0) {
1032
1086
  fromPathname = "/";
1033
1087
  }
1034
-
1035
1088
  let {
1036
1089
  pathname: toPathname,
1037
1090
  search = "",
@@ -1044,7 +1097,6 @@
1044
1097
  hash: normalizeHash(hash)
1045
1098
  };
1046
1099
  }
1047
-
1048
1100
  function resolvePathname(relativePath, fromPathname) {
1049
1101
  let segments = fromPathname.replace(/\/+$/, "").split("/");
1050
1102
  let relativeSegments = relativePath.split("/");
@@ -1058,10 +1110,10 @@
1058
1110
  });
1059
1111
  return segments.length > 1 ? segments.join("/") : "/";
1060
1112
  }
1061
-
1062
1113
  function getInvalidPathError(char, field, dest, path) {
1063
1114
  return "Cannot include a '" + char + "' character in a manually specified " + ("`to." + field + "` field [" + JSON.stringify(path) + "]. Please separate it out to the ") + ("`to." + dest + "` field. Alternatively you may provide the full path as ") + "a string in <Link to=\"...\"> and the router will parse it for you.";
1064
1115
  }
1116
+
1065
1117
  /**
1066
1118
  * @private
1067
1119
  *
@@ -1085,22 +1137,18 @@
1085
1137
  * </Route>
1086
1138
  * </Route>
1087
1139
  */
1088
-
1089
-
1090
1140
  function getPathContributingMatches(matches) {
1091
1141
  return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
1092
1142
  }
1143
+
1093
1144
  /**
1094
1145
  * @private
1095
1146
  */
1096
-
1097
1147
  function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
1098
1148
  if (isPathRelative === void 0) {
1099
1149
  isPathRelative = false;
1100
1150
  }
1101
-
1102
1151
  let to;
1103
-
1104
1152
  if (typeof toArg === "string") {
1105
1153
  to = parsePath(toArg);
1106
1154
  } else {
@@ -1109,10 +1157,11 @@
1109
1157
  invariant(!to.pathname || !to.pathname.includes("#"), getInvalidPathError("#", "pathname", "hash", to));
1110
1158
  invariant(!to.search || !to.search.includes("#"), getInvalidPathError("#", "search", "hash", to));
1111
1159
  }
1112
-
1113
1160
  let isEmptyPath = toArg === "" || to.pathname === "";
1114
1161
  let toPathname = isEmptyPath ? "/" : to.pathname;
1115
- let from; // Routing is relative to the current pathname if explicitly requested.
1162
+ let from;
1163
+
1164
+ // Routing is relative to the current pathname if explicitly requested.
1116
1165
  //
1117
1166
  // If a pathname is explicitly provided in `to`, it should be relative to the
1118
1167
  // route context. This is explained in `Note on `<Link to>` values` in our
@@ -1121,71 +1170,66 @@
1121
1170
  // `to` values that do not provide a pathname. `to` can simply be a search or
1122
1171
  // hash string, in which case we should assume that the navigation is relative
1123
1172
  // to the current location's pathname and *not* the route pathname.
1124
-
1125
1173
  if (isPathRelative || toPathname == null) {
1126
1174
  from = locationPathname;
1127
1175
  } else {
1128
1176
  let routePathnameIndex = routePathnames.length - 1;
1129
-
1130
1177
  if (toPathname.startsWith("..")) {
1131
- let toSegments = toPathname.split("/"); // Each leading .. segment means "go up one route" instead of "go up one
1178
+ let toSegments = toPathname.split("/");
1179
+
1180
+ // Each leading .. segment means "go up one route" instead of "go up one
1132
1181
  // URL segment". This is a key difference from how <a href> works and a
1133
1182
  // major reason we call this a "to" value instead of a "href".
1134
-
1135
1183
  while (toSegments[0] === "..") {
1136
1184
  toSegments.shift();
1137
1185
  routePathnameIndex -= 1;
1138
1186
  }
1139
-
1140
1187
  to.pathname = toSegments.join("/");
1141
- } // If there are more ".." segments than parent routes, resolve relative to
1142
- // the root / URL.
1143
-
1188
+ }
1144
1189
 
1190
+ // If there are more ".." segments than parent routes, resolve relative to
1191
+ // the root / URL.
1145
1192
  from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
1146
1193
  }
1194
+ let path = resolvePath(to, from);
1147
1195
 
1148
- let path = resolvePath(to, from); // Ensure the pathname has a trailing slash if the original "to" had one
1149
-
1150
- let hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/"); // Or if this was a link to the current path which has a trailing slash
1151
-
1196
+ // Ensure the pathname has a trailing slash if the original "to" had one
1197
+ let hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/");
1198
+ // Or if this was a link to the current path which has a trailing slash
1152
1199
  let hasCurrentTrailingSlash = (isEmptyPath || toPathname === ".") && locationPathname.endsWith("/");
1153
-
1154
1200
  if (!path.pathname.endsWith("/") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) {
1155
1201
  path.pathname += "/";
1156
1202
  }
1157
-
1158
1203
  return path;
1159
1204
  }
1205
+
1160
1206
  /**
1161
1207
  * @private
1162
1208
  */
1163
-
1164
1209
  function getToPathname(to) {
1165
1210
  // Empty strings should be treated the same as / paths
1166
1211
  return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
1167
1212
  }
1213
+
1168
1214
  /**
1169
1215
  * @private
1170
1216
  */
1171
-
1172
1217
  const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
1218
+
1173
1219
  /**
1174
1220
  * @private
1175
1221
  */
1176
-
1177
1222
  const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
1223
+
1178
1224
  /**
1179
1225
  * @private
1180
1226
  */
1181
-
1182
1227
  const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
1228
+
1183
1229
  /**
1184
1230
  * @private
1185
1231
  */
1186
-
1187
1232
  const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
1188
-
1189
1233
  /**
1190
1234
  * This is a shortcut for creating `application/json` responses. Converts `data`
1191
1235
  * to JSON and sets the `Content-Type` header.
@@ -1194,16 +1238,13 @@
1194
1238
  if (init === void 0) {
1195
1239
  init = {};
1196
1240
  }
1197
-
1198
1241
  let responseInit = typeof init === "number" ? {
1199
1242
  status: init
1200
1243
  } : init;
1201
1244
  let headers = new Headers(responseInit.headers);
1202
-
1203
1245
  if (!headers.has("Content-Type")) {
1204
1246
  headers.set("Content-Type", "application/json; charset=utf-8");
1205
1247
  }
1206
-
1207
1248
  return new Response(JSON.stringify(data), _extends({}, responseInit, {
1208
1249
  headers
1209
1250
  }));
@@ -1214,17 +1255,15 @@
1214
1255
  this.pendingKeysSet = new Set();
1215
1256
  this.subscribers = new Set();
1216
1257
  this.deferredKeys = [];
1217
- invariant(data && typeof data === "object" && !Array.isArray(data), "defer() only accepts plain objects"); // Set up an AbortController + Promise we can race against to exit early
1218
- // cancellation
1258
+ invariant(data && typeof data === "object" && !Array.isArray(data), "defer() only accepts plain objects");
1219
1259
 
1260
+ // Set up an AbortController + Promise we can race against to exit early
1261
+ // cancellation
1220
1262
  let reject;
1221
1263
  this.abortPromise = new Promise((_, r) => reject = r);
1222
1264
  this.controller = new AbortController();
1223
-
1224
1265
  let onAbort = () => reject(new AbortedDeferredError("Deferred data aborted"));
1225
-
1226
1266
  this.unlistenAbortSignal = () => this.controller.signal.removeEventListener("abort", onAbort);
1227
-
1228
1267
  this.controller.signal.addEventListener("abort", onAbort);
1229
1268
  this.data = Object.entries(data).reduce((acc, _ref) => {
1230
1269
  let [key, value] = _ref;
@@ -1232,34 +1271,31 @@
1232
1271
  [key]: this.trackPromise(key, value)
1233
1272
  });
1234
1273
  }, {});
1235
-
1236
1274
  if (this.done) {
1237
1275
  // All incoming values were resolved
1238
1276
  this.unlistenAbortSignal();
1239
1277
  }
1240
-
1241
1278
  this.init = responseInit;
1242
1279
  }
1243
-
1244
1280
  trackPromise(key, value) {
1245
1281
  if (!(value instanceof Promise)) {
1246
1282
  return value;
1247
1283
  }
1248
-
1249
1284
  this.deferredKeys.push(key);
1250
- this.pendingKeysSet.add(key); // We store a little wrapper promise that will be extended with
1285
+ this.pendingKeysSet.add(key);
1286
+
1287
+ // We store a little wrapper promise that will be extended with
1251
1288
  // _data/_error props upon resolve/reject
1289
+ let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, null, data), error => this.onSettle(promise, key, error));
1252
1290
 
1253
- let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, null, data), error => this.onSettle(promise, key, error)); // Register rejection listeners to avoid uncaught promise rejections on
1291
+ // Register rejection listeners to avoid uncaught promise rejections on
1254
1292
  // errors or aborted deferred values
1255
-
1256
1293
  promise.catch(() => {});
1257
1294
  Object.defineProperty(promise, "_tracked", {
1258
1295
  get: () => true
1259
1296
  });
1260
1297
  return promise;
1261
1298
  }
1262
-
1263
1299
  onSettle(promise, key, error, data) {
1264
1300
  if (this.controller.signal.aborted && error instanceof AbortedDeferredError) {
1265
1301
  this.unlistenAbortSignal();
@@ -1268,14 +1304,11 @@
1268
1304
  });
1269
1305
  return Promise.reject(error);
1270
1306
  }
1271
-
1272
1307
  this.pendingKeysSet.delete(key);
1273
-
1274
1308
  if (this.done) {
1275
1309
  // Nothing left to abort!
1276
1310
  this.unlistenAbortSignal();
1277
1311
  }
1278
-
1279
1312
  if (error) {
1280
1313
  Object.defineProperty(promise, "_error", {
1281
1314
  get: () => error
@@ -1283,54 +1316,43 @@
1283
1316
  this.emit(false, key);
1284
1317
  return Promise.reject(error);
1285
1318
  }
1286
-
1287
1319
  Object.defineProperty(promise, "_data", {
1288
1320
  get: () => data
1289
1321
  });
1290
1322
  this.emit(false, key);
1291
1323
  return data;
1292
1324
  }
1293
-
1294
1325
  emit(aborted, settledKey) {
1295
1326
  this.subscribers.forEach(subscriber => subscriber(aborted, settledKey));
1296
1327
  }
1297
-
1298
1328
  subscribe(fn) {
1299
1329
  this.subscribers.add(fn);
1300
1330
  return () => this.subscribers.delete(fn);
1301
1331
  }
1302
-
1303
1332
  cancel() {
1304
1333
  this.controller.abort();
1305
1334
  this.pendingKeysSet.forEach((v, k) => this.pendingKeysSet.delete(k));
1306
1335
  this.emit(true);
1307
1336
  }
1308
-
1309
1337
  async resolveData(signal) {
1310
1338
  let aborted = false;
1311
-
1312
1339
  if (!this.done) {
1313
1340
  let onAbort = () => this.cancel();
1314
-
1315
1341
  signal.addEventListener("abort", onAbort);
1316
1342
  aborted = await new Promise(resolve => {
1317
1343
  this.subscribe(aborted => {
1318
1344
  signal.removeEventListener("abort", onAbort);
1319
-
1320
1345
  if (aborted || this.done) {
1321
1346
  resolve(aborted);
1322
1347
  }
1323
1348
  });
1324
1349
  });
1325
1350
  }
1326
-
1327
1351
  return aborted;
1328
1352
  }
1329
-
1330
1353
  get done() {
1331
1354
  return this.pendingKeysSet.size === 0;
1332
1355
  }
1333
-
1334
1356
  get unwrappedData() {
1335
1357
  invariant(this.data !== null && this.done, "Can only unwrap data on initialized and settled deferreds");
1336
1358
  return Object.entries(this.data).reduce((acc, _ref2) => {
@@ -1340,40 +1362,31 @@
1340
1362
  });
1341
1363
  }, {});
1342
1364
  }
1343
-
1344
1365
  get pendingKeys() {
1345
1366
  return Array.from(this.pendingKeysSet);
1346
1367
  }
1347
-
1348
1368
  }
1349
-
1350
1369
  function isTrackedPromise(value) {
1351
1370
  return value instanceof Promise && value._tracked === true;
1352
1371
  }
1353
-
1354
1372
  function unwrapTrackedPromise(value) {
1355
1373
  if (!isTrackedPromise(value)) {
1356
1374
  return value;
1357
1375
  }
1358
-
1359
1376
  if (value._error) {
1360
1377
  throw value._error;
1361
1378
  }
1362
-
1363
1379
  return value._data;
1364
1380
  }
1365
-
1366
1381
  const defer = function defer(data, init) {
1367
1382
  if (init === void 0) {
1368
1383
  init = {};
1369
1384
  }
1370
-
1371
1385
  let responseInit = typeof init === "number" ? {
1372
1386
  status: init
1373
1387
  } : init;
1374
1388
  return new DeferredData(data, responseInit);
1375
1389
  };
1376
-
1377
1390
  /**
1378
1391
  * A redirect response. Sets the status code and the `Location` header.
1379
1392
  * Defaults to "302 Found".
@@ -1382,9 +1395,7 @@
1382
1395
  if (init === void 0) {
1383
1396
  init = 302;
1384
1397
  }
1385
-
1386
1398
  let responseInit = init;
1387
-
1388
1399
  if (typeof responseInit === "number") {
1389
1400
  responseInit = {
1390
1401
  status: responseInit
@@ -1392,28 +1403,25 @@
1392
1403
  } else if (typeof responseInit.status === "undefined") {
1393
1404
  responseInit.status = 302;
1394
1405
  }
1395
-
1396
1406
  let headers = new Headers(responseInit.headers);
1397
1407
  headers.set("Location", url);
1398
1408
  return new Response(null, _extends({}, responseInit, {
1399
1409
  headers
1400
1410
  }));
1401
1411
  };
1412
+
1402
1413
  /**
1403
1414
  * @private
1404
1415
  * Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
1405
1416
  */
1406
-
1407
1417
  class ErrorResponse {
1408
1418
  constructor(status, statusText, data, internal) {
1409
1419
  if (internal === void 0) {
1410
1420
  internal = false;
1411
1421
  }
1412
-
1413
1422
  this.status = status;
1414
1423
  this.statusText = statusText || "";
1415
1424
  this.internal = internal;
1416
-
1417
1425
  if (data instanceof Error) {
1418
1426
  this.data = data.toString();
1419
1427
  this.error = data;
@@ -1421,24 +1429,82 @@
1421
1429
  this.data = data;
1422
1430
  }
1423
1431
  }
1424
-
1425
1432
  }
1433
+
1426
1434
  /**
1427
1435
  * Check if the given error is an ErrorResponse generated from a 4xx/5xx
1428
1436
  * Response thrown from an action/loader
1429
1437
  */
1430
-
1431
1438
  function isRouteErrorResponse(error) {
1432
1439
  return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
1433
1440
  }
1434
1441
 
1442
+ ////////////////////////////////////////////////////////////////////////////////
1435
1443
  //#region Types and Constants
1436
1444
  ////////////////////////////////////////////////////////////////////////////////
1437
1445
 
1438
1446
  /**
1439
1447
  * A Router instance manages all navigation and data loading/mutations
1440
1448
  */
1441
-
1449
+ /**
1450
+ * State maintained internally by the router. During a navigation, all states
1451
+ * reflect the the "old" location unless otherwise noted.
1452
+ */
1453
+ /**
1454
+ * Data that can be passed into hydrate a Router from SSR
1455
+ */
1456
+ /**
1457
+ * Future flags to toggle new feature behavior
1458
+ */
1459
+ /**
1460
+ * Initialization options for createRouter
1461
+ */
1462
+ /**
1463
+ * State returned from a server-side query() call
1464
+ */
1465
+ /**
1466
+ * A StaticHandler instance manages a singular SSR navigation/fetch event
1467
+ */
1468
+ /**
1469
+ * Subscriber function signature for changes to router state
1470
+ */
1471
+ /**
1472
+ * Function signature for determining the key to be used in scroll restoration
1473
+ * for a given location
1474
+ */
1475
+ /**
1476
+ * Function signature for determining the current scroll position
1477
+ */
1478
+ /**
1479
+ * Options for a navigate() call for a Link navigation
1480
+ */
1481
+ /**
1482
+ * Options for a navigate() call for a Form navigation
1483
+ */
1484
+ /**
1485
+ * Options to pass to navigate() for either a Link or Form navigation
1486
+ */
1487
+ /**
1488
+ * Options to pass to fetch()
1489
+ */
1490
+ /**
1491
+ * Potential states for state.navigation
1492
+ */
1493
+ /**
1494
+ * Potential states for fetchers
1495
+ */
1496
+ /**
1497
+ * Cached info for active fetcher.load() instances so they can participate
1498
+ * in revalidation
1499
+ */
1500
+ /**
1501
+ * Identified fetcher.load() calls that need to be revalidated
1502
+ */
1503
+ /**
1504
+ * Wrapper object to allow us to throw any response out from callLoaderOrAction
1505
+ * for queryRouter while preserving whether or not it was thrown or returned
1506
+ * from the loader/action
1507
+ */
1442
1508
  const validMutationMethodsArr = ["post", "put", "patch", "delete"];
1443
1509
  const validMutationMethods = new Set(validMutationMethodsArr);
1444
1510
  const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
@@ -1468,12 +1534,12 @@
1468
1534
  location: undefined
1469
1535
  };
1470
1536
  const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
1471
- const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
1472
- const isServer = !isBrowser;
1473
-
1474
1537
  const defaultMapRouteProperties = route => ({
1475
1538
  hasErrorBoundary: Boolean(route.hasErrorBoundary)
1476
- }); //#endregion
1539
+ });
1540
+
1541
+ //#endregion
1542
+
1477
1543
  ////////////////////////////////////////////////////////////////////////////////
1478
1544
  //#region createRouter
1479
1545
  ////////////////////////////////////////////////////////////////////////////////
@@ -1481,57 +1547,54 @@
1481
1547
  /**
1482
1548
  * Create a router and listen to history POP navigations
1483
1549
  */
1484
-
1485
-
1486
1550
  function createRouter(init) {
1551
+ const routerWindow = init.window ? init.window : typeof window !== "undefined" ? window : undefined;
1552
+ const isBrowser = typeof routerWindow !== "undefined" && typeof routerWindow.document !== "undefined" && typeof routerWindow.document.createElement !== "undefined";
1553
+ const isServer = !isBrowser;
1487
1554
  invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
1488
1555
  let mapRouteProperties;
1489
-
1490
1556
  if (init.mapRouteProperties) {
1491
1557
  mapRouteProperties = init.mapRouteProperties;
1492
1558
  } else if (init.detectErrorBoundary) {
1493
1559
  // If they are still using the deprecated version, wrap it with the new API
1494
1560
  let detectErrorBoundary = init.detectErrorBoundary;
1495
-
1496
1561
  mapRouteProperties = route => ({
1497
1562
  hasErrorBoundary: detectErrorBoundary(route)
1498
1563
  });
1499
1564
  } else {
1500
1565
  mapRouteProperties = defaultMapRouteProperties;
1501
- } // Routes keyed by ID
1502
-
1503
-
1504
- let manifest = {}; // Routes in tree format for matching
1566
+ }
1505
1567
 
1568
+ // Routes keyed by ID
1569
+ let manifest = {};
1570
+ // Routes in tree format for matching
1506
1571
  let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
1507
1572
  let inFlightDataRoutes;
1508
- let basename = init.basename || "/"; // Config driven behavior flags
1509
-
1573
+ let basename = init.basename || "/";
1574
+ // Config driven behavior flags
1510
1575
  let future = _extends({
1511
1576
  v7_normalizeFormMethod: false,
1512
1577
  v7_prependBasename: false
1513
- }, init.future); // Cleanup function for history
1514
-
1515
-
1516
- let unlistenHistory = null; // Externally-provided functions to call on all state changes
1517
-
1518
- let subscribers = new Set(); // Externally-provided object to hold scroll restoration locations during routing
1519
-
1520
- let savedScrollPositions = null; // Externally-provided function to get scroll restoration keys
1521
-
1522
- let getScrollRestorationKey = null; // Externally-provided function to get current scroll position
1523
-
1524
- let getScrollPosition = null; // One-time flag to control the initial hydration scroll restoration. Because
1578
+ }, init.future);
1579
+ // Cleanup function for history
1580
+ let unlistenHistory = null;
1581
+ // Externally-provided functions to call on all state changes
1582
+ let subscribers = new Set();
1583
+ // Externally-provided object to hold scroll restoration locations during routing
1584
+ let savedScrollPositions = null;
1585
+ // Externally-provided function to get scroll restoration keys
1586
+ let getScrollRestorationKey = null;
1587
+ // Externally-provided function to get current scroll position
1588
+ let getScrollPosition = null;
1589
+ // One-time flag to control the initial hydration scroll restoration. Because
1525
1590
  // we don't get the saved positions from <ScrollRestoration /> until _after_
1526
1591
  // the initial render, we need to manually trigger a separate updateState to
1527
1592
  // send along the restoreScrollPosition
1528
1593
  // Set to true if we have `hydrationData` since we assume we were SSR'd and that
1529
1594
  // SSR did the initial scroll restoration.
1530
-
1531
1595
  let initialScrollRestored = init.hydrationData != null;
1532
1596
  let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
1533
1597
  let initialErrors = null;
1534
-
1535
1598
  if (initialMatches == null) {
1536
1599
  // If we do not match a user-provided-route, fall back to the root
1537
1600
  // to allow the error boundary to take over
@@ -1547,10 +1610,11 @@
1547
1610
  [route.id]: error
1548
1611
  };
1549
1612
  }
1550
-
1551
- let initialized = // All initialMatches need to be loaded before we're ready. If we have lazy
1613
+ let initialized =
1614
+ // All initialMatches need to be loaded before we're ready. If we have lazy
1552
1615
  // functions around still then we'll need to run them in initialize()
1553
- !initialMatches.some(m => m.route.lazy) && ( // And we have to either have no loaders or have been provided hydrationData
1616
+ !initialMatches.some(m => m.route.lazy) && (
1617
+ // And we have to either have no loaders or have been provided hydrationData
1554
1618
  !initialMatches.some(m => m.route.loader) || init.hydrationData != null);
1555
1619
  let router;
1556
1620
  let state = {
@@ -1568,57 +1632,74 @@
1568
1632
  errors: init.hydrationData && init.hydrationData.errors || initialErrors,
1569
1633
  fetchers: new Map(),
1570
1634
  blockers: new Map()
1571
- }; // -- Stateful internal variables to manage navigations --
1635
+ };
1636
+
1637
+ // -- Stateful internal variables to manage navigations --
1572
1638
  // Current navigation in progress (to be committed in completeNavigation)
1639
+ let pendingAction = exports.Action.Pop;
1573
1640
 
1574
- let pendingAction = exports.Action.Pop; // Should the current navigation prevent the scroll reset if scroll cannot
1641
+ // Should the current navigation prevent the scroll reset if scroll cannot
1575
1642
  // be restored?
1643
+ let pendingPreventScrollReset = false;
1576
1644
 
1577
- let pendingPreventScrollReset = false; // AbortController for the active navigation
1645
+ // AbortController for the active navigation
1646
+ let pendingNavigationController;
1578
1647
 
1579
- let pendingNavigationController; // We use this to avoid touching history in completeNavigation if a
1648
+ // We use this to avoid touching history in completeNavigation if a
1580
1649
  // revalidation is entirely uninterrupted
1650
+ let isUninterruptedRevalidation = false;
1581
1651
 
1582
- let isUninterruptedRevalidation = false; // Use this internal flag to force revalidation of all loaders:
1652
+ // Use this internal flag to force revalidation of all loaders:
1583
1653
  // - submissions (completed or interrupted)
1584
1654
  // - useRevalidator()
1585
1655
  // - X-Remix-Revalidate (from redirect)
1656
+ let isRevalidationRequired = false;
1586
1657
 
1587
- let isRevalidationRequired = false; // Use this internal array to capture routes that require revalidation due
1658
+ // Use this internal array to capture routes that require revalidation due
1588
1659
  // to a cancelled deferred on action submission
1660
+ let cancelledDeferredRoutes = [];
1589
1661
 
1590
- let cancelledDeferredRoutes = []; // Use this internal array to capture fetcher loads that were cancelled by an
1662
+ // Use this internal array to capture fetcher loads that were cancelled by an
1591
1663
  // action navigation and require revalidation
1664
+ let cancelledFetcherLoads = [];
1592
1665
 
1593
- let cancelledFetcherLoads = []; // AbortControllers for any in-flight fetchers
1666
+ // AbortControllers for any in-flight fetchers
1667
+ let fetchControllers = new Map();
1594
1668
 
1595
- let fetchControllers = new Map(); // Track loads based on the order in which they started
1669
+ // Track loads based on the order in which they started
1670
+ let incrementingLoadId = 0;
1596
1671
 
1597
- let incrementingLoadId = 0; // Track the outstanding pending navigation data load to be compared against
1672
+ // Track the outstanding pending navigation data load to be compared against
1598
1673
  // the globally incrementing load when a fetcher load lands after a completed
1599
1674
  // navigation
1675
+ let pendingNavigationLoadId = -1;
1600
1676
 
1601
- let pendingNavigationLoadId = -1; // Fetchers that triggered data reloads as a result of their actions
1677
+ // Fetchers that triggered data reloads as a result of their actions
1678
+ let fetchReloadIds = new Map();
1602
1679
 
1603
- let fetchReloadIds = new Map(); // Fetchers that triggered redirect navigations
1680
+ // Fetchers that triggered redirect navigations
1681
+ let fetchRedirectIds = new Set();
1604
1682
 
1605
- let fetchRedirectIds = new Set(); // Most recent href/match for fetcher.load calls for fetchers
1683
+ // Most recent href/match for fetcher.load calls for fetchers
1684
+ let fetchLoadMatches = new Map();
1606
1685
 
1607
- let fetchLoadMatches = new Map(); // Store DeferredData instances for active route matches. When a
1686
+ // Store DeferredData instances for active route matches. When a
1608
1687
  // route loader returns defer() we stick one in here. Then, when a nested
1609
1688
  // promise resolves we update loaderData. If a new navigation starts we
1610
1689
  // cancel active deferreds for eliminated routes.
1690
+ let activeDeferreds = new Map();
1611
1691
 
1612
- let activeDeferreds = new Map(); // Store blocker functions in a separate Map outside of router state since
1692
+ // Store blocker functions in a separate Map outside of router state since
1613
1693
  // we don't need to update UI state if they change
1694
+ let blockerFunctions = new Map();
1614
1695
 
1615
- let blockerFunctions = new Map(); // Flag to ignore the next history update, so we can revert the URL change on
1696
+ // Flag to ignore the next history update, so we can revert the URL change on
1616
1697
  // a POP navigation that was blocked by the user without touching router state
1698
+ let ignoreNextHistoryUpdate = false;
1617
1699
 
1618
- let ignoreNextHistoryUpdate = false; // Initialize the router, all side effects should be kicked off from here.
1700
+ // Initialize the router, all side effects should be kicked off from here.
1619
1701
  // Implemented as a Fluent API for ease of:
1620
1702
  // let router = createRouter(init).initialize();
1621
-
1622
1703
  function initialize() {
1623
1704
  // If history informs us of a POP navigation, start the navigation but do not update
1624
1705
  // state. We'll update our own state once the navigation completes
@@ -1628,98 +1709,90 @@
1628
1709
  location,
1629
1710
  delta
1630
1711
  } = _ref;
1631
-
1632
1712
  // Ignore this event if it was just us resetting the URL from a
1633
1713
  // blocked POP navigation
1634
1714
  if (ignoreNextHistoryUpdate) {
1635
1715
  ignoreNextHistoryUpdate = false;
1636
1716
  return;
1637
1717
  }
1638
-
1639
1718
  warning(blockerFunctions.size === 0 || delta != null, "You are trying to use a blocker on a POP navigation to a location " + "that was not created by @remix-run/router. This will fail silently in " + "production. This can happen if you are navigating outside the router " + "via `window.history.pushState`/`window.location.hash` instead of using " + "router navigation APIs. This can also happen if you are using " + "createHashRouter and the user manually changes the URL.");
1640
1719
  let blockerKey = shouldBlockNavigation({
1641
1720
  currentLocation: state.location,
1642
1721
  nextLocation: location,
1643
1722
  historyAction
1644
1723
  });
1645
-
1646
1724
  if (blockerKey && delta != null) {
1647
1725
  // Restore the URL to match the current UI, but don't update router state
1648
1726
  ignoreNextHistoryUpdate = true;
1649
- init.history.go(delta * -1); // Put the blocker into a blocked state
1727
+ init.history.go(delta * -1);
1650
1728
 
1729
+ // Put the blocker into a blocked state
1651
1730
  updateBlocker(blockerKey, {
1652
1731
  state: "blocked",
1653
1732
  location,
1654
-
1655
1733
  proceed() {
1656
1734
  updateBlocker(blockerKey, {
1657
1735
  state: "proceeding",
1658
1736
  proceed: undefined,
1659
1737
  reset: undefined,
1660
1738
  location
1661
- }); // Re-do the same POP navigation we just blocked
1662
-
1739
+ });
1740
+ // Re-do the same POP navigation we just blocked
1663
1741
  init.history.go(delta);
1664
1742
  },
1665
-
1666
1743
  reset() {
1667
1744
  deleteBlocker(blockerKey);
1668
1745
  updateState({
1669
1746
  blockers: new Map(router.state.blockers)
1670
1747
  });
1671
1748
  }
1672
-
1673
1749
  });
1674
1750
  return;
1675
1751
  }
1676
-
1677
1752
  return startNavigation(historyAction, location);
1678
- }); // Kick off initial data load if needed. Use Pop to avoid modifying history
1753
+ });
1754
+
1755
+ // Kick off initial data load if needed. Use Pop to avoid modifying history
1679
1756
  // Note we don't do any handling of lazy here. For SPA's it'll get handled
1680
1757
  // in the normal navigation flow. For SSR it's expected that lazy modules are
1681
1758
  // resolved prior to router creation since we can't go into a fallbackElement
1682
1759
  // UI for SSR'd apps
1683
-
1684
1760
  if (!state.initialized) {
1685
1761
  startNavigation(exports.Action.Pop, state.location);
1686
1762
  }
1687
-
1688
1763
  return router;
1689
- } // Clean up a router and it's side effects
1690
-
1764
+ }
1691
1765
 
1766
+ // Clean up a router and it's side effects
1692
1767
  function dispose() {
1693
1768
  if (unlistenHistory) {
1694
1769
  unlistenHistory();
1695
1770
  }
1696
-
1697
1771
  subscribers.clear();
1698
1772
  pendingNavigationController && pendingNavigationController.abort();
1699
1773
  state.fetchers.forEach((_, key) => deleteFetcher(key));
1700
1774
  state.blockers.forEach((_, key) => deleteBlocker(key));
1701
- } // Subscribe to state updates for the router
1702
-
1775
+ }
1703
1776
 
1777
+ // Subscribe to state updates for the router
1704
1778
  function subscribe(fn) {
1705
1779
  subscribers.add(fn);
1706
1780
  return () => subscribers.delete(fn);
1707
- } // Update our state and notify the calling context of the change
1708
-
1781
+ }
1709
1782
 
1783
+ // Update our state and notify the calling context of the change
1710
1784
  function updateState(newState) {
1711
1785
  state = _extends({}, state, newState);
1712
1786
  subscribers.forEach(subscriber => subscriber(state));
1713
- } // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
1787
+ }
1788
+
1789
+ // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
1714
1790
  // and setting state.[historyAction/location/matches] to the new route.
1715
1791
  // - Location is a required param
1716
1792
  // - Navigation will always be set to IDLE_NAVIGATION
1717
1793
  // - Can pass any other state in newState
1718
-
1719
-
1720
1794
  function completeNavigation(location, newState) {
1721
1795
  var _location$state, _location$state2;
1722
-
1723
1796
  // Deduce if we're in a loading/actionReload state:
1724
1797
  // - We have committed actionData in the store
1725
1798
  // - The current navigation was a mutation submission
@@ -1727,7 +1800,6 @@
1727
1800
  // - The location being loaded is not the result of a redirect
1728
1801
  let isActionReload = state.actionData != null && state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && state.navigation.state === "loading" && ((_location$state = location.state) == null ? void 0 : _location$state._isRedirect) !== true;
1729
1802
  let actionData;
1730
-
1731
1803
  if (newState.actionData) {
1732
1804
  if (Object.keys(newState.actionData).length > 0) {
1733
1805
  actionData = newState.actionData;
@@ -1741,25 +1813,24 @@
1741
1813
  } else {
1742
1814
  // Clear actionData on any other completed navigations
1743
1815
  actionData = null;
1744
- } // Always preserve any existing loaderData from re-used routes
1816
+ }
1745
1817
 
1818
+ // Always preserve any existing loaderData from re-used routes
1819
+ let loaderData = newState.loaderData ? mergeLoaderData(state.loaderData, newState.loaderData, newState.matches || [], newState.errors) : state.loaderData;
1746
1820
 
1747
- let loaderData = newState.loaderData ? mergeLoaderData(state.loaderData, newState.loaderData, newState.matches || [], newState.errors) : state.loaderData; // On a successful navigation we can assume we got through all blockers
1821
+ // On a successful navigation we can assume we got through all blockers
1748
1822
  // so we can start fresh
1749
-
1750
1823
  for (let [key] of blockerFunctions) {
1751
1824
  deleteBlocker(key);
1752
- } // Always respect the user flag. Otherwise don't reset on mutation
1753
- // submission navigations unless they redirect
1754
-
1825
+ }
1755
1826
 
1827
+ // Always respect the user flag. Otherwise don't reset on mutation
1828
+ // submission navigations unless they redirect
1756
1829
  let preventScrollReset = pendingPreventScrollReset === true || state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && ((_location$state2 = location.state) == null ? void 0 : _location$state2._isRedirect) !== true;
1757
-
1758
1830
  if (inFlightDataRoutes) {
1759
1831
  dataRoutes = inFlightDataRoutes;
1760
1832
  inFlightDataRoutes = undefined;
1761
1833
  }
1762
-
1763
1834
  updateState(_extends({}, newState, {
1764
1835
  // matches, errors, fetchers go through as-is
1765
1836
  actionData,
@@ -1773,30 +1844,28 @@
1773
1844
  preventScrollReset,
1774
1845
  blockers: new Map(state.blockers)
1775
1846
  }));
1776
-
1777
1847
  if (isUninterruptedRevalidation) ; else if (pendingAction === exports.Action.Pop) ; else if (pendingAction === exports.Action.Push) {
1778
1848
  init.history.push(location, location.state);
1779
1849
  } else if (pendingAction === exports.Action.Replace) {
1780
1850
  init.history.replace(location, location.state);
1781
- } // Reset stateful navigation vars
1782
-
1851
+ }
1783
1852
 
1853
+ // Reset stateful navigation vars
1784
1854
  pendingAction = exports.Action.Pop;
1785
1855
  pendingPreventScrollReset = false;
1786
1856
  isUninterruptedRevalidation = false;
1787
1857
  isRevalidationRequired = false;
1788
1858
  cancelledDeferredRoutes = [];
1789
1859
  cancelledFetcherLoads = [];
1790
- } // Trigger a navigation event, which can either be a numerical POP or a PUSH
1791
- // replace with an optional submission
1792
-
1860
+ }
1793
1861
 
1862
+ // Trigger a navigation event, which can either be a numerical POP or a PUSH
1863
+ // replace with an optional submission
1794
1864
  async function navigate(to, opts) {
1795
1865
  if (typeof to === "number") {
1796
1866
  init.history.go(to);
1797
1867
  return;
1798
1868
  }
1799
-
1800
1869
  let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, to, opts == null ? void 0 : opts.fromRouteId, opts == null ? void 0 : opts.relative);
1801
1870
  let {
1802
1871
  path,
@@ -1804,16 +1873,16 @@
1804
1873
  error
1805
1874
  } = normalizeNavigateOptions(future.v7_normalizeFormMethod, false, normalizedPath, opts);
1806
1875
  let currentLocation = state.location;
1807
- let nextLocation = createLocation(state.location, path, opts && opts.state); // When using navigate as a PUSH/REPLACE we aren't reading an already-encoded
1876
+ let nextLocation = createLocation(state.location, path, opts && opts.state);
1877
+
1878
+ // When using navigate as a PUSH/REPLACE we aren't reading an already-encoded
1808
1879
  // URL from window.location, so we need to encode it here so the behavior
1809
1880
  // remains the same as POP and non-data-router usages. new URL() does all
1810
1881
  // the same encoding we'd get from a history.pushState/window.location read
1811
1882
  // without having to touch history
1812
-
1813
1883
  nextLocation = _extends({}, nextLocation, init.history.encodeLocation(nextLocation));
1814
1884
  let userReplace = opts && opts.replace != null ? opts.replace : undefined;
1815
1885
  let historyAction = exports.Action.Push;
1816
-
1817
1886
  if (userReplace === true) {
1818
1887
  historyAction = exports.Action.Replace;
1819
1888
  } else if (userReplace === false) ; else if (submission != null && isMutationMethod(submission.formMethod) && submission.formAction === state.location.pathname + state.location.search) {
@@ -1823,42 +1892,36 @@
1823
1892
  // action/loader this will be ignored and the redirect will be a PUSH
1824
1893
  historyAction = exports.Action.Replace;
1825
1894
  }
1826
-
1827
1895
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
1828
1896
  let blockerKey = shouldBlockNavigation({
1829
1897
  currentLocation,
1830
1898
  nextLocation,
1831
1899
  historyAction
1832
1900
  });
1833
-
1834
1901
  if (blockerKey) {
1835
1902
  // Put the blocker into a blocked state
1836
1903
  updateBlocker(blockerKey, {
1837
1904
  state: "blocked",
1838
1905
  location: nextLocation,
1839
-
1840
1906
  proceed() {
1841
1907
  updateBlocker(blockerKey, {
1842
1908
  state: "proceeding",
1843
1909
  proceed: undefined,
1844
1910
  reset: undefined,
1845
1911
  location: nextLocation
1846
- }); // Send the same navigation through
1847
-
1912
+ });
1913
+ // Send the same navigation through
1848
1914
  navigate(to, opts);
1849
1915
  },
1850
-
1851
1916
  reset() {
1852
1917
  deleteBlocker(blockerKey);
1853
1918
  updateState({
1854
1919
  blockers: new Map(state.blockers)
1855
1920
  });
1856
1921
  }
1857
-
1858
1922
  });
1859
1923
  return;
1860
1924
  }
1861
-
1862
1925
  return await startNavigation(historyAction, nextLocation, {
1863
1926
  submission,
1864
1927
  // Send through the formData serialization error if we have one so we can
@@ -1867,43 +1930,44 @@
1867
1930
  preventScrollReset,
1868
1931
  replace: opts && opts.replace
1869
1932
  });
1870
- } // Revalidate all current loaders. If a navigation is in progress or if this
1933
+ }
1934
+
1935
+ // Revalidate all current loaders. If a navigation is in progress or if this
1871
1936
  // is interrupted by a navigation, allow this to "succeed" by calling all
1872
1937
  // loaders during the next loader round
1873
-
1874
-
1875
1938
  function revalidate() {
1876
1939
  interruptActiveLoads();
1877
1940
  updateState({
1878
1941
  revalidation: "loading"
1879
- }); // If we're currently submitting an action, we don't need to start a new
1880
- // navigation, we'll just let the follow up loader execution call all loaders
1942
+ });
1881
1943
 
1944
+ // If we're currently submitting an action, we don't need to start a new
1945
+ // navigation, we'll just let the follow up loader execution call all loaders
1882
1946
  if (state.navigation.state === "submitting") {
1883
1947
  return;
1884
- } // If we're currently in an idle state, start a new navigation for the current
1948
+ }
1949
+
1950
+ // If we're currently in an idle state, start a new navigation for the current
1885
1951
  // action/location and mark it as uninterrupted, which will skip the history
1886
1952
  // update in completeNavigation
1887
-
1888
-
1889
1953
  if (state.navigation.state === "idle") {
1890
1954
  startNavigation(state.historyAction, state.location, {
1891
1955
  startUninterruptedRevalidation: true
1892
1956
  });
1893
1957
  return;
1894
- } // Otherwise, if we're currently in a loading state, just start a new
1958
+ }
1959
+
1960
+ // Otherwise, if we're currently in a loading state, just start a new
1895
1961
  // navigation to the navigation.location but do not trigger an uninterrupted
1896
1962
  // revalidation so that history correctly updates once the navigation completes
1897
-
1898
-
1899
1963
  startNavigation(pendingAction || state.historyAction, state.navigation.location, {
1900
1964
  overrideNavigation: state.navigation
1901
1965
  });
1902
- } // Start a navigation to the given action/location. Can optionally provide a
1966
+ }
1967
+
1968
+ // Start a navigation to the given action/location. Can optionally provide a
1903
1969
  // overrideNavigation which will override the normalLoad in the case of a redirect
1904
1970
  // navigation
1905
-
1906
-
1907
1971
  async function startNavigation(historyAction, location, opts) {
1908
1972
  // Abort any in-progress navigations and start a new one. Unset any ongoing
1909
1973
  // uninterrupted revalidations unless told otherwise, since we want this
@@ -1911,15 +1975,17 @@
1911
1975
  pendingNavigationController && pendingNavigationController.abort();
1912
1976
  pendingNavigationController = null;
1913
1977
  pendingAction = historyAction;
1914
- isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true; // Save the current scroll position every time we start a new navigation,
1915
- // and track whether we should reset scroll on completion
1978
+ isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true;
1916
1979
 
1980
+ // Save the current scroll position every time we start a new navigation,
1981
+ // and track whether we should reset scroll on completion
1917
1982
  saveScrollPosition(state.location, state.matches);
1918
1983
  pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
1919
1984
  let routesToUse = inFlightDataRoutes || dataRoutes;
1920
1985
  let loadingNavigation = opts && opts.overrideNavigation;
1921
- let matches = matchRoutes(routesToUse, location, basename); // Short circuit with a 404 on the root error boundary if we match nothing
1986
+ let matches = matchRoutes(routesToUse, location, basename);
1922
1987
 
1988
+ // Short circuit with a 404 on the root error boundary if we match nothing
1923
1989
  if (!matches) {
1924
1990
  let error = getInternalRouterError(404, {
1925
1991
  pathname: location.pathname
@@ -1927,8 +1993,8 @@
1927
1993
  let {
1928
1994
  matches: notFoundMatches,
1929
1995
  route
1930
- } = getShortCircuitMatches(routesToUse); // Cancel all pending deferred on 404s since we don't keep any routes
1931
-
1996
+ } = getShortCircuitMatches(routesToUse);
1997
+ // Cancel all pending deferred on 404s since we don't keep any routes
1932
1998
  cancelActiveDeferreds();
1933
1999
  completeNavigation(location, {
1934
2000
  matches: notFoundMatches,
@@ -1938,26 +2004,26 @@
1938
2004
  }
1939
2005
  });
1940
2006
  return;
1941
- } // Short circuit if it's only a hash change and not a mutation submission.
1942
- // Ignore on initial page loads because since the initial load will always
1943
- // be "same hash".
1944
- // For example, on /page#hash and submit a <Form method="post"> which will
1945
- // default to a navigation to /page
1946
-
2007
+ }
1947
2008
 
1948
- if (state.initialized && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
2009
+ // Short circuit if it's only a hash change and not a revalidation or
2010
+ // mutation submission.
2011
+ //
2012
+ // Ignore on initial page loads because since the initial load will always
2013
+ // be "same hash". For example, on /page#hash and submit a <Form method="post">
2014
+ // which will default to a navigation to /page
2015
+ if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
1949
2016
  completeNavigation(location, {
1950
2017
  matches
1951
2018
  });
1952
2019
  return;
1953
- } // Create a controller/Request for this navigation
1954
-
2020
+ }
1955
2021
 
2022
+ // Create a controller/Request for this navigation
1956
2023
  pendingNavigationController = new AbortController();
1957
2024
  let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.submission);
1958
2025
  let pendingActionData;
1959
2026
  let pendingError;
1960
-
1961
2027
  if (opts && opts.pendingError) {
1962
2028
  // If we have a pendingError, it means the user attempted a GET submission
1963
2029
  // with binary FormData so assign here and skip to handleLoaders. That
@@ -1971,40 +2037,36 @@
1971
2037
  let actionOutput = await handleAction(request, location, opts.submission, matches, {
1972
2038
  replace: opts.replace
1973
2039
  });
1974
-
1975
2040
  if (actionOutput.shortCircuited) {
1976
2041
  return;
1977
2042
  }
1978
-
1979
2043
  pendingActionData = actionOutput.pendingActionData;
1980
2044
  pendingError = actionOutput.pendingActionError;
1981
-
1982
2045
  let navigation = _extends({
1983
2046
  state: "loading",
1984
2047
  location
1985
2048
  }, opts.submission);
2049
+ loadingNavigation = navigation;
1986
2050
 
1987
- loadingNavigation = navigation; // Create a GET request for the loaders
1988
-
2051
+ // Create a GET request for the loaders
1989
2052
  request = new Request(request.url, {
1990
2053
  signal: request.signal
1991
2054
  });
1992
- } // Call loaders
1993
-
2055
+ }
1994
2056
 
2057
+ // Call loaders
1995
2058
  let {
1996
2059
  shortCircuited,
1997
2060
  loaderData,
1998
2061
  errors
1999
2062
  } = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, pendingActionData, pendingError);
2000
-
2001
2063
  if (shortCircuited) {
2002
2064
  return;
2003
- } // Clean up now that the action/loaders have completed. Don't clean up if
2065
+ }
2066
+
2067
+ // Clean up now that the action/loaders have completed. Don't clean up if
2004
2068
  // we short circuited because pendingNavigationController will have already
2005
2069
  // been assigned to a new controller for the next navigation
2006
-
2007
-
2008
2070
  pendingNavigationController = null;
2009
2071
  completeNavigation(location, _extends({
2010
2072
  matches
@@ -2014,25 +2076,25 @@
2014
2076
  loaderData,
2015
2077
  errors
2016
2078
  }));
2017
- } // Call the action matched by the leaf route for this navigation and handle
2018
- // redirects/errors
2019
-
2079
+ }
2020
2080
 
2081
+ // Call the action matched by the leaf route for this navigation and handle
2082
+ // redirects/errors
2021
2083
  async function handleAction(request, location, submission, matches, opts) {
2022
- interruptActiveLoads(); // Put us in a submitting state
2084
+ interruptActiveLoads();
2023
2085
 
2086
+ // Put us in a submitting state
2024
2087
  let navigation = _extends({
2025
2088
  state: "submitting",
2026
2089
  location
2027
2090
  }, submission);
2028
-
2029
2091
  updateState({
2030
2092
  navigation
2031
- }); // Call our action and get the result
2093
+ });
2032
2094
 
2095
+ // Call our action and get the result
2033
2096
  let result;
2034
2097
  let actionMatch = getTargetMatch(matches, location);
2035
-
2036
2098
  if (!actionMatch.route.action && !actionMatch.route.lazy) {
2037
2099
  result = {
2038
2100
  type: ResultType.error,
@@ -2044,17 +2106,14 @@
2044
2106
  };
2045
2107
  } else {
2046
2108
  result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename);
2047
-
2048
2109
  if (request.signal.aborted) {
2049
2110
  return {
2050
2111
  shortCircuited: true
2051
2112
  };
2052
2113
  }
2053
2114
  }
2054
-
2055
2115
  if (isRedirectResult(result)) {
2056
2116
  let replace;
2057
-
2058
2117
  if (opts && opts.replace != null) {
2059
2118
  replace = opts.replace;
2060
2119
  } else {
@@ -2063,7 +2122,6 @@
2063
2122
  // double back-buttons
2064
2123
  replace = result.location === state.location.pathname + state.location.search;
2065
2124
  }
2066
-
2067
2125
  await startRedirectNavigation(state, result, {
2068
2126
  submission,
2069
2127
  replace
@@ -2072,19 +2130,18 @@
2072
2130
  shortCircuited: true
2073
2131
  };
2074
2132
  }
2075
-
2076
2133
  if (isErrorResult(result)) {
2077
2134
  // Store off the pending error - we use it to determine which loaders
2078
2135
  // to call and will commit it when we complete the navigation
2079
- let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id); // By default, all submissions are REPLACE navigations, but if the
2136
+ let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
2137
+
2138
+ // By default, all submissions are REPLACE navigations, but if the
2080
2139
  // action threw an error that'll be rendered in an errorElement, we fall
2081
2140
  // back to PUSH so that the user can use the back button to get back to
2082
2141
  // the pre-submission form location to try again
2083
-
2084
2142
  if ((opts && opts.replace) !== true) {
2085
2143
  pendingAction = exports.Action.Push;
2086
2144
  }
2087
-
2088
2145
  return {
2089
2146
  // Send back an empty object we can use to clear out any prior actionData
2090
2147
  pendingActionData: {},
@@ -2093,26 +2150,23 @@
2093
2150
  }
2094
2151
  };
2095
2152
  }
2096
-
2097
2153
  if (isDeferredResult(result)) {
2098
2154
  throw getInternalRouterError(400, {
2099
2155
  type: "defer-action"
2100
2156
  });
2101
2157
  }
2102
-
2103
2158
  return {
2104
2159
  pendingActionData: {
2105
2160
  [actionMatch.route.id]: result.data
2106
2161
  }
2107
2162
  };
2108
- } // Call all applicable loaders for the given matches, handling redirects,
2109
- // errors, etc.
2110
-
2163
+ }
2111
2164
 
2165
+ // Call all applicable loaders for the given matches, handling redirects,
2166
+ // errors, etc.
2112
2167
  async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, pendingActionData, pendingError) {
2113
2168
  // Figure out the right navigation we want to use for data loading
2114
2169
  let loadingNavigation = overrideNavigation;
2115
-
2116
2170
  if (!loadingNavigation) {
2117
2171
  let navigation = _extends({
2118
2172
  state: "loading",
@@ -2122,12 +2176,11 @@
2122
2176
  formEncType: undefined,
2123
2177
  formData: undefined
2124
2178
  }, submission);
2125
-
2126
2179
  loadingNavigation = navigation;
2127
- } // If this was a redirect from an action we don't have a "submission" but
2128
- // we have it on the loading navigation so use that if available
2129
-
2180
+ }
2130
2181
 
2182
+ // If this was a redirect from an action we don't have a "submission" but
2183
+ // we have it on the loading navigation so use that if available
2131
2184
  let activeSubmission = submission || fetcherSubmission ? submission || fetcherSubmission : loadingNavigation.formMethod && loadingNavigation.formAction && loadingNavigation.formData && loadingNavigation.formEncType ? {
2132
2185
  formMethod: loadingNavigation.formMethod,
2133
2186
  formAction: loadingNavigation.formAction,
@@ -2135,12 +2188,14 @@
2135
2188
  formEncType: loadingNavigation.formEncType
2136
2189
  } : undefined;
2137
2190
  let routesToUse = inFlightDataRoutes || dataRoutes;
2138
- let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, basename, pendingActionData, pendingError); // Cancel pending deferreds for no-longer-matched routes or routes we're
2191
+ let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, basename, pendingActionData, pendingError);
2192
+
2193
+ // Cancel pending deferreds for no-longer-matched routes or routes we're
2139
2194
  // about to reload. Note that if this is an action reload we would have
2140
2195
  // already cancelled all pending deferreds so this would be a no-op
2196
+ cancelActiveDeferreds(routeId => !(matches && matches.some(m => m.route.id === routeId)) || matchesToLoad && matchesToLoad.some(m => m.route.id === routeId));
2141
2197
 
2142
- cancelActiveDeferreds(routeId => !(matches && matches.some(m => m.route.id === routeId)) || matchesToLoad && matchesToLoad.some(m => m.route.id === routeId)); // Short circuit if we have no loaders to run
2143
-
2198
+ // Short circuit if we have no loaders to run
2144
2199
  if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
2145
2200
  let updatedFetchers = markFetchRedirectsDone();
2146
2201
  completeNavigation(location, _extends({
@@ -2156,12 +2211,12 @@
2156
2211
  return {
2157
2212
  shortCircuited: true
2158
2213
  };
2159
- } // If this is an uninterrupted revalidation, we remain in our current idle
2214
+ }
2215
+
2216
+ // If this is an uninterrupted revalidation, we remain in our current idle
2160
2217
  // state. If not, we need to switch to our loading state and load data,
2161
2218
  // preserving any new action data or existing action data (in the case of
2162
2219
  // a revalidation interrupting an actionReload)
2163
-
2164
-
2165
2220
  if (!isUninterruptedRevalidation) {
2166
2221
  revalidatingFetchers.forEach(rf => {
2167
2222
  let fetcher = state.fetchers.get(rf.key);
@@ -2187,7 +2242,6 @@
2187
2242
  fetchers: new Map(state.fetchers)
2188
2243
  } : {}));
2189
2244
  }
2190
-
2191
2245
  pendingNavigationLoadId = ++incrementingLoadId;
2192
2246
  revalidatingFetchers.forEach(rf => {
2193
2247
  if (rf.controller) {
@@ -2196,37 +2250,34 @@
2196
2250
  // triggered the revalidation
2197
2251
  fetchControllers.set(rf.key, rf.controller);
2198
2252
  }
2199
- }); // Proxy navigation abort through to revalidation fetchers
2253
+ });
2200
2254
 
2255
+ // Proxy navigation abort through to revalidation fetchers
2201
2256
  let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(f => abortFetcher(f.key));
2202
-
2203
2257
  if (pendingNavigationController) {
2204
2258
  pendingNavigationController.signal.addEventListener("abort", abortPendingFetchRevalidations);
2205
2259
  }
2206
-
2207
2260
  let {
2208
2261
  results,
2209
2262
  loaderResults,
2210
2263
  fetcherResults
2211
2264
  } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
2212
-
2213
2265
  if (request.signal.aborted) {
2214
2266
  return {
2215
2267
  shortCircuited: true
2216
2268
  };
2217
- } // Clean up _after_ loaders have completed. Don't clean up if we short
2269
+ }
2270
+
2271
+ // Clean up _after_ loaders have completed. Don't clean up if we short
2218
2272
  // circuited because fetchControllers would have been aborted and
2219
2273
  // reassigned to new controllers for the next navigation
2220
-
2221
-
2222
2274
  if (pendingNavigationController) {
2223
2275
  pendingNavigationController.signal.removeEventListener("abort", abortPendingFetchRevalidations);
2224
2276
  }
2277
+ revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key));
2225
2278
 
2226
- revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key)); // If any loaders returned a redirect Response, start a new REPLACE navigation
2227
-
2279
+ // If any loaders returned a redirect Response, start a new REPLACE navigation
2228
2280
  let redirect = findRedirect(results);
2229
-
2230
2281
  if (redirect) {
2231
2282
  await startRedirectNavigation(state, redirect, {
2232
2283
  replace
@@ -2234,14 +2285,15 @@
2234
2285
  return {
2235
2286
  shortCircuited: true
2236
2287
  };
2237
- } // Process and commit output from loaders
2238
-
2288
+ }
2239
2289
 
2290
+ // Process and commit output from loaders
2240
2291
  let {
2241
2292
  loaderData,
2242
2293
  errors
2243
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds); // Wire up subscribers to update loaderData as promises settle
2294
+ } = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds);
2244
2295
 
2296
+ // Wire up subscribers to update loaderData as promises settle
2245
2297
  activeDeferreds.forEach((deferredData, routeId) => {
2246
2298
  deferredData.subscribe(aborted => {
2247
2299
  // Note: No need to updateState here since the TrackedPromise on
@@ -2262,56 +2314,50 @@
2262
2314
  fetchers: new Map(state.fetchers)
2263
2315
  } : {});
2264
2316
  }
2265
-
2266
2317
  function getFetcher(key) {
2267
2318
  return state.fetchers.get(key) || IDLE_FETCHER;
2268
- } // Trigger a fetcher load/submit for the given fetcher key
2269
-
2319
+ }
2270
2320
 
2321
+ // Trigger a fetcher load/submit for the given fetcher key
2271
2322
  function fetch(key, routeId, href, opts) {
2272
2323
  if (isServer) {
2273
2324
  throw new Error("router.fetch() was called during the server render, but it shouldn't be. " + "You are likely calling a useFetcher() method in the body of your component. " + "Try moving it to a useEffect or a callback.");
2274
2325
  }
2275
-
2276
2326
  if (fetchControllers.has(key)) abortFetcher(key);
2277
2327
  let routesToUse = inFlightDataRoutes || dataRoutes;
2278
2328
  let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, routeId, opts == null ? void 0 : opts.relative);
2279
2329
  let matches = matchRoutes(routesToUse, normalizedPath, basename);
2280
-
2281
2330
  if (!matches) {
2282
2331
  setFetcherError(key, routeId, getInternalRouterError(404, {
2283
2332
  pathname: normalizedPath
2284
2333
  }));
2285
2334
  return;
2286
2335
  }
2287
-
2288
2336
  let {
2289
2337
  path,
2290
2338
  submission
2291
2339
  } = normalizeNavigateOptions(future.v7_normalizeFormMethod, true, normalizedPath, opts);
2292
2340
  let match = getTargetMatch(matches, path);
2293
2341
  pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2294
-
2295
2342
  if (submission && isMutationMethod(submission.formMethod)) {
2296
2343
  handleFetcherAction(key, routeId, path, match, matches, submission);
2297
2344
  return;
2298
- } // Store off the match so we can call it's shouldRevalidate on subsequent
2299
- // revalidations
2300
-
2345
+ }
2301
2346
 
2347
+ // Store off the match so we can call it's shouldRevalidate on subsequent
2348
+ // revalidations
2302
2349
  fetchLoadMatches.set(key, {
2303
2350
  routeId,
2304
2351
  path
2305
2352
  });
2306
2353
  handleFetcherLoader(key, routeId, path, match, matches, submission);
2307
- } // Call the action for the matched fetcher.submit(), and then handle redirects,
2308
- // errors, and revalidation
2309
-
2354
+ }
2310
2355
 
2356
+ // Call the action for the matched fetcher.submit(), and then handle redirects,
2357
+ // errors, and revalidation
2311
2358
  async function handleFetcherAction(key, routeId, path, match, requestMatches, submission) {
2312
2359
  interruptActiveLoads();
2313
2360
  fetchLoadMatches.delete(key);
2314
-
2315
2361
  if (!match.route.action && !match.route.lazy) {
2316
2362
  let error = getInternalRouterError(405, {
2317
2363
  method: submission.formMethod,
@@ -2320,49 +2366,43 @@
2320
2366
  });
2321
2367
  setFetcherError(key, routeId, error);
2322
2368
  return;
2323
- } // Put this fetcher into it's submitting state
2324
-
2369
+ }
2325
2370
 
2371
+ // Put this fetcher into it's submitting state
2326
2372
  let existingFetcher = state.fetchers.get(key);
2327
-
2328
2373
  let fetcher = _extends({
2329
2374
  state: "submitting"
2330
2375
  }, submission, {
2331
2376
  data: existingFetcher && existingFetcher.data,
2332
2377
  " _hasFetcherDoneAnything ": true
2333
2378
  });
2334
-
2335
2379
  state.fetchers.set(key, fetcher);
2336
2380
  updateState({
2337
2381
  fetchers: new Map(state.fetchers)
2338
- }); // Call the action for the fetcher
2382
+ });
2339
2383
 
2384
+ // Call the action for the fetcher
2340
2385
  let abortController = new AbortController();
2341
2386
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2342
2387
  fetchControllers.set(key, abortController);
2343
2388
  let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename);
2344
-
2345
2389
  if (fetchRequest.signal.aborted) {
2346
2390
  // We can delete this so long as we weren't aborted by ou our own fetcher
2347
2391
  // re-submit which would have put _new_ controller is in fetchControllers
2348
2392
  if (fetchControllers.get(key) === abortController) {
2349
2393
  fetchControllers.delete(key);
2350
2394
  }
2351
-
2352
2395
  return;
2353
2396
  }
2354
-
2355
2397
  if (isRedirectResult(actionResult)) {
2356
2398
  fetchControllers.delete(key);
2357
2399
  fetchRedirectIds.add(key);
2358
-
2359
2400
  let loadingFetcher = _extends({
2360
2401
  state: "loading"
2361
2402
  }, submission, {
2362
2403
  data: undefined,
2363
2404
  " _hasFetcherDoneAnything ": true
2364
2405
  });
2365
-
2366
2406
  state.fetchers.set(key, loadingFetcher);
2367
2407
  updateState({
2368
2408
  fetchers: new Map(state.fetchers)
@@ -2371,22 +2411,21 @@
2371
2411
  submission,
2372
2412
  isFetchActionRedirect: true
2373
2413
  });
2374
- } // Process any non-redirect errors thrown
2375
-
2414
+ }
2376
2415
 
2416
+ // Process any non-redirect errors thrown
2377
2417
  if (isErrorResult(actionResult)) {
2378
2418
  setFetcherError(key, routeId, actionResult.error);
2379
2419
  return;
2380
2420
  }
2381
-
2382
2421
  if (isDeferredResult(actionResult)) {
2383
2422
  throw getInternalRouterError(400, {
2384
2423
  type: "defer-action"
2385
2424
  });
2386
- } // Start the data load for current matches, or the next location if we're
2387
- // in the middle of a navigation
2388
-
2425
+ }
2389
2426
 
2427
+ // Start the data load for current matches, or the next location if we're
2428
+ // in the middle of a navigation
2390
2429
  let nextLocation = state.navigation.location || state.location;
2391
2430
  let revalidationRequest = createClientSideRequest(init.history, nextLocation, abortController.signal);
2392
2431
  let routesToUse = inFlightDataRoutes || dataRoutes;
@@ -2394,22 +2433,21 @@
2394
2433
  invariant(matches, "Didn't find any matches after fetcher action");
2395
2434
  let loadId = ++incrementingLoadId;
2396
2435
  fetchReloadIds.set(key, loadId);
2397
-
2398
2436
  let loadFetcher = _extends({
2399
2437
  state: "loading",
2400
2438
  data: actionResult.data
2401
2439
  }, submission, {
2402
2440
  " _hasFetcherDoneAnything ": true
2403
2441
  });
2404
-
2405
2442
  state.fetchers.set(key, loadFetcher);
2406
2443
  let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, basename, {
2407
2444
  [match.route.id]: actionResult.data
2408
2445
  }, undefined // No need to send through errors since we short circuit above
2409
- ); // Put all revalidating fetchers into the loading state, except for the
2446
+ );
2447
+
2448
+ // Put all revalidating fetchers into the loading state, except for the
2410
2449
  // current fetcher which we want to keep in it's current loading state which
2411
2450
  // contains it's action submission info + action data
2412
-
2413
2451
  revalidatingFetchers.filter(rf => rf.key !== key).forEach(rf => {
2414
2452
  let staleKey = rf.key;
2415
2453
  let existingFetcher = state.fetchers.get(staleKey);
@@ -2423,7 +2461,6 @@
2423
2461
  " _hasFetcherDoneAnything ": true
2424
2462
  };
2425
2463
  state.fetchers.set(staleKey, revalidatingFetcher);
2426
-
2427
2464
  if (rf.controller) {
2428
2465
  fetchControllers.set(staleKey, rf.controller);
2429
2466
  }
@@ -2431,49 +2468,50 @@
2431
2468
  updateState({
2432
2469
  fetchers: new Map(state.fetchers)
2433
2470
  });
2434
-
2435
2471
  let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(rf => abortFetcher(rf.key));
2436
-
2437
2472
  abortController.signal.addEventListener("abort", abortPendingFetchRevalidations);
2438
2473
  let {
2439
2474
  results,
2440
2475
  loaderResults,
2441
2476
  fetcherResults
2442
2477
  } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
2443
-
2444
2478
  if (abortController.signal.aborted) {
2445
2479
  return;
2446
2480
  }
2447
-
2448
2481
  abortController.signal.removeEventListener("abort", abortPendingFetchRevalidations);
2449
2482
  fetchReloadIds.delete(key);
2450
2483
  fetchControllers.delete(key);
2451
2484
  revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2452
2485
  let redirect = findRedirect(results);
2453
-
2454
2486
  if (redirect) {
2455
2487
  return startRedirectNavigation(state, redirect);
2456
- } // Process and commit output from loaders
2457
-
2488
+ }
2458
2489
 
2490
+ // Process and commit output from loaders
2459
2491
  let {
2460
2492
  loaderData,
2461
2493
  errors
2462
2494
  } = processLoaderData(state, state.matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
2463
- let doneFetcher = {
2464
- state: "idle",
2465
- data: actionResult.data,
2466
- formMethod: undefined,
2467
- formAction: undefined,
2468
- formEncType: undefined,
2469
- formData: undefined,
2470
- " _hasFetcherDoneAnything ": true
2471
- };
2472
- state.fetchers.set(key, doneFetcher);
2473
- let didAbortFetchLoads = abortStaleFetchLoads(loadId); // If we are currently in a navigation loading state and this fetcher is
2495
+
2496
+ // Since we let revalidations complete even if the submitting fetcher was
2497
+ // deleted, only put it back to idle if it hasn't been deleted
2498
+ if (state.fetchers.has(key)) {
2499
+ let doneFetcher = {
2500
+ state: "idle",
2501
+ data: actionResult.data,
2502
+ formMethod: undefined,
2503
+ formAction: undefined,
2504
+ formEncType: undefined,
2505
+ formData: undefined,
2506
+ " _hasFetcherDoneAnything ": true
2507
+ };
2508
+ state.fetchers.set(key, doneFetcher);
2509
+ }
2510
+ let didAbortFetchLoads = abortStaleFetchLoads(loadId);
2511
+
2512
+ // If we are currently in a navigation loading state and this fetcher is
2474
2513
  // more recent than the navigation, we want the newer data so abort the
2475
2514
  // navigation and complete it with the fetcher data
2476
-
2477
2515
  if (state.navigation.state === "loading" && loadId > pendingNavigationLoadId) {
2478
2516
  invariant(pendingAction, "Expected pending action");
2479
2517
  pendingNavigationController && pendingNavigationController.abort();
@@ -2490,17 +2528,17 @@
2490
2528
  updateState(_extends({
2491
2529
  errors,
2492
2530
  loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors)
2493
- }, didAbortFetchLoads ? {
2531
+ }, didAbortFetchLoads || revalidatingFetchers.length > 0 ? {
2494
2532
  fetchers: new Map(state.fetchers)
2495
2533
  } : {}));
2496
2534
  isRevalidationRequired = false;
2497
2535
  }
2498
- } // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
2499
-
2536
+ }
2500
2537
 
2538
+ // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
2501
2539
  async function handleFetcherLoader(key, routeId, path, match, matches, submission) {
2502
- let existingFetcher = state.fetchers.get(key); // Put this fetcher into it's loading state
2503
-
2540
+ let existingFetcher = state.fetchers.get(key);
2541
+ // Put this fetcher into it's loading state
2504
2542
  let loadingFetcher = _extends({
2505
2543
  state: "loading",
2506
2544
  formMethod: undefined,
@@ -2511,48 +2549,48 @@
2511
2549
  data: existingFetcher && existingFetcher.data,
2512
2550
  " _hasFetcherDoneAnything ": true
2513
2551
  });
2514
-
2515
2552
  state.fetchers.set(key, loadingFetcher);
2516
2553
  updateState({
2517
2554
  fetchers: new Map(state.fetchers)
2518
- }); // Call the loader for this fetcher route match
2555
+ });
2519
2556
 
2557
+ // Call the loader for this fetcher route match
2520
2558
  let abortController = new AbortController();
2521
2559
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2522
2560
  fetchControllers.set(key, abortController);
2523
- let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename); // Deferred isn't supported for fetcher loads, await everything and treat it
2561
+ let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename);
2562
+
2563
+ // Deferred isn't supported for fetcher loads, await everything and treat it
2524
2564
  // as a normal load. resolveDeferredData will return undefined if this
2525
2565
  // fetcher gets aborted, so we just leave result untouched and short circuit
2526
2566
  // below if that happens
2527
-
2528
2567
  if (isDeferredResult(result)) {
2529
2568
  result = (await resolveDeferredData(result, fetchRequest.signal, true)) || result;
2530
- } // We can delete this so long as we weren't aborted by our our own fetcher
2531
- // re-load which would have put _new_ controller is in fetchControllers
2532
-
2569
+ }
2533
2570
 
2571
+ // We can delete this so long as we weren't aborted by our our own fetcher
2572
+ // re-load which would have put _new_ controller is in fetchControllers
2534
2573
  if (fetchControllers.get(key) === abortController) {
2535
2574
  fetchControllers.delete(key);
2536
2575
  }
2537
-
2538
2576
  if (fetchRequest.signal.aborted) {
2539
2577
  return;
2540
- } // If the loader threw a redirect Response, start a new REPLACE navigation
2541
-
2578
+ }
2542
2579
 
2580
+ // If the loader threw a redirect Response, start a new REPLACE navigation
2543
2581
  if (isRedirectResult(result)) {
2544
2582
  fetchRedirectIds.add(key);
2545
2583
  await startRedirectNavigation(state, result);
2546
2584
  return;
2547
- } // Process any non-redirect errors thrown
2548
-
2585
+ }
2549
2586
 
2587
+ // Process any non-redirect errors thrown
2550
2588
  if (isErrorResult(result)) {
2551
2589
  let boundaryMatch = findNearestBoundary(state.matches, routeId);
2552
- state.fetchers.delete(key); // TODO: In remix, this would reset to IDLE_NAVIGATION if it was a catch -
2590
+ state.fetchers.delete(key);
2591
+ // TODO: In remix, this would reset to IDLE_NAVIGATION if it was a catch -
2553
2592
  // do we need to behave any differently with our non-redirect errors?
2554
2593
  // What if it was a non-redirect Response?
2555
-
2556
2594
  updateState({
2557
2595
  fetchers: new Map(state.fetchers),
2558
2596
  errors: {
@@ -2561,9 +2599,9 @@
2561
2599
  });
2562
2600
  return;
2563
2601
  }
2602
+ invariant(!isDeferredResult(result), "Unhandled fetcher deferred data");
2564
2603
 
2565
- invariant(!isDeferredResult(result), "Unhandled fetcher deferred data"); // Put the fetcher back into an idle state
2566
-
2604
+ // Put the fetcher back into an idle state
2567
2605
  let doneFetcher = {
2568
2606
  state: "idle",
2569
2607
  data: result.data,
@@ -2578,6 +2616,7 @@
2578
2616
  fetchers: new Map(state.fetchers)
2579
2617
  });
2580
2618
  }
2619
+
2581
2620
  /**
2582
2621
  * Utility function to handle redirects returned from an action or loader.
2583
2622
  * Normally, a redirect "replaces" the navigation that triggered it. So, for
@@ -2597,57 +2636,49 @@
2597
2636
  * actually touch history until we've processed redirects, so we just use
2598
2637
  * the history action from the original navigation (PUSH or REPLACE).
2599
2638
  */
2600
-
2601
-
2602
2639
  async function startRedirectNavigation(state, redirect, _temp) {
2603
- var _window;
2604
-
2605
2640
  let {
2606
2641
  submission,
2607
2642
  replace,
2608
2643
  isFetchActionRedirect
2609
2644
  } = _temp === void 0 ? {} : _temp;
2610
-
2611
2645
  if (redirect.revalidate) {
2612
2646
  isRevalidationRequired = true;
2613
2647
  }
2614
-
2615
2648
  let redirectLocation = createLocation(state.location, redirect.location, // TODO: This can be removed once we get rid of useTransition in Remix v2
2616
2649
  _extends({
2617
2650
  _isRedirect: true
2618
2651
  }, isFetchActionRedirect ? {
2619
2652
  _isFetchActionRedirect: true
2620
2653
  } : {}));
2621
- invariant(redirectLocation, "Expected a location on the redirect navigation"); // Check if this an absolute external redirect that goes to a new origin
2622
-
2623
- if (ABSOLUTE_URL_REGEX.test(redirect.location) && isBrowser && typeof ((_window = window) == null ? void 0 : _window.location) !== "undefined") {
2654
+ invariant(redirectLocation, "Expected a location on the redirect navigation");
2655
+ // Check if this an absolute external redirect that goes to a new origin
2656
+ if (ABSOLUTE_URL_REGEX.test(redirect.location) && isBrowser) {
2624
2657
  let url = init.history.createURL(redirect.location);
2625
2658
  let isDifferentBasename = stripBasename(url.pathname, basename) == null;
2626
-
2627
- if (window.location.origin !== url.origin || isDifferentBasename) {
2659
+ if (routerWindow.location.origin !== url.origin || isDifferentBasename) {
2628
2660
  if (replace) {
2629
- window.location.replace(redirect.location);
2661
+ routerWindow.location.replace(redirect.location);
2630
2662
  } else {
2631
- window.location.assign(redirect.location);
2663
+ routerWindow.location.assign(redirect.location);
2632
2664
  }
2633
-
2634
2665
  return;
2635
2666
  }
2636
- } // There's no need to abort on redirects, since we don't detect the
2637
- // redirect until the action/loaders have settled
2638
-
2667
+ }
2639
2668
 
2669
+ // There's no need to abort on redirects, since we don't detect the
2670
+ // redirect until the action/loaders have settled
2640
2671
  pendingNavigationController = null;
2641
- let redirectHistoryAction = replace === true ? exports.Action.Replace : exports.Action.Push; // Use the incoming submission if provided, fallback on the active one in
2642
- // state.navigation
2672
+ let redirectHistoryAction = replace === true ? exports.Action.Replace : exports.Action.Push;
2643
2673
 
2674
+ // Use the incoming submission if provided, fallback on the active one in
2675
+ // state.navigation
2644
2676
  let {
2645
2677
  formMethod,
2646
2678
  formAction,
2647
2679
  formEncType,
2648
2680
  formData
2649
2681
  } = state.navigation;
2650
-
2651
2682
  if (!submission && formMethod && formAction && formData && formEncType) {
2652
2683
  submission = {
2653
2684
  formMethod,
@@ -2655,11 +2686,11 @@
2655
2686
  formEncType,
2656
2687
  formData
2657
2688
  };
2658
- } // If this was a 307/308 submission we want to preserve the HTTP method and
2689
+ }
2690
+
2691
+ // If this was a 307/308 submission we want to preserve the HTTP method and
2659
2692
  // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
2660
2693
  // redirected location
2661
-
2662
-
2663
2694
  if (redirectPreserveMethodStatusCodes.has(redirect.status) && submission && isMutationMethod(submission.formMethod)) {
2664
2695
  await startNavigation(redirectHistoryAction, redirectLocation, {
2665
2696
  submission: _extends({}, submission, {
@@ -2701,7 +2732,6 @@
2701
2732
  });
2702
2733
  }
2703
2734
  }
2704
-
2705
2735
  async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
2706
2736
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2707
2737
  // then slice off the results into separate arrays so we can handle them
@@ -2728,14 +2758,15 @@
2728
2758
  fetcherResults
2729
2759
  };
2730
2760
  }
2731
-
2732
2761
  function interruptActiveLoads() {
2733
2762
  // Every interruption triggers a revalidation
2734
- isRevalidationRequired = true; // Cancel pending route-level deferreds and mark cancelled routes for
2735
- // revalidation
2763
+ isRevalidationRequired = true;
2736
2764
 
2737
- cancelledDeferredRoutes.push(...cancelActiveDeferreds()); // Abort in-flight fetcher loads
2765
+ // Cancel pending route-level deferreds and mark cancelled routes for
2766
+ // revalidation
2767
+ cancelledDeferredRoutes.push(...cancelActiveDeferreds());
2738
2768
 
2769
+ // Abort in-flight fetcher loads
2739
2770
  fetchLoadMatches.forEach((_, key) => {
2740
2771
  if (fetchControllers.has(key)) {
2741
2772
  cancelledFetcherLoads.push(key);
@@ -2743,7 +2774,6 @@
2743
2774
  }
2744
2775
  });
2745
2776
  }
2746
-
2747
2777
  function setFetcherError(key, routeId, error) {
2748
2778
  let boundaryMatch = findNearestBoundary(state.matches, routeId);
2749
2779
  deleteFetcher(key);
@@ -2754,22 +2784,25 @@
2754
2784
  fetchers: new Map(state.fetchers)
2755
2785
  });
2756
2786
  }
2757
-
2758
2787
  function deleteFetcher(key) {
2759
- if (fetchControllers.has(key)) abortFetcher(key);
2788
+ let fetcher = state.fetchers.get(key);
2789
+ // Don't abort the controller if this is a deletion of a fetcher.submit()
2790
+ // in it's loading phase since - we don't want to abort the corresponding
2791
+ // revalidation and want them to complete and land
2792
+ if (fetchControllers.has(key) && !(fetcher && fetcher.state === "loading" && fetchReloadIds.has(key))) {
2793
+ abortFetcher(key);
2794
+ }
2760
2795
  fetchLoadMatches.delete(key);
2761
2796
  fetchReloadIds.delete(key);
2762
2797
  fetchRedirectIds.delete(key);
2763
2798
  state.fetchers.delete(key);
2764
2799
  }
2765
-
2766
2800
  function abortFetcher(key) {
2767
2801
  let controller = fetchControllers.get(key);
2768
2802
  invariant(controller, "Expected fetch controller: " + key);
2769
2803
  controller.abort();
2770
2804
  fetchControllers.delete(key);
2771
2805
  }
2772
-
2773
2806
  function markFetchersDone(keys) {
2774
2807
  for (let key of keys) {
2775
2808
  let fetcher = getFetcher(key);
@@ -2785,34 +2818,27 @@
2785
2818
  state.fetchers.set(key, doneFetcher);
2786
2819
  }
2787
2820
  }
2788
-
2789
2821
  function markFetchRedirectsDone() {
2790
2822
  let doneKeys = [];
2791
2823
  let updatedFetchers = false;
2792
-
2793
2824
  for (let key of fetchRedirectIds) {
2794
2825
  let fetcher = state.fetchers.get(key);
2795
2826
  invariant(fetcher, "Expected fetcher: " + key);
2796
-
2797
2827
  if (fetcher.state === "loading") {
2798
2828
  fetchRedirectIds.delete(key);
2799
2829
  doneKeys.push(key);
2800
2830
  updatedFetchers = true;
2801
2831
  }
2802
2832
  }
2803
-
2804
2833
  markFetchersDone(doneKeys);
2805
2834
  return updatedFetchers;
2806
2835
  }
2807
-
2808
2836
  function abortStaleFetchLoads(landedId) {
2809
2837
  let yeetedKeys = [];
2810
-
2811
2838
  for (let [key, id] of fetchReloadIds) {
2812
2839
  if (id < landedId) {
2813
2840
  let fetcher = state.fetchers.get(key);
2814
2841
  invariant(fetcher, "Expected fetcher: " + key);
2815
-
2816
2842
  if (fetcher.state === "loading") {
2817
2843
  abortFetcher(key);
2818
2844
  fetchReloadIds.delete(key);
@@ -2820,67 +2846,59 @@
2820
2846
  }
2821
2847
  }
2822
2848
  }
2823
-
2824
2849
  markFetchersDone(yeetedKeys);
2825
2850
  return yeetedKeys.length > 0;
2826
2851
  }
2827
-
2828
2852
  function getBlocker(key, fn) {
2829
2853
  let blocker = state.blockers.get(key) || IDLE_BLOCKER;
2830
-
2831
2854
  if (blockerFunctions.get(key) !== fn) {
2832
2855
  blockerFunctions.set(key, fn);
2833
2856
  }
2834
-
2835
2857
  return blocker;
2836
2858
  }
2837
-
2838
2859
  function deleteBlocker(key) {
2839
2860
  state.blockers.delete(key);
2840
2861
  blockerFunctions.delete(key);
2841
- } // Utility function to update blockers, ensuring valid state transitions
2842
-
2862
+ }
2843
2863
 
2864
+ // Utility function to update blockers, ensuring valid state transitions
2844
2865
  function updateBlocker(key, newBlocker) {
2845
- let blocker = state.blockers.get(key) || IDLE_BLOCKER; // Poor mans state machine :)
2846
- // https://mermaid.live/edit#pako:eNqVkc9OwzAMxl8l8nnjAYrEtDIOHEBIgwvKJTReGy3_lDpIqO27k6awMG0XcrLlnz87nwdonESogKXXBuE79rq75XZO3-yHds0RJVuv70YrPlUrCEe2HfrORS3rubqZfuhtpg5C9wk5tZ4VKcRUq88q9Z8RS0-48cE1iHJkL0ugbHuFLus9L6spZy8nX9MP2CNdomVaposqu3fGayT8T8-jJQwhepo_UtpgBQaDEUom04dZhAN1aJBDlUKJBxE1ceB2Smj0Mln-IBW5AFU2dwUiktt_2Qaq2dBfaKdEup85UV7Yd-dKjlnkabl2Pvr0DTkTreM
2866
+ let blocker = state.blockers.get(key) || IDLE_BLOCKER;
2847
2867
 
2868
+ // Poor mans state machine :)
2869
+ // https://mermaid.live/edit#pako:eNqVkc9OwzAMxl8l8nnjAYrEtDIOHEBIgwvKJTReGy3_lDpIqO27k6awMG0XcrLlnz87nwdonESogKXXBuE79rq75XZO3-yHds0RJVuv70YrPlUrCEe2HfrORS3rubqZfuhtpg5C9wk5tZ4VKcRUq88q9Z8RS0-48cE1iHJkL0ugbHuFLus9L6spZy8nX9MP2CNdomVaposqu3fGayT8T8-jJQwhepo_UtpgBQaDEUom04dZhAN1aJBDlUKJBxE1ceB2Smj0Mln-IBW5AFU2dwUiktt_2Qaq2dBfaKdEup85UV7Yd-dKjlnkabl2Pvr0DTkTreM
2848
2870
  invariant(blocker.state === "unblocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "proceeding" || blocker.state === "blocked" && newBlocker.state === "unblocked" || blocker.state === "proceeding" && newBlocker.state === "unblocked", "Invalid blocker state transition: " + blocker.state + " -> " + newBlocker.state);
2849
2871
  state.blockers.set(key, newBlocker);
2850
2872
  updateState({
2851
2873
  blockers: new Map(state.blockers)
2852
2874
  });
2853
2875
  }
2854
-
2855
2876
  function shouldBlockNavigation(_ref2) {
2856
2877
  let {
2857
2878
  currentLocation,
2858
2879
  nextLocation,
2859
2880
  historyAction
2860
2881
  } = _ref2;
2861
-
2862
2882
  if (blockerFunctions.size === 0) {
2863
2883
  return;
2864
- } // We ony support a single active blocker at the moment since we don't have
2865
- // any compelling use cases for multi-blocker yet
2866
-
2884
+ }
2867
2885
 
2886
+ // We ony support a single active blocker at the moment since we don't have
2887
+ // any compelling use cases for multi-blocker yet
2868
2888
  if (blockerFunctions.size > 1) {
2869
2889
  warning(false, "A router only supports one blocker at a time");
2870
2890
  }
2871
-
2872
2891
  let entries = Array.from(blockerFunctions.entries());
2873
2892
  let [blockerKey, blockerFunction] = entries[entries.length - 1];
2874
2893
  let blocker = state.blockers.get(blockerKey);
2875
-
2876
2894
  if (blocker && blocker.state === "proceeding") {
2877
2895
  // If the blocker is currently proceeding, we don't need to re-check
2878
2896
  // it and can let this navigation continue
2879
2897
  return;
2880
- } // At this point, we know we're unblocked/blocked so we need to check the
2881
- // user-provided blocker function
2882
-
2898
+ }
2883
2899
 
2900
+ // At this point, we know we're unblocked/blocked so we need to check the
2901
+ // user-provided blocker function
2884
2902
  if (blockerFunction({
2885
2903
  currentLocation,
2886
2904
  nextLocation,
@@ -2889,7 +2907,6 @@
2889
2907
  return blockerKey;
2890
2908
  }
2891
2909
  }
2892
-
2893
2910
  function cancelActiveDeferreds(predicate) {
2894
2911
  let cancelledRouteIds = [];
2895
2912
  activeDeferreds.forEach((dfd, routeId) => {
@@ -2903,37 +2920,33 @@
2903
2920
  }
2904
2921
  });
2905
2922
  return cancelledRouteIds;
2906
- } // Opt in to capturing and reporting scroll positions during navigations,
2907
- // used by the <ScrollRestoration> component
2908
-
2923
+ }
2909
2924
 
2925
+ // Opt in to capturing and reporting scroll positions during navigations,
2926
+ // used by the <ScrollRestoration> component
2910
2927
  function enableScrollRestoration(positions, getPosition, getKey) {
2911
2928
  savedScrollPositions = positions;
2912
2929
  getScrollPosition = getPosition;
2930
+ getScrollRestorationKey = getKey || (location => location.key);
2913
2931
 
2914
- getScrollRestorationKey = getKey || (location => location.key); // Perform initial hydration scroll restoration, since we miss the boat on
2932
+ // Perform initial hydration scroll restoration, since we miss the boat on
2915
2933
  // the initial updateState() because we've not yet rendered <ScrollRestoration/>
2916
2934
  // and therefore have no savedScrollPositions available
2917
-
2918
-
2919
2935
  if (!initialScrollRestored && state.navigation === IDLE_NAVIGATION) {
2920
2936
  initialScrollRestored = true;
2921
2937
  let y = getSavedScrollPosition(state.location, state.matches);
2922
-
2923
2938
  if (y != null) {
2924
2939
  updateState({
2925
2940
  restoreScrollPosition: y
2926
2941
  });
2927
2942
  }
2928
2943
  }
2929
-
2930
2944
  return () => {
2931
2945
  savedScrollPositions = null;
2932
2946
  getScrollPosition = null;
2933
2947
  getScrollRestorationKey = null;
2934
2948
  };
2935
2949
  }
2936
-
2937
2950
  function saveScrollPosition(location, matches) {
2938
2951
  if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
2939
2952
  let userMatches = matches.map(m => createUseMatchesMatch(m, state.loaderData));
@@ -2941,39 +2954,31 @@
2941
2954
  savedScrollPositions[key] = getScrollPosition();
2942
2955
  }
2943
2956
  }
2944
-
2945
2957
  function getSavedScrollPosition(location, matches) {
2946
2958
  if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
2947
2959
  let userMatches = matches.map(m => createUseMatchesMatch(m, state.loaderData));
2948
2960
  let key = getScrollRestorationKey(location, userMatches) || location.key;
2949
2961
  let y = savedScrollPositions[key];
2950
-
2951
2962
  if (typeof y === "number") {
2952
2963
  return y;
2953
2964
  }
2954
2965
  }
2955
-
2956
2966
  return null;
2957
2967
  }
2958
-
2959
2968
  function _internalSetRoutes(newRoutes) {
2960
2969
  manifest = {};
2961
2970
  inFlightDataRoutes = convertRoutesToDataRoutes(newRoutes, mapRouteProperties, undefined, manifest);
2962
2971
  }
2963
-
2964
2972
  router = {
2965
2973
  get basename() {
2966
2974
  return basename;
2967
2975
  },
2968
-
2969
2976
  get state() {
2970
2977
  return state;
2971
2978
  },
2972
-
2973
2979
  get routes() {
2974
2980
  return dataRoutes;
2975
2981
  },
2976
-
2977
2982
  initialize,
2978
2983
  subscribe,
2979
2984
  enableScrollRestoration,
@@ -2996,7 +3001,9 @@
2996
3001
  _internalSetRoutes
2997
3002
  };
2998
3003
  return router;
2999
- } //#endregion
3004
+ }
3005
+ //#endregion
3006
+
3000
3007
  ////////////////////////////////////////////////////////////////////////////////
3001
3008
  //#region createStaticHandler
3002
3009
  ////////////////////////////////////////////////////////////////////////////////
@@ -3007,21 +3014,19 @@
3007
3014
  let manifest = {};
3008
3015
  let basename = (opts ? opts.basename : null) || "/";
3009
3016
  let mapRouteProperties;
3010
-
3011
3017
  if (opts != null && opts.mapRouteProperties) {
3012
3018
  mapRouteProperties = opts.mapRouteProperties;
3013
3019
  } else if (opts != null && opts.detectErrorBoundary) {
3014
3020
  // If they are still using the deprecated version, wrap it with the new API
3015
3021
  let detectErrorBoundary = opts.detectErrorBoundary;
3016
-
3017
3022
  mapRouteProperties = route => ({
3018
3023
  hasErrorBoundary: detectErrorBoundary(route)
3019
3024
  });
3020
3025
  } else {
3021
3026
  mapRouteProperties = defaultMapRouteProperties;
3022
3027
  }
3023
-
3024
3028
  let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
3029
+
3025
3030
  /**
3026
3031
  * The query() method is intended for document requests, in which we want to
3027
3032
  * call an optional action and potentially multiple loaders for all nested
@@ -3041,7 +3046,6 @@
3041
3046
  * propagate that out and return the raw Response so the HTTP server can
3042
3047
  * return it directly.
3043
3048
  */
3044
-
3045
3049
  async function query(request, _temp2) {
3046
3050
  let {
3047
3051
  requestContext
@@ -3049,8 +3053,9 @@
3049
3053
  let url = new URL(request.url);
3050
3054
  let method = request.method;
3051
3055
  let location = createLocation("", createPath(url), null, "default");
3052
- let matches = matchRoutes(dataRoutes, location, basename); // SSR supports HEAD requests while SPA doesn't
3056
+ let matches = matchRoutes(dataRoutes, location, basename);
3053
3057
 
3058
+ // SSR supports HEAD requests while SPA doesn't
3054
3059
  if (!isValidMethod(method) && method !== "HEAD") {
3055
3060
  let error = getInternalRouterError(405, {
3056
3061
  method
@@ -3096,21 +3101,20 @@
3096
3101
  activeDeferreds: null
3097
3102
  };
3098
3103
  }
3099
-
3100
3104
  let result = await queryImpl(request, location, matches, requestContext);
3101
-
3102
3105
  if (isResponse(result)) {
3103
3106
  return result;
3104
- } // When returning StaticHandlerContext, we patch back in the location here
3107
+ }
3108
+
3109
+ // When returning StaticHandlerContext, we patch back in the location here
3105
3110
  // since we need it for React Context. But this helps keep our submit and
3106
3111
  // loadRouteData operating on a Request instead of a Location
3107
-
3108
-
3109
3112
  return _extends({
3110
3113
  location,
3111
3114
  basename
3112
3115
  }, result);
3113
3116
  }
3117
+
3114
3118
  /**
3115
3119
  * The queryRoute() method is intended for targeted route requests, either
3116
3120
  * for fetch ?_data requests or resource route requests. In this case, we
@@ -3131,8 +3135,6 @@
3131
3135
  * code. Examples here are 404 and 405 errors that occur prior to reaching
3132
3136
  * any user-defined loaders.
3133
3137
  */
3134
-
3135
-
3136
3138
  async function queryRoute(request, _temp3) {
3137
3139
  let {
3138
3140
  routeId,
@@ -3141,8 +3143,9 @@
3141
3143
  let url = new URL(request.url);
3142
3144
  let method = request.method;
3143
3145
  let location = createLocation("", createPath(url), null, "default");
3144
- let matches = matchRoutes(dataRoutes, location, basename); // SSR supports HEAD requests while SPA doesn't
3146
+ let matches = matchRoutes(dataRoutes, location, basename);
3145
3147
 
3148
+ // SSR supports HEAD requests while SPA doesn't
3146
3149
  if (!isValidMethod(method) && method !== "HEAD" && method !== "OPTIONS") {
3147
3150
  throw getInternalRouterError(405, {
3148
3151
  method
@@ -3152,9 +3155,7 @@
3152
3155
  pathname: location.pathname
3153
3156
  });
3154
3157
  }
3155
-
3156
3158
  let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);
3157
-
3158
3159
  if (routeId && !match) {
3159
3160
  throw getInternalRouterError(403, {
3160
3161
  pathname: location.pathname,
@@ -3166,52 +3167,40 @@
3166
3167
  pathname: location.pathname
3167
3168
  });
3168
3169
  }
3169
-
3170
3170
  let result = await queryImpl(request, location, matches, requestContext, match);
3171
-
3172
3171
  if (isResponse(result)) {
3173
3172
  return result;
3174
3173
  }
3175
-
3176
3174
  let error = result.errors ? Object.values(result.errors)[0] : undefined;
3177
-
3178
3175
  if (error !== undefined) {
3179
3176
  // If we got back result.errors, that means the loader/action threw
3180
3177
  // _something_ that wasn't a Response, but it's not guaranteed/required
3181
3178
  // to be an `instanceof Error` either, so we have to use throw here to
3182
3179
  // preserve the "error" state outside of queryImpl.
3183
3180
  throw error;
3184
- } // Pick off the right state value to return
3185
-
3181
+ }
3186
3182
 
3183
+ // Pick off the right state value to return
3187
3184
  if (result.actionData) {
3188
3185
  return Object.values(result.actionData)[0];
3189
3186
  }
3190
-
3191
3187
  if (result.loaderData) {
3192
3188
  var _result$activeDeferre;
3193
-
3194
3189
  let data = Object.values(result.loaderData)[0];
3195
-
3196
3190
  if ((_result$activeDeferre = result.activeDeferreds) != null && _result$activeDeferre[match.route.id]) {
3197
3191
  data[UNSAFE_DEFERRED_SYMBOL] = result.activeDeferreds[match.route.id];
3198
3192
  }
3199
-
3200
3193
  return data;
3201
3194
  }
3202
-
3203
3195
  return undefined;
3204
3196
  }
3205
-
3206
3197
  async function queryImpl(request, location, matches, requestContext, routeMatch) {
3207
3198
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
3208
-
3209
3199
  try {
3210
3200
  if (isMutationMethod(request.method.toLowerCase())) {
3211
3201
  let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
3212
3202
  return result;
3213
3203
  }
3214
-
3215
3204
  let result = await loadRouteData(request, matches, requestContext, routeMatch);
3216
3205
  return isResponse(result) ? result : _extends({}, result, {
3217
3206
  actionData: null,
@@ -3225,47 +3214,38 @@
3225
3214
  if (e.type === ResultType.error && !isRedirectResponse(e.response)) {
3226
3215
  throw e.response;
3227
3216
  }
3228
-
3229
3217
  return e.response;
3230
- } // Redirects are always returned since they don't propagate to catch
3218
+ }
3219
+ // Redirects are always returned since they don't propagate to catch
3231
3220
  // boundaries
3232
-
3233
-
3234
3221
  if (isRedirectResponse(e)) {
3235
3222
  return e;
3236
3223
  }
3237
-
3238
3224
  throw e;
3239
3225
  }
3240
3226
  }
3241
-
3242
3227
  async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
3243
3228
  let result;
3244
-
3245
3229
  if (!actionMatch.route.action && !actionMatch.route.lazy) {
3246
3230
  let error = getInternalRouterError(405, {
3247
3231
  method: request.method,
3248
3232
  pathname: new URL(request.url).pathname,
3249
3233
  routeId: actionMatch.route.id
3250
3234
  });
3251
-
3252
3235
  if (isRouteRequest) {
3253
3236
  throw error;
3254
3237
  }
3255
-
3256
3238
  result = {
3257
3239
  type: ResultType.error,
3258
3240
  error
3259
3241
  };
3260
3242
  } else {
3261
3243
  result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, true, isRouteRequest, requestContext);
3262
-
3263
3244
  if (request.signal.aborted) {
3264
3245
  let method = isRouteRequest ? "queryRoute" : "query";
3265
3246
  throw new Error(method + "() call aborted");
3266
3247
  }
3267
3248
  }
3268
-
3269
3249
  if (isRedirectResult(result)) {
3270
3250
  // Uhhhh - this should never happen, we should always throw these from
3271
3251
  // callLoaderOrAction, but the type narrowing here keeps TS happy and we
@@ -3278,29 +3258,24 @@
3278
3258
  }
3279
3259
  });
3280
3260
  }
3281
-
3282
3261
  if (isDeferredResult(result)) {
3283
3262
  let error = getInternalRouterError(400, {
3284
3263
  type: "defer-action"
3285
3264
  });
3286
-
3287
3265
  if (isRouteRequest) {
3288
3266
  throw error;
3289
3267
  }
3290
-
3291
3268
  result = {
3292
3269
  type: ResultType.error,
3293
3270
  error
3294
3271
  };
3295
3272
  }
3296
-
3297
3273
  if (isRouteRequest) {
3298
3274
  // Note: This should only be non-Response values if we get here, since
3299
3275
  // isRouteRequest should throw any Response received in callLoaderOrAction
3300
3276
  if (isErrorResult(result)) {
3301
3277
  throw result.error;
3302
3278
  }
3303
-
3304
3279
  return {
3305
3280
  matches: [actionMatch],
3306
3281
  loaderData: {},
@@ -3316,15 +3291,15 @@
3316
3291
  activeDeferreds: null
3317
3292
  };
3318
3293
  }
3319
-
3320
3294
  if (isErrorResult(result)) {
3321
3295
  // Store off the pending error - we use it to determine which loaders
3322
3296
  // to call and will commit it when we complete the navigation
3323
3297
  let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
3324
3298
  let context = await loadRouteData(request, matches, requestContext, undefined, {
3325
3299
  [boundaryMatch.route.id]: result.error
3326
- }); // action status codes take precedence over loader status codes
3300
+ });
3327
3301
 
3302
+ // action status codes take precedence over loader status codes
3328
3303
  return _extends({}, context, {
3329
3304
  statusCode: isRouteErrorResponse(result.error) ? result.error.status : 500,
3330
3305
  actionData: null,
@@ -3332,9 +3307,9 @@
3332
3307
  [actionMatch.route.id]: result.headers
3333
3308
  } : {})
3334
3309
  });
3335
- } // Create a GET request for the loaders
3336
-
3310
+ }
3337
3311
 
3312
+ // Create a GET request for the loaders
3338
3313
  let loaderRequest = new Request(request.url, {
3339
3314
  headers: request.headers,
3340
3315
  redirect: request.redirect,
@@ -3352,10 +3327,10 @@
3352
3327
  } : {})
3353
3328
  });
3354
3329
  }
3355
-
3356
3330
  async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
3357
- let isRouteRequest = routeMatch != null; // Short circuit if we have no loaders to run (queryRoute())
3331
+ let isRouteRequest = routeMatch != null;
3358
3332
 
3333
+ // Short circuit if we have no loaders to run (queryRoute())
3359
3334
  if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
3360
3335
  throw getInternalRouterError(400, {
3361
3336
  method: request.method,
@@ -3363,10 +3338,10 @@
3363
3338
  routeId: routeMatch == null ? void 0 : routeMatch.route.id
3364
3339
  });
3365
3340
  }
3366
-
3367
3341
  let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]);
3368
- let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy); // Short circuit if we have no loaders to run (query())
3342
+ let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
3369
3343
 
3344
+ // Short circuit if we have no loaders to run (query())
3370
3345
  if (matchesToLoad.length === 0) {
3371
3346
  return {
3372
3347
  matches,
@@ -3380,18 +3355,17 @@
3380
3355
  activeDeferreds: null
3381
3356
  };
3382
3357
  }
3383
-
3384
3358
  let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, true, isRouteRequest, requestContext))]);
3385
-
3386
3359
  if (request.signal.aborted) {
3387
3360
  let method = isRouteRequest ? "queryRoute" : "query";
3388
3361
  throw new Error(method + "() call aborted");
3389
- } // Process and commit output from loaders
3390
-
3362
+ }
3391
3363
 
3364
+ // Process and commit output from loaders
3392
3365
  let activeDeferreds = new Map();
3393
- let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionError, activeDeferreds); // Add a null for any non-loader matches for proper revalidation on the client
3366
+ let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionError, activeDeferreds);
3394
3367
 
3368
+ // Add a null for any non-loader matches for proper revalidation on the client
3395
3369
  let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
3396
3370
  matches.forEach(match => {
3397
3371
  if (!executedLoaders.has(match.route.id)) {
@@ -3403,13 +3377,15 @@
3403
3377
  activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
3404
3378
  });
3405
3379
  }
3406
-
3407
3380
  return {
3408
3381
  dataRoutes,
3409
3382
  query,
3410
3383
  queryRoute
3411
3384
  };
3412
- } //#endregion
3385
+ }
3386
+
3387
+ //#endregion
3388
+
3413
3389
  ////////////////////////////////////////////////////////////////////////////////
3414
3390
  //#region Helpers
3415
3391
  ////////////////////////////////////////////////////////////////////////////////
@@ -3418,7 +3394,6 @@
3418
3394
  * Given an existing StaticHandlerContext and an error thrown at render time,
3419
3395
  * provide an updated StaticHandlerContext suitable for a second SSR render
3420
3396
  */
3421
-
3422
3397
  function getStaticContextFromError(routes, context, error) {
3423
3398
  let newContext = _extends({}, context, {
3424
3399
  statusCode: 500,
@@ -3426,28 +3401,22 @@
3426
3401
  [context._deepestRenderedBoundaryId || routes[0].id]: error
3427
3402
  }
3428
3403
  });
3429
-
3430
3404
  return newContext;
3431
3405
  }
3432
-
3433
3406
  function isSubmissionNavigation(opts) {
3434
3407
  return opts != null && "formData" in opts;
3435
3408
  }
3436
-
3437
3409
  function normalizeTo(location, matches, basename, prependBasename, to, fromRouteId, relative) {
3438
3410
  let contextualMatches;
3439
3411
  let activeRouteMatch;
3440
-
3441
3412
  if (fromRouteId != null && relative !== "path") {
3442
3413
  // Grab matches up to the calling route so our route-relative logic is
3443
3414
  // relative to the correct source route. When using relative:path,
3444
3415
  // fromRouteId is ignored since that is always relative to the current
3445
3416
  // location path
3446
3417
  contextualMatches = [];
3447
-
3448
3418
  for (let match of matches) {
3449
3419
  contextualMatches.push(match);
3450
-
3451
3420
  if (match.route.id === fromRouteId) {
3452
3421
  activeRouteMatch = match;
3453
3422
  break;
@@ -3456,36 +3425,36 @@
3456
3425
  } else {
3457
3426
  contextualMatches = matches;
3458
3427
  activeRouteMatch = matches[matches.length - 1];
3459
- } // Resolve the relative path
3428
+ }
3460
3429
 
3430
+ // Resolve the relative path
3431
+ let path = resolveTo(to ? to : ".", getPathContributingMatches(contextualMatches).map(m => m.pathnameBase), stripBasename(location.pathname, basename) || location.pathname, relative === "path");
3461
3432
 
3462
- let path = resolveTo(to ? to : ".", getPathContributingMatches(contextualMatches).map(m => m.pathnameBase), stripBasename(location.pathname, basename) || location.pathname, relative === "path"); // When `to` is not specified we inherit search/hash from the current
3433
+ // When `to` is not specified we inherit search/hash from the current
3463
3434
  // location, unlike when to="." and we just inherit the path.
3464
3435
  // See https://github.com/remix-run/remix/issues/927
3465
-
3466
3436
  if (to == null) {
3467
3437
  path.search = location.search;
3468
3438
  path.hash = location.hash;
3469
- } // Add an ?index param for matched index routes if we don't already have one
3470
-
3439
+ }
3471
3440
 
3441
+ // Add an ?index param for matched index routes if we don't already have one
3472
3442
  if ((to == null || to === "" || to === ".") && activeRouteMatch && activeRouteMatch.route.index && !hasNakedIndexQuery(path.search)) {
3473
3443
  path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
3474
- } // If we're operating within a basename, prepend it to the pathname. If
3444
+ }
3445
+
3446
+ // If we're operating within a basename, prepend it to the pathname. If
3475
3447
  // this is a root navigation, then just use the raw basename which allows
3476
3448
  // the basename to have full control over the presence of a trailing slash
3477
3449
  // on root actions
3478
-
3479
-
3480
3450
  if (prependBasename && basename !== "/") {
3481
3451
  path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
3482
3452
  }
3483
-
3484
3453
  return createPath(path);
3485
- } // Normalize navigation options by converting formMethod=GET formData objects to
3486
- // URLSearchParams so they behave identically to links with query params
3487
-
3454
+ }
3488
3455
 
3456
+ // Normalize navigation options by converting formMethod=GET formData objects to
3457
+ // URLSearchParams so they behave identically to links with query params
3489
3458
  function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
3490
3459
  // Return location verbatim on non-submission navigations
3491
3460
  if (!opts || !isSubmissionNavigation(opts)) {
@@ -3493,7 +3462,6 @@
3493
3462
  path
3494
3463
  };
3495
3464
  }
3496
-
3497
3465
  if (opts.formMethod && !isValidMethod(opts.formMethod)) {
3498
3466
  return {
3499
3467
  path,
@@ -3501,11 +3469,10 @@
3501
3469
  method: opts.formMethod
3502
3470
  })
3503
3471
  };
3504
- } // Create a Submission on non-GET navigations
3505
-
3472
+ }
3506
3473
 
3474
+ // Create a Submission on non-GET navigations
3507
3475
  let submission;
3508
-
3509
3476
  if (opts.formData) {
3510
3477
  let formMethod = opts.formMethod || "get";
3511
3478
  submission = {
@@ -3514,53 +3481,48 @@
3514
3481
  formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
3515
3482
  formData: opts.formData
3516
3483
  };
3517
-
3518
3484
  if (isMutationMethod(submission.formMethod)) {
3519
3485
  return {
3520
3486
  path,
3521
3487
  submission
3522
3488
  };
3523
3489
  }
3524
- } // Flatten submission onto URLSearchParams for GET submissions
3525
-
3490
+ }
3526
3491
 
3492
+ // Flatten submission onto URLSearchParams for GET submissions
3527
3493
  let parsedPath = parsePath(path);
3528
- let searchParams = convertFormDataToSearchParams(opts.formData); // On GET navigation submissions we can drop the ?index param from the
3494
+ let searchParams = convertFormDataToSearchParams(opts.formData);
3495
+ // On GET navigation submissions we can drop the ?index param from the
3529
3496
  // resulting location since all loaders will run. But fetcher GET submissions
3530
3497
  // only run a single loader so we need to preserve any incoming ?index params
3531
-
3532
3498
  if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
3533
3499
  searchParams.append("index", "");
3534
3500
  }
3535
-
3536
3501
  parsedPath.search = "?" + searchParams;
3537
3502
  return {
3538
3503
  path: createPath(parsedPath),
3539
3504
  submission
3540
3505
  };
3541
- } // Filter out all routes below any caught error as they aren't going to
3542
- // render so we don't need to load them
3543
-
3506
+ }
3544
3507
 
3508
+ // Filter out all routes below any caught error as they aren't going to
3509
+ // render so we don't need to load them
3545
3510
  function getLoaderMatchesUntilBoundary(matches, boundaryId) {
3546
3511
  let boundaryMatches = matches;
3547
-
3548
3512
  if (boundaryId) {
3549
3513
  let index = matches.findIndex(m => m.route.id === boundaryId);
3550
-
3551
3514
  if (index >= 0) {
3552
3515
  boundaryMatches = matches.slice(0, index);
3553
3516
  }
3554
3517
  }
3555
-
3556
3518
  return boundaryMatches;
3557
3519
  }
3558
-
3559
3520
  function getMatchesToLoad(history, state, matches, submission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, basename, pendingActionData, pendingError) {
3560
3521
  let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined;
3561
3522
  let currentUrl = history.createURL(state.location);
3562
- let nextUrl = history.createURL(location); // Pick navigation matches that are net-new or qualify for revalidation
3523
+ let nextUrl = history.createURL(location);
3563
3524
 
3525
+ // Pick navigation matches that are net-new or qualify for revalidation
3564
3526
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3565
3527
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3566
3528
  let navigationMatches = boundaryMatches.filter((match, index) => {
@@ -3568,20 +3530,19 @@
3568
3530
  // We haven't loaded this route yet so we don't know if it's got a loader!
3569
3531
  return true;
3570
3532
  }
3571
-
3572
3533
  if (match.route.loader == null) {
3573
3534
  return false;
3574
- } // Always call the loader on new route instances and pending defer cancellations
3575
-
3535
+ }
3576
3536
 
3537
+ // Always call the loader on new route instances and pending defer cancellations
3577
3538
  if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
3578
3539
  return true;
3579
- } // This is the default implementation for when we revalidate. If the route
3540
+ }
3541
+
3542
+ // This is the default implementation for when we revalidate. If the route
3580
3543
  // provides it's own implementation, then we give them full control but
3581
3544
  // provide this value so they can leverage it if needed after they check
3582
3545
  // their own specific use cases
3583
-
3584
-
3585
3546
  let currentRouteMatch = state.matches[index];
3586
3547
  let nextRouteMatch = match;
3587
3548
  return shouldRevalidateLoader(match, _extends({
@@ -3591,23 +3552,27 @@
3591
3552
  nextParams: nextRouteMatch.params
3592
3553
  }, submission, {
3593
3554
  actionResult,
3594
- defaultShouldRevalidate: // Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
3595
- isRevalidationRequired || // Clicked the same link, resubmitted a GET form
3596
- currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search || // Search params affect all loaders
3555
+ defaultShouldRevalidate:
3556
+ // Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
3557
+ isRevalidationRequired ||
3558
+ // Clicked the same link, resubmitted a GET form
3559
+ currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
3560
+ // Search params affect all loaders
3597
3561
  currentUrl.search !== nextUrl.search || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
3598
3562
  }));
3599
- }); // Pick fetcher.loads that need to be revalidated
3563
+ });
3600
3564
 
3565
+ // Pick fetcher.loads that need to be revalidated
3601
3566
  let revalidatingFetchers = [];
3602
3567
  fetchLoadMatches.forEach((f, key) => {
3603
3568
  // Don't revalidate if fetcher won't be present in the subsequent render
3604
3569
  if (!matches.some(m => m.route.id === f.routeId)) {
3605
3570
  return;
3606
3571
  }
3572
+ let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
3607
3573
 
3608
- let fetcherMatches = matchRoutes(routesToUse, f.path, basename); // If the fetcher path no longer matches, push it in with null matches so
3574
+ // If the fetcher path no longer matches, push it in with null matches so
3609
3575
  // we can trigger a 404 in callLoadersAndMaybeResolveData
3610
-
3611
3576
  if (!fetcherMatches) {
3612
3577
  revalidatingFetchers.push({
3613
3578
  key,
@@ -3619,9 +3584,7 @@
3619
3584
  });
3620
3585
  return;
3621
3586
  }
3622
-
3623
3587
  let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
3624
-
3625
3588
  if (cancelledFetcherLoads.includes(key)) {
3626
3589
  revalidatingFetchers.push({
3627
3590
  key,
@@ -3632,12 +3595,12 @@
3632
3595
  controller: new AbortController()
3633
3596
  });
3634
3597
  return;
3635
- } // Revalidating fetchers are decoupled from the route matches since they
3598
+ }
3599
+
3600
+ // Revalidating fetchers are decoupled from the route matches since they
3636
3601
  // hit a static href, so they _always_ check shouldRevalidate and the
3637
3602
  // default is strictly if a revalidation is explicitly required (action
3638
3603
  // submissions, useRevalidator, X-Remix-Revalidate).
3639
-
3640
-
3641
3604
  let shouldRevalidate = shouldRevalidateLoader(fetcherMatch, _extends({
3642
3605
  currentUrl,
3643
3606
  currentParams: state.matches[state.matches.length - 1].params,
@@ -3648,7 +3611,6 @@
3648
3611
  // Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
3649
3612
  defaultShouldRevalidate: isRevalidationRequired
3650
3613
  }));
3651
-
3652
3614
  if (shouldRevalidate) {
3653
3615
  revalidatingFetchers.push({
3654
3616
  key,
@@ -3662,60 +3624,61 @@
3662
3624
  });
3663
3625
  return [navigationMatches, revalidatingFetchers];
3664
3626
  }
3665
-
3666
3627
  function isNewLoader(currentLoaderData, currentMatch, match) {
3667
- let isNew = // [a] -> [a, b]
3668
- !currentMatch || // [a, b] -> [a, c]
3669
- match.route.id !== currentMatch.route.id; // Handle the case that we don't have data for a re-used route, potentially
3670
- // from a prior error or from a cancelled pending deferred
3628
+ let isNew =
3629
+ // [a] -> [a, b]
3630
+ !currentMatch ||
3631
+ // [a, b] -> [a, c]
3632
+ match.route.id !== currentMatch.route.id;
3671
3633
 
3672
- let isMissingData = currentLoaderData[match.route.id] === undefined; // Always load if this is a net-new route or we don't yet have data
3634
+ // Handle the case that we don't have data for a re-used route, potentially
3635
+ // from a prior error or from a cancelled pending deferred
3636
+ let isMissingData = currentLoaderData[match.route.id] === undefined;
3673
3637
 
3638
+ // Always load if this is a net-new route or we don't yet have data
3674
3639
  return isNew || isMissingData;
3675
3640
  }
3676
-
3677
3641
  function isNewRouteInstance(currentMatch, match) {
3678
3642
  let currentPath = currentMatch.route.path;
3679
- return (// param change for this match, /users/123 -> /users/456
3680
- currentMatch.pathname !== match.pathname || // splat param changed, which is not present in match.path
3643
+ return (
3644
+ // param change for this match, /users/123 -> /users/456
3645
+ currentMatch.pathname !== match.pathname ||
3646
+ // splat param changed, which is not present in match.path
3681
3647
  // e.g. /files/images/avatar.jpg -> files/finances.xls
3682
3648
  currentPath != null && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
3683
3649
  );
3684
3650
  }
3685
-
3686
3651
  function shouldRevalidateLoader(loaderMatch, arg) {
3687
3652
  if (loaderMatch.route.shouldRevalidate) {
3688
3653
  let routeChoice = loaderMatch.route.shouldRevalidate(arg);
3689
-
3690
3654
  if (typeof routeChoice === "boolean") {
3691
3655
  return routeChoice;
3692
3656
  }
3693
3657
  }
3694
-
3695
3658
  return arg.defaultShouldRevalidate;
3696
3659
  }
3660
+
3697
3661
  /**
3698
3662
  * Execute route.lazy() methods to lazily load route modules (loader, action,
3699
3663
  * shouldRevalidate) and update the routeManifest in place which shares objects
3700
3664
  * with dataRoutes so those get updated as well.
3701
3665
  */
3702
-
3703
-
3704
3666
  async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
3705
3667
  if (!route.lazy) {
3706
3668
  return;
3707
3669
  }
3670
+ let lazyRoute = await route.lazy();
3708
3671
 
3709
- let lazyRoute = await route.lazy(); // If the lazy route function was executed and removed by another parallel
3672
+ // If the lazy route function was executed and removed by another parallel
3710
3673
  // call then we can return - first lazy() to finish wins because the return
3711
3674
  // value of lazy is expected to be static
3712
-
3713
3675
  if (!route.lazy) {
3714
3676
  return;
3715
3677
  }
3716
-
3717
3678
  let routeToUpdate = manifest[route.id];
3718
- invariant(routeToUpdate, "No route found in manifest"); // Update the route in place. This should be safe because there's no way
3679
+ invariant(routeToUpdate, "No route found in manifest");
3680
+
3681
+ // Update the route in place. This should be safe because there's no way
3719
3682
  // we could yet be sitting on this route as we can't get there without
3720
3683
  // resolving lazy() first.
3721
3684
  //
@@ -3723,52 +3686,45 @@
3723
3686
  // on the route being updated. The main concern boils down to "does this
3724
3687
  // mutation affect any ongoing navigations or any current state.matches
3725
3688
  // values?". If not, it should be safe to update in place.
3726
-
3727
3689
  let routeUpdates = {};
3728
-
3729
3690
  for (let lazyRouteProperty in lazyRoute) {
3730
3691
  let staticRouteValue = routeToUpdate[lazyRouteProperty];
3731
- let isPropertyStaticallyDefined = staticRouteValue !== undefined && // This property isn't static since it should always be updated based
3692
+ let isPropertyStaticallyDefined = staticRouteValue !== undefined &&
3693
+ // This property isn't static since it should always be updated based
3732
3694
  // on the route updates
3733
3695
  lazyRouteProperty !== "hasErrorBoundary";
3734
3696
  warning(!isPropertyStaticallyDefined, "Route \"" + routeToUpdate.id + "\" has a static property \"" + lazyRouteProperty + "\" " + "defined but its lazy function is also returning a value for this property. " + ("The lazy route property \"" + lazyRouteProperty + "\" will be ignored."));
3735
-
3736
3697
  if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {
3737
3698
  routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];
3738
3699
  }
3739
- } // Mutate the route with the provided updates. Do this first so we pass
3740
- // the updated version to mapRouteProperties
3700
+ }
3741
3701
 
3702
+ // Mutate the route with the provided updates. Do this first so we pass
3703
+ // the updated version to mapRouteProperties
3704
+ Object.assign(routeToUpdate, routeUpdates);
3742
3705
 
3743
- Object.assign(routeToUpdate, routeUpdates); // Mutate the `hasErrorBoundary` property on the route based on the route
3706
+ // Mutate the `hasErrorBoundary` property on the route based on the route
3744
3707
  // updates and remove the `lazy` function so we don't resolve the lazy
3745
3708
  // route again.
3746
-
3747
3709
  Object.assign(routeToUpdate, _extends({}, mapRouteProperties(routeToUpdate), {
3748
3710
  lazy: undefined
3749
3711
  }));
3750
3712
  }
3751
-
3752
3713
  async function callLoaderOrAction(type, request, match, matches, manifest, mapRouteProperties, basename, isStaticRequest, isRouteRequest, requestContext) {
3753
3714
  if (isStaticRequest === void 0) {
3754
3715
  isStaticRequest = false;
3755
3716
  }
3756
-
3757
3717
  if (isRouteRequest === void 0) {
3758
3718
  isRouteRequest = false;
3759
3719
  }
3760
-
3761
3720
  let resultType;
3762
3721
  let result;
3763
3722
  let onReject;
3764
-
3765
3723
  let runHandler = handler => {
3766
3724
  // Setup a promise we can race against so that abort signals short circuit
3767
3725
  let reject;
3768
3726
  let abortPromise = new Promise((_, r) => reject = r);
3769
-
3770
3727
  onReject = () => reject();
3771
-
3772
3728
  request.signal.addEventListener("abort", onReject);
3773
3729
  return Promise.race([handler({
3774
3730
  request,
@@ -3776,10 +3732,8 @@
3776
3732
  context: requestContext
3777
3733
  }), abortPromise]);
3778
3734
  };
3779
-
3780
3735
  try {
3781
3736
  let handler = match.route[type];
3782
-
3783
3737
  if (match.route.lazy) {
3784
3738
  if (handler) {
3785
3739
  // Run statically defined handler in parallel with lazy()
@@ -3789,7 +3743,6 @@
3789
3743
  // Load lazy route module, then run any returned handler
3790
3744
  await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
3791
3745
  handler = match.route[type];
3792
-
3793
3746
  if (handler) {
3794
3747
  // Handler still run even if we got interrupted to maintain consistency
3795
3748
  // with un-abortable behavior of handler execution on non-lazy or
@@ -3821,7 +3774,6 @@
3821
3774
  } else {
3822
3775
  result = await runHandler(handler);
3823
3776
  }
3824
-
3825
3777
  invariant(result !== undefined, "You defined " + (type === "action" ? "an action" : "a loader") + " for route " + ("\"" + match.route.id + "\" but didn't return anything from your `" + type + "` ") + "function. Please return a value or `null`.");
3826
3778
  } catch (e) {
3827
3779
  resultType = ResultType.error;
@@ -3831,14 +3783,15 @@
3831
3783
  request.signal.removeEventListener("abort", onReject);
3832
3784
  }
3833
3785
  }
3834
-
3835
3786
  if (isResponse(result)) {
3836
- let status = result.status; // Process redirects
3787
+ let status = result.status;
3837
3788
 
3789
+ // Process redirects
3838
3790
  if (redirectStatusCodes.has(status)) {
3839
3791
  let location = result.headers.get("Location");
3840
- invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header"); // Support relative routing in internal redirects
3792
+ invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
3841
3793
 
3794
+ // Support relative routing in internal redirects
3842
3795
  if (!ABSOLUTE_URL_REGEX.test(location)) {
3843
3796
  location = normalizeTo(new URL(request.url), matches.slice(0, matches.indexOf(match) + 1), basename, true, location);
3844
3797
  } else if (!isStaticRequest) {
@@ -3848,32 +3801,30 @@
3848
3801
  let currentUrl = new URL(request.url);
3849
3802
  let url = location.startsWith("//") ? new URL(currentUrl.protocol + location) : new URL(location);
3850
3803
  let isSameBasename = stripBasename(url.pathname, basename) != null;
3851
-
3852
3804
  if (url.origin === currentUrl.origin && isSameBasename) {
3853
3805
  location = url.pathname + url.search + url.hash;
3854
3806
  }
3855
- } // Don't process redirects in the router during static requests requests.
3807
+ }
3808
+
3809
+ // Don't process redirects in the router during static requests requests.
3856
3810
  // Instead, throw the Response and let the server handle it with an HTTP
3857
3811
  // redirect. We also update the Location header in place in this flow so
3858
3812
  // basename and relative routing is taken into account
3859
-
3860
-
3861
3813
  if (isStaticRequest) {
3862
3814
  result.headers.set("Location", location);
3863
3815
  throw result;
3864
3816
  }
3865
-
3866
3817
  return {
3867
3818
  type: ResultType.redirect,
3868
3819
  status,
3869
3820
  location,
3870
3821
  revalidate: result.headers.get("X-Remix-Revalidate") !== null
3871
3822
  };
3872
- } // For SSR single-route requests, we want to hand Responses back directly
3823
+ }
3824
+
3825
+ // For SSR single-route requests, we want to hand Responses back directly
3873
3826
  // without unwrapping. We do this with the QueryRouteResponse wrapper
3874
3827
  // interface so we can know whether it was returned or thrown
3875
-
3876
-
3877
3828
  if (isRouteRequest) {
3878
3829
  // eslint-disable-next-line no-throw-literal
3879
3830
  throw {
@@ -3881,17 +3832,15 @@
3881
3832
  response: result
3882
3833
  };
3883
3834
  }
3884
-
3885
3835
  let data;
3886
- let contentType = result.headers.get("Content-Type"); // Check between word boundaries instead of startsWith() due to the last
3836
+ let contentType = result.headers.get("Content-Type");
3837
+ // Check between word boundaries instead of startsWith() due to the last
3887
3838
  // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
3888
-
3889
3839
  if (contentType && /\bapplication\/json\b/.test(contentType)) {
3890
3840
  data = await result.json();
3891
3841
  } else {
3892
3842
  data = await result.text();
3893
3843
  }
3894
-
3895
3844
  if (resultType === ResultType.error) {
3896
3845
  return {
3897
3846
  type: resultType,
@@ -3899,7 +3848,6 @@
3899
3848
  headers: result.headers
3900
3849
  };
3901
3850
  }
3902
-
3903
3851
  return {
3904
3852
  type: ResultType.data,
3905
3853
  data,
@@ -3907,17 +3855,14 @@
3907
3855
  headers: result.headers
3908
3856
  };
3909
3857
  }
3910
-
3911
3858
  if (resultType === ResultType.error) {
3912
3859
  return {
3913
3860
  type: resultType,
3914
3861
  error: result
3915
3862
  };
3916
3863
  }
3917
-
3918
3864
  if (isDeferredData(result)) {
3919
3865
  var _result$init, _result$init2;
3920
-
3921
3866
  return {
3922
3867
  type: ResultType.deferred,
3923
3868
  deferredData: result,
@@ -3925,90 +3870,84 @@
3925
3870
  headers: ((_result$init2 = result.init) == null ? void 0 : _result$init2.headers) && new Headers(result.init.headers)
3926
3871
  };
3927
3872
  }
3928
-
3929
3873
  return {
3930
3874
  type: ResultType.data,
3931
3875
  data: result
3932
3876
  };
3933
- } // Utility method for creating the Request instances for loaders/actions during
3877
+ }
3878
+
3879
+ // Utility method for creating the Request instances for loaders/actions during
3934
3880
  // client-side navigations and fetches. During SSR we will always have a
3935
3881
  // Request instance from the static handler (query/queryRoute)
3936
-
3937
-
3938
3882
  function createClientSideRequest(history, location, signal, submission) {
3939
3883
  let url = history.createURL(stripHashFromPath(location)).toString();
3940
3884
  let init = {
3941
3885
  signal
3942
3886
  };
3943
-
3944
3887
  if (submission && isMutationMethod(submission.formMethod)) {
3945
3888
  let {
3946
3889
  formMethod,
3947
3890
  formEncType,
3948
3891
  formData
3949
- } = submission; // Didn't think we needed this but it turns out unlike other methods, patch
3892
+ } = submission;
3893
+ // Didn't think we needed this but it turns out unlike other methods, patch
3950
3894
  // won't be properly normalized to uppercase and results in a 405 error.
3951
3895
  // See: https://fetch.spec.whatwg.org/#concept-method
3952
-
3953
3896
  init.method = formMethod.toUpperCase();
3954
3897
  init.body = formEncType === "application/x-www-form-urlencoded" ? convertFormDataToSearchParams(formData) : formData;
3955
- } // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
3956
-
3898
+ }
3957
3899
 
3900
+ // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
3958
3901
  return new Request(url, init);
3959
3902
  }
3960
-
3961
3903
  function convertFormDataToSearchParams(formData) {
3962
3904
  let searchParams = new URLSearchParams();
3963
-
3964
3905
  for (let [key, value] of formData.entries()) {
3965
3906
  // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs
3966
3907
  searchParams.append(key, value instanceof File ? value.name : value);
3967
3908
  }
3968
-
3969
3909
  return searchParams;
3970
3910
  }
3971
-
3972
3911
  function processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds) {
3973
3912
  // Fill in loaderData/errors from our loaders
3974
3913
  let loaderData = {};
3975
3914
  let errors = null;
3976
3915
  let statusCode;
3977
3916
  let foundError = false;
3978
- let loaderHeaders = {}; // Process loader results into state.loaderData/state.errors
3917
+ let loaderHeaders = {};
3979
3918
 
3919
+ // Process loader results into state.loaderData/state.errors
3980
3920
  results.forEach((result, index) => {
3981
3921
  let id = matchesToLoad[index].route.id;
3982
3922
  invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
3983
-
3984
3923
  if (isErrorResult(result)) {
3985
3924
  // Look upwards from the matched route for the closest ancestor
3986
3925
  // error boundary, defaulting to the root match
3987
3926
  let boundaryMatch = findNearestBoundary(matches, id);
3988
- let error = result.error; // If we have a pending action error, we report it at the highest-route
3927
+ let error = result.error;
3928
+ // If we have a pending action error, we report it at the highest-route
3989
3929
  // that throws a loader error, and then clear it out to indicate that
3990
3930
  // it was consumed
3991
-
3992
3931
  if (pendingError) {
3993
3932
  error = Object.values(pendingError)[0];
3994
3933
  pendingError = undefined;
3995
3934
  }
3935
+ errors = errors || {};
3996
3936
 
3997
- errors = errors || {}; // Prefer higher error values if lower errors bubble to the same boundary
3998
-
3937
+ // Prefer higher error values if lower errors bubble to the same boundary
3999
3938
  if (errors[boundaryMatch.route.id] == null) {
4000
3939
  errors[boundaryMatch.route.id] = error;
4001
- } // Clear our any prior loaderData for the throwing route
3940
+ }
4002
3941
 
3942
+ // Clear our any prior loaderData for the throwing route
3943
+ loaderData[id] = undefined;
4003
3944
 
4004
- loaderData[id] = undefined; // Once we find our first (highest) error, we set the status code and
3945
+ // Once we find our first (highest) error, we set the status code and
4005
3946
  // prevent deeper status codes from overriding
4006
-
4007
3947
  if (!foundError) {
4008
3948
  foundError = true;
4009
3949
  statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
4010
3950
  }
4011
-
4012
3951
  if (result.headers) {
4013
3952
  loaderHeaders[id] = result.headers;
4014
3953
  }
@@ -4018,27 +3957,26 @@
4018
3957
  loaderData[id] = result.deferredData.data;
4019
3958
  } else {
4020
3959
  loaderData[id] = result.data;
4021
- } // Error status codes always override success status codes, but if all
4022
- // loaders are successful we take the deepest status code.
4023
-
3960
+ }
4024
3961
 
3962
+ // Error status codes always override success status codes, but if all
3963
+ // loaders are successful we take the deepest status code.
4025
3964
  if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
4026
3965
  statusCode = result.statusCode;
4027
3966
  }
4028
-
4029
3967
  if (result.headers) {
4030
3968
  loaderHeaders[id] = result.headers;
4031
3969
  }
4032
3970
  }
4033
- }); // If we didn't consume the pending action error (i.e., all loaders
3971
+ });
3972
+
3973
+ // If we didn't consume the pending action error (i.e., all loaders
4034
3974
  // resolved), then consume it here. Also clear out any loaderData for the
4035
3975
  // throwing route
4036
-
4037
3976
  if (pendingError) {
4038
3977
  errors = pendingError;
4039
3978
  loaderData[Object.keys(pendingError)[0]] = undefined;
4040
3979
  }
4041
-
4042
3980
  return {
4043
3981
  loaderData,
4044
3982
  errors,
@@ -4046,13 +3984,13 @@
4046
3984
  loaderHeaders
4047
3985
  };
4048
3986
  }
4049
-
4050
3987
  function processLoaderData(state, matches, matchesToLoad, results, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds) {
4051
3988
  let {
4052
3989
  loaderData,
4053
3990
  errors
4054
- } = processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds); // Process results from our revalidating fetchers
3991
+ } = processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds);
4055
3992
 
3993
+ // Process results from our revalidating fetchers
4056
3994
  for (let index = 0; index < revalidatingFetchers.length; index++) {
4057
3995
  let {
4058
3996
  key,
@@ -4060,20 +3998,19 @@
4060
3998
  controller
4061
3999
  } = revalidatingFetchers[index];
4062
4000
  invariant(fetcherResults !== undefined && fetcherResults[index] !== undefined, "Did not find corresponding fetcher result");
4063
- let result = fetcherResults[index]; // Process fetcher non-redirect errors
4001
+ let result = fetcherResults[index];
4064
4002
 
4003
+ // Process fetcher non-redirect errors
4065
4004
  if (controller && controller.signal.aborted) {
4066
4005
  // Nothing to do for aborted fetchers
4067
4006
  continue;
4068
4007
  } else if (isErrorResult(result)) {
4069
4008
  let boundaryMatch = findNearestBoundary(state.matches, match == null ? void 0 : match.route.id);
4070
-
4071
4009
  if (!(errors && errors[boundaryMatch.route.id])) {
4072
4010
  errors = _extends({}, errors, {
4073
4011
  [boundaryMatch.route.id]: result.error
4074
4012
  });
4075
4013
  }
4076
-
4077
4014
  state.fetchers.delete(key);
4078
4015
  } else if (isRedirectResult(result)) {
4079
4016
  // Should never get here, redirects should get processed above, but we
@@ -4096,19 +4033,15 @@
4096
4033
  state.fetchers.set(key, doneFetcher);
4097
4034
  }
4098
4035
  }
4099
-
4100
4036
  return {
4101
4037
  loaderData,
4102
4038
  errors
4103
4039
  };
4104
4040
  }
4105
-
4106
4041
  function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
4107
4042
  let mergedLoaderData = _extends({}, newLoaderData);
4108
-
4109
4043
  for (let match of matches) {
4110
4044
  let id = match.route.id;
4111
-
4112
4045
  if (newLoaderData.hasOwnProperty(id)) {
4113
4046
  if (newLoaderData[id] !== undefined) {
4114
4047
  mergedLoaderData[id] = newLoaderData[id];
@@ -4118,24 +4051,21 @@
4118
4051
  // wasn't removed by HMR
4119
4052
  mergedLoaderData[id] = loaderData[id];
4120
4053
  }
4121
-
4122
4054
  if (errors && errors.hasOwnProperty(id)) {
4123
4055
  // Don't keep any loader data below the boundary
4124
4056
  break;
4125
4057
  }
4126
4058
  }
4127
-
4128
4059
  return mergedLoaderData;
4129
- } // Find the nearest error boundary, looking upwards from the leaf route (or the
4060
+ }
4061
+
4062
+ // Find the nearest error boundary, looking upwards from the leaf route (or the
4130
4063
  // route specified by routeId) for the closest ancestor error boundary,
4131
4064
  // defaulting to the root match
4132
-
4133
-
4134
4065
  function findNearestBoundary(matches, routeId) {
4135
4066
  let eligibleMatches = routeId ? matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1) : [...matches];
4136
4067
  return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];
4137
4068
  }
4138
-
4139
4069
  function getShortCircuitMatches(routes) {
4140
4070
  // Prefer a root layout route if present, otherwise shim in a route object
4141
4071
  let route = routes.find(r => r.index || !r.path || r.path === "/") || {
@@ -4151,7 +4081,6 @@
4151
4081
  route
4152
4082
  };
4153
4083
  }
4154
-
4155
4084
  function getInternalRouterError(status, _temp4) {
4156
4085
  let {
4157
4086
  pathname,
@@ -4161,10 +4090,8 @@
4161
4090
  } = _temp4 === void 0 ? {} : _temp4;
4162
4091
  let statusText = "Unknown Server Error";
4163
4092
  let errorMessage = "Unknown @remix-run/router error";
4164
-
4165
4093
  if (status === 400) {
4166
4094
  statusText = "Bad Request";
4167
-
4168
4095
  if (method && pathname && routeId) {
4169
4096
  errorMessage = "You made a " + method + " request to \"" + pathname + "\" but " + ("did not provide a `loader` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
4170
4097
  } else if (type === "defer-action") {
@@ -4178,40 +4105,34 @@
4178
4105
  errorMessage = "No route matches URL \"" + pathname + "\"";
4179
4106
  } else if (status === 405) {
4180
4107
  statusText = "Method Not Allowed";
4181
-
4182
4108
  if (method && pathname && routeId) {
4183
4109
  errorMessage = "You made a " + method.toUpperCase() + " request to \"" + pathname + "\" but " + ("did not provide an `action` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
4184
4110
  } else if (method) {
4185
4111
  errorMessage = "Invalid request method \"" + method.toUpperCase() + "\"";
4186
4112
  }
4187
4113
  }
4188
-
4189
4114
  return new ErrorResponse(status || 500, statusText, new Error(errorMessage), true);
4190
- } // Find any returned redirect errors, starting from the lowest match
4191
-
4115
+ }
4192
4116
 
4117
+ // Find any returned redirect errors, starting from the lowest match
4193
4118
  function findRedirect(results) {
4194
4119
  for (let i = results.length - 1; i >= 0; i--) {
4195
4120
  let result = results[i];
4196
-
4197
4121
  if (isRedirectResult(result)) {
4198
4122
  return result;
4199
4123
  }
4200
4124
  }
4201
4125
  }
4202
-
4203
4126
  function stripHashFromPath(path) {
4204
4127
  let parsedPath = typeof path === "string" ? parsePath(path) : path;
4205
4128
  return createPath(_extends({}, parsedPath, {
4206
4129
  hash: ""
4207
4130
  }));
4208
4131
  }
4209
-
4210
4132
  function isHashChangeOnly(a, b) {
4211
4133
  if (a.pathname !== b.pathname || a.search !== b.search) {
4212
4134
  return false;
4213
4135
  }
4214
-
4215
4136
  if (a.hash === "") {
4216
4137
  // /page -> /page#hash
4217
4138
  return b.hash !== "";
@@ -4221,70 +4142,57 @@
4221
4142
  } else if (b.hash !== "") {
4222
4143
  // /page#hash -> /page#other
4223
4144
  return true;
4224
- } // If the hash is removed the browser will re-perform a request to the server
4225
- // /page#hash -> /page
4226
-
4145
+ }
4227
4146
 
4147
+ // If the hash is removed the browser will re-perform a request to the server
4148
+ // /page#hash -> /page
4228
4149
  return false;
4229
4150
  }
4230
-
4231
4151
  function isDeferredResult(result) {
4232
4152
  return result.type === ResultType.deferred;
4233
4153
  }
4234
-
4235
4154
  function isErrorResult(result) {
4236
4155
  return result.type === ResultType.error;
4237
4156
  }
4238
-
4239
4157
  function isRedirectResult(result) {
4240
4158
  return (result && result.type) === ResultType.redirect;
4241
4159
  }
4242
-
4243
4160
  function isDeferredData(value) {
4244
4161
  let deferred = value;
4245
4162
  return deferred && typeof deferred === "object" && typeof deferred.data === "object" && typeof deferred.subscribe === "function" && typeof deferred.cancel === "function" && typeof deferred.resolveData === "function";
4246
4163
  }
4247
-
4248
4164
  function isResponse(value) {
4249
4165
  return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
4250
4166
  }
4251
-
4252
4167
  function isRedirectResponse(result) {
4253
4168
  if (!isResponse(result)) {
4254
4169
  return false;
4255
4170
  }
4256
-
4257
4171
  let status = result.status;
4258
4172
  let location = result.headers.get("Location");
4259
4173
  return status >= 300 && status <= 399 && location != null;
4260
4174
  }
4261
-
4262
4175
  function isQueryRouteResponse(obj) {
4263
4176
  return obj && isResponse(obj.response) && (obj.type === ResultType.data || ResultType.error);
4264
4177
  }
4265
-
4266
4178
  function isValidMethod(method) {
4267
4179
  return validRequestMethods.has(method.toLowerCase());
4268
4180
  }
4269
-
4270
4181
  function isMutationMethod(method) {
4271
4182
  return validMutationMethods.has(method.toLowerCase());
4272
4183
  }
4273
-
4274
4184
  async function resolveDeferredResults(currentMatches, matchesToLoad, results, signals, isFetcher, currentLoaderData) {
4275
4185
  for (let index = 0; index < results.length; index++) {
4276
4186
  let result = results[index];
4277
- let match = matchesToLoad[index]; // If we don't have a match, then we can have a deferred result to do
4187
+ let match = matchesToLoad[index];
4188
+ // If we don't have a match, then we can have a deferred result to do
4278
4189
  // anything with. This is for revalidating fetchers where the route was
4279
4190
  // removed during HMR
4280
-
4281
4191
  if (!match) {
4282
4192
  continue;
4283
4193
  }
4284
-
4285
4194
  let currentMatch = currentMatches.find(m => m.route.id === match.route.id);
4286
4195
  let isRevalidatingLoader = currentMatch != null && !isNewRouteInstance(currentMatch, match) && (currentLoaderData && currentLoaderData[match.route.id]) !== undefined;
4287
-
4288
4196
  if (isDeferredResult(result) && (isFetcher || isRevalidatingLoader)) {
4289
4197
  // Note: we do not have to touch activeDeferreds here since we race them
4290
4198
  // against the signal in resolveDeferredData and they'll get aborted
@@ -4299,18 +4207,14 @@
4299
4207
  }
4300
4208
  }
4301
4209
  }
4302
-
4303
4210
  async function resolveDeferredData(result, signal, unwrap) {
4304
4211
  if (unwrap === void 0) {
4305
4212
  unwrap = false;
4306
4213
  }
4307
-
4308
4214
  let aborted = await result.deferredData.resolveData(signal);
4309
-
4310
4215
  if (aborted) {
4311
4216
  return;
4312
4217
  }
4313
-
4314
4218
  if (unwrap) {
4315
4219
  try {
4316
4220
  return {
@@ -4325,19 +4229,17 @@
4325
4229
  };
4326
4230
  }
4327
4231
  }
4328
-
4329
4232
  return {
4330
4233
  type: ResultType.data,
4331
4234
  data: result.deferredData.data
4332
4235
  };
4333
4236
  }
4334
-
4335
4237
  function hasNakedIndexQuery(search) {
4336
4238
  return new URLSearchParams(search).getAll("index").some(v => v === "");
4337
- } // Note: This should match the format exported by useMatches, so if you change
4338
- // this please also change that :) Eventually we'll DRY this up
4339
-
4239
+ }
4340
4240
 
4241
+ // Note: This should match the format exported by useMatches, so if you change
4242
+ // this please also change that :) Eventually we'll DRY this up
4341
4243
  function createUseMatchesMatch(match, loaderData) {
4342
4244
  let {
4343
4245
  route,
@@ -4352,20 +4254,18 @@
4352
4254
  handle: route.handle
4353
4255
  };
4354
4256
  }
4355
-
4356
4257
  function getTargetMatch(matches, location) {
4357
4258
  let search = typeof location === "string" ? parsePath(location).search : location.search;
4358
-
4359
4259
  if (matches[matches.length - 1].route.index && hasNakedIndexQuery(search || "")) {
4360
4260
  // Return the leaf index route when index is present
4361
4261
  return matches[matches.length - 1];
4362
- } // Otherwise grab the deepest "path contributing" match (ignoring index and
4262
+ }
4263
+ // Otherwise grab the deepest "path contributing" match (ignoring index and
4363
4264
  // pathless layout routes)
4364
-
4365
-
4366
4265
  let pathMatches = getPathContributingMatches(matches);
4367
4266
  return pathMatches[pathMatches.length - 1];
4368
- } //#endregion
4267
+ }
4268
+ //#endregion
4369
4269
 
4370
4270
  exports.AbortedDeferredError = AbortedDeferredError;
4371
4271
  exports.ErrorResponse = ErrorResponse;