@remix-run/router 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +155 -0
  3. package/__tests__/TestSequences/EncodedReservedCharacters.d.ts +2 -0
  4. package/__tests__/TestSequences/GoBack.d.ts +2 -0
  5. package/__tests__/TestSequences/GoForward.d.ts +2 -0
  6. package/__tests__/TestSequences/InitialLocationDefaultKey.d.ts +2 -0
  7. package/__tests__/TestSequences/InitialLocationHasKey.d.ts +2 -0
  8. package/__tests__/TestSequences/Listen.d.ts +2 -0
  9. package/__tests__/TestSequences/ListenPopOnly.d.ts +2 -0
  10. package/__tests__/TestSequences/PushMissingPathname.d.ts +2 -0
  11. package/__tests__/TestSequences/PushNewLocation.d.ts +2 -0
  12. package/__tests__/TestSequences/PushRelativePathname.d.ts +2 -0
  13. package/__tests__/TestSequences/PushRelativePathnameWarning.d.ts +2 -0
  14. package/__tests__/TestSequences/PushSamePath.d.ts +2 -0
  15. package/__tests__/TestSequences/PushState.d.ts +2 -0
  16. package/__tests__/TestSequences/ReplaceNewLocation.d.ts +2 -0
  17. package/__tests__/TestSequences/ReplaceSamePath.d.ts +2 -0
  18. package/__tests__/TestSequences/ReplaceState.d.ts +2 -0
  19. package/__tests__/browser-test.d.ts +4 -0
  20. package/__tests__/create-path-test.d.ts +1 -0
  21. package/__tests__/hash-base-test.d.ts +4 -0
  22. package/__tests__/hash-test.d.ts +4 -0
  23. package/__tests__/memory-test.d.ts +1 -0
  24. package/__tests__/router-test.d.ts +2 -0
  25. package/__tests__/setup.d.ts +1 -0
  26. package/history.d.ts +226 -0
  27. package/index.d.ts +11 -0
  28. package/index.js +2392 -0
  29. package/index.js.map +1 -0
  30. package/main.js +19 -0
  31. package/package.json +22 -0
  32. package/router.d.ts +298 -0
  33. package/router.development.js +2226 -0
  34. package/router.development.js.map +1 -0
  35. package/router.production.min.js +12 -0
  36. package/router.production.min.js.map +1 -0
  37. package/umd/router.development.js +2418 -0
  38. package/umd/router.development.js.map +1 -0
  39. package/umd/router.production.min.js +12 -0
  40. package/umd/router.production.min.js.map +1 -0
  41. package/utils.d.ts +248 -0
package/index.js ADDED
@@ -0,0 +1,2392 @@
1
+ /**
2
+ * @remix-run/router v0.1.0
3
+ *
4
+ * Copyright (c) Remix Software Inc.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ function _extends() {
12
+ _extends = Object.assign || function (target) {
13
+ for (var i = 1; i < arguments.length; i++) {
14
+ var source = arguments[i];
15
+
16
+ for (var key in source) {
17
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
18
+ target[key] = source[key];
19
+ }
20
+ }
21
+ }
22
+
23
+ return target;
24
+ };
25
+
26
+ return _extends.apply(this, arguments);
27
+ }
28
+
29
+ function _objectWithoutPropertiesLoose(source, excluded) {
30
+ if (source == null) return {};
31
+ var target = {};
32
+ var sourceKeys = Object.keys(source);
33
+ var key, i;
34
+
35
+ for (i = 0; i < sourceKeys.length; i++) {
36
+ key = sourceKeys[i];
37
+ if (excluded.indexOf(key) >= 0) continue;
38
+ target[key] = source[key];
39
+ }
40
+
41
+ return target;
42
+ }
43
+
44
+ ////////////////////////////////////////////////////////////////////////////////
45
+ //#region Types and Constants
46
+ ////////////////////////////////////////////////////////////////////////////////
47
+
48
+ /**
49
+ * Actions represent the type of change to a location value.
50
+ */
51
+ let Action;
52
+ /**
53
+ * The pathname, search, and hash values of a URL.
54
+ */
55
+
56
+ (function (Action) {
57
+ Action["Pop"] = "POP";
58
+ Action["Push"] = "PUSH";
59
+ Action["Replace"] = "REPLACE";
60
+ })(Action || (Action = {}));
61
+
62
+ const PopStateEventType = "popstate"; //#endregion
63
+ ////////////////////////////////////////////////////////////////////////////////
64
+ //#region Memory History
65
+ ////////////////////////////////////////////////////////////////////////////////
66
+
67
+ /**
68
+ * A user-supplied object that describes a location. Used when providing
69
+ * entries to `createMemoryHistory` via its `initialEntries` option.
70
+ */
71
+
72
+ /**
73
+ * Memory history stores the current location in memory. It is designed for use
74
+ * in stateful non-browser environments like tests and React Native.
75
+ */
76
+ function createMemoryHistory(options) {
77
+ if (options === void 0) {
78
+ options = {};
79
+ }
80
+
81
+ let {
82
+ initialEntries = ["/"],
83
+ initialIndex,
84
+ v5Compat = false
85
+ } = options;
86
+ let entries; // Declare so we can access from createMemoryLocation
87
+
88
+ entries = initialEntries.map((entry, index) => createMemoryLocation(entry, null, index === 0 ? "default" : undefined));
89
+ let index = clampIndex(initialIndex == null ? entries.length - 1 : initialIndex);
90
+ let action = Action.Pop;
91
+ let listener = null;
92
+
93
+ function clampIndex(n) {
94
+ return Math.min(Math.max(n, 0), entries.length - 1);
95
+ }
96
+
97
+ function getCurrentLocation() {
98
+ return entries[index];
99
+ }
100
+
101
+ function createMemoryLocation(to, state, key) {
102
+ if (state === void 0) {
103
+ state = null;
104
+ }
105
+
106
+ let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key);
107
+ process.env.NODE_ENV !== "production" ? warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to)) : void 0;
108
+ return location;
109
+ }
110
+
111
+ let history = {
112
+ get index() {
113
+ return index;
114
+ },
115
+
116
+ get action() {
117
+ return action;
118
+ },
119
+
120
+ get location() {
121
+ return getCurrentLocation();
122
+ },
123
+
124
+ createHref(to) {
125
+ return typeof to === "string" ? to : createPath(to);
126
+ },
127
+
128
+ push(to, state) {
129
+ action = Action.Push;
130
+ let nextLocation = createMemoryLocation(to, state);
131
+ index += 1;
132
+ entries.splice(index, entries.length, nextLocation);
133
+
134
+ if (v5Compat && listener) {
135
+ listener({
136
+ action,
137
+ location: nextLocation
138
+ });
139
+ }
140
+ },
141
+
142
+ replace(to, state) {
143
+ action = Action.Replace;
144
+ let nextLocation = createMemoryLocation(to, state);
145
+ entries[index] = nextLocation;
146
+
147
+ if (v5Compat && listener) {
148
+ listener({
149
+ action,
150
+ location: nextLocation
151
+ });
152
+ }
153
+ },
154
+
155
+ go(delta) {
156
+ action = Action.Pop;
157
+ index = clampIndex(index + delta);
158
+
159
+ if (listener) {
160
+ listener({
161
+ action,
162
+ location: getCurrentLocation()
163
+ });
164
+ }
165
+ },
166
+
167
+ listen(fn) {
168
+ listener = fn;
169
+ return () => {
170
+ listener = null;
171
+ };
172
+ }
173
+
174
+ };
175
+ return history;
176
+ } //#endregion
177
+ ////////////////////////////////////////////////////////////////////////////////
178
+ //#region Browser History
179
+ ////////////////////////////////////////////////////////////////////////////////
180
+
181
+ /**
182
+ * A browser history stores the current location in regular URLs in a web
183
+ * browser environment. This is the standard for most web apps and provides the
184
+ * cleanest URLs the browser's address bar.
185
+ *
186
+ * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#browserhistory
187
+ */
188
+
189
+ /**
190
+ * Browser history stores the location in regular URLs. This is the standard for
191
+ * most web apps, but it requires some configuration on the server to ensure you
192
+ * serve the same app at multiple URLs.
193
+ *
194
+ * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createbrowserhistory
195
+ */
196
+ function createBrowserHistory(options) {
197
+ if (options === void 0) {
198
+ options = {};
199
+ }
200
+
201
+ function createBrowserLocation(window, globalHistory) {
202
+ var _globalHistory$state, _globalHistory$state2;
203
+
204
+ let {
205
+ pathname,
206
+ search,
207
+ hash
208
+ } = window.location;
209
+ return createLocation("", {
210
+ pathname,
211
+ search,
212
+ hash
213
+ }, // state defaults to `null` because `window.history.state` does
214
+ ((_globalHistory$state = globalHistory.state) == null ? void 0 : _globalHistory$state.usr) || null, ((_globalHistory$state2 = globalHistory.state) == null ? void 0 : _globalHistory$state2.key) || "default");
215
+ }
216
+
217
+ function createBrowserHref(window, to) {
218
+ return typeof to === "string" ? to : createPath(to);
219
+ }
220
+
221
+ return getUrlBasedHistory(createBrowserLocation, createBrowserHref, null, options);
222
+ } //#endregion
223
+ ////////////////////////////////////////////////////////////////////////////////
224
+ //#region Hash History
225
+ ////////////////////////////////////////////////////////////////////////////////
226
+
227
+ /**
228
+ * A hash history stores the current location in the fragment identifier portion
229
+ * of the URL in a web browser environment.
230
+ *
231
+ * This is ideal for apps that do not control the server for some reason
232
+ * (because the fragment identifier is never sent to the server), including some
233
+ * shared hosting environments that do not provide fine-grained controls over
234
+ * which pages are served at which URLs.
235
+ *
236
+ * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#hashhistory
237
+ */
238
+
239
+ /**
240
+ * Hash history stores the location in window.location.hash. This makes it ideal
241
+ * for situations where you don't want to send the location to the server for
242
+ * some reason, either because you do cannot configure it or the URL space is
243
+ * reserved for something else.
244
+ *
245
+ * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createhashhistory
246
+ */
247
+ function createHashHistory(options) {
248
+ if (options === void 0) {
249
+ options = {};
250
+ }
251
+
252
+ function createHashLocation(window, globalHistory) {
253
+ var _globalHistory$state3, _globalHistory$state4;
254
+
255
+ let {
256
+ pathname = "/",
257
+ search = "",
258
+ hash = ""
259
+ } = parsePath(window.location.hash.substr(1));
260
+ return createLocation("", {
261
+ pathname,
262
+ search,
263
+ hash
264
+ }, // state defaults to `null` because `window.history.state` does
265
+ ((_globalHistory$state3 = globalHistory.state) == null ? void 0 : _globalHistory$state3.usr) || null, ((_globalHistory$state4 = globalHistory.state) == null ? void 0 : _globalHistory$state4.key) || "default");
266
+ }
267
+
268
+ function createHashHref(window, to) {
269
+ let base = window.document.querySelector("base");
270
+ let href = "";
271
+
272
+ if (base && base.getAttribute("href")) {
273
+ let url = window.location.href;
274
+ let hashIndex = url.indexOf("#");
275
+ href = hashIndex === -1 ? url : url.slice(0, hashIndex);
276
+ }
277
+
278
+ return href + "#" + (typeof to === "string" ? to : createPath(to));
279
+ }
280
+
281
+ function validateHashLocation(location, to) {
282
+ process.env.NODE_ENV !== "production" ? warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")") : void 0;
283
+ }
284
+
285
+ return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
286
+ } //#endregion
287
+ ////////////////////////////////////////////////////////////////////////////////
288
+ //#region UTILS
289
+ ////////////////////////////////////////////////////////////////////////////////
290
+
291
+ const readOnly = process.env.NODE_ENV !== "production" ? obj => Object.freeze(obj) : obj => obj;
292
+
293
+ function warning(cond, message) {
294
+ if (!cond) {
295
+ // eslint-disable-next-line no-console
296
+ if (typeof console !== "undefined") console.warn(message);
297
+
298
+ try {
299
+ // Welcome to debugging history!
300
+ //
301
+ // This error is thrown as a convenience so you can more easily
302
+ // find the source for a warning that appears in the console by
303
+ // enabling "pause on exceptions" in your JavaScript debugger.
304
+ throw new Error(message); // eslint-disable-next-line no-empty
305
+ } catch (e) {}
306
+ }
307
+ }
308
+
309
+ function createKey() {
310
+ return Math.random().toString(36).substr(2, 8);
311
+ }
312
+ /**
313
+ * For browser-based histories, we combine the state and key into an object
314
+ */
315
+
316
+
317
+ function getHistoryState(location) {
318
+ return {
319
+ usr: location.state,
320
+ key: location.key
321
+ };
322
+ }
323
+ /**
324
+ * Creates a Location object with a unique key from the given Path
325
+ */
326
+
327
+
328
+ function createLocation(current, to, state, key) {
329
+ if (state === void 0) {
330
+ state = null;
331
+ }
332
+
333
+ return readOnly(_extends({
334
+ pathname: typeof current === "string" ? current : current.pathname,
335
+ search: "",
336
+ hash: ""
337
+ }, typeof to === "string" ? parsePath(to) : to, {
338
+ state,
339
+ // TODO: This could be cleaned up. push/replace should probably just take
340
+ // full Locations now and avoid the need to run through this flow at all
341
+ // But that's a pretty big refactor to the current test suite so going to
342
+ // keep as is for the time being and just let any incoming keys take precedence
343
+ key: (to == null ? void 0 : to.key) || key || createKey()
344
+ }));
345
+ }
346
+ /**
347
+ * Creates a string URL path from the given pathname, search, and hash components.
348
+ */
349
+
350
+ function createPath(_ref) {
351
+ let {
352
+ pathname = "/",
353
+ search = "",
354
+ hash = ""
355
+ } = _ref;
356
+ if (search && search !== "?") pathname += search.charAt(0) === "?" ? search : "?" + search;
357
+ if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : "#" + hash;
358
+ return pathname;
359
+ }
360
+ /**
361
+ * Parses a string URL path into its separate pathname, search, and hash components.
362
+ */
363
+
364
+ function parsePath(path) {
365
+ let parsedPath = {};
366
+
367
+ if (path) {
368
+ let hashIndex = path.indexOf("#");
369
+
370
+ if (hashIndex >= 0) {
371
+ parsedPath.hash = path.substr(hashIndex);
372
+ path = path.substr(0, hashIndex);
373
+ }
374
+
375
+ let searchIndex = path.indexOf("?");
376
+
377
+ if (searchIndex >= 0) {
378
+ parsedPath.search = path.substr(searchIndex);
379
+ path = path.substr(0, searchIndex);
380
+ }
381
+
382
+ if (path) {
383
+ parsedPath.pathname = path;
384
+ }
385
+ }
386
+
387
+ return parsedPath;
388
+ }
389
+
390
+ function getUrlBasedHistory(getLocation, createHref, validateLocation, options) {
391
+ if (options === void 0) {
392
+ options = {};
393
+ }
394
+
395
+ let {
396
+ window = document.defaultView,
397
+ v5Compat = false
398
+ } = options;
399
+ let globalHistory = window.history;
400
+ let action = Action.Pop;
401
+ let listener = null;
402
+
403
+ function handlePop() {
404
+ action = Action.Pop;
405
+
406
+ if (listener) {
407
+ listener({
408
+ action,
409
+ location: history.location
410
+ });
411
+ }
412
+ }
413
+
414
+ function push(to, state) {
415
+ action = Action.Push;
416
+ let location = createLocation(history.location, to, state);
417
+ validateLocation == null ? void 0 : validateLocation(location, to);
418
+ let historyState = getHistoryState(location);
419
+ let url = history.createHref(location); // try...catch because iOS limits us to 100 pushState calls :/
420
+
421
+ try {
422
+ globalHistory.pushState(historyState, "", url);
423
+ } catch (error) {
424
+ // They are going to lose state here, but there is no real
425
+ // way to warn them about it since the page will refresh...
426
+ window.location.assign(url);
427
+ }
428
+
429
+ if (v5Compat && listener) {
430
+ listener({
431
+ action,
432
+ location
433
+ });
434
+ }
435
+ }
436
+
437
+ function replace(to, state) {
438
+ action = Action.Replace;
439
+ let location = createLocation(history.location, to, state);
440
+ validateLocation == null ? void 0 : validateLocation(location, to);
441
+ let historyState = getHistoryState(location);
442
+ let url = history.createHref(location);
443
+ globalHistory.replaceState(historyState, "", url);
444
+
445
+ if (v5Compat && listener) {
446
+ listener({
447
+ action,
448
+ location: location
449
+ });
450
+ }
451
+ }
452
+
453
+ let history = {
454
+ get action() {
455
+ return action;
456
+ },
457
+
458
+ get location() {
459
+ return getLocation(window, globalHistory);
460
+ },
461
+
462
+ listen(fn) {
463
+ if (listener) {
464
+ throw new Error("A history only accepts one active listener");
465
+ }
466
+
467
+ window.addEventListener(PopStateEventType, handlePop);
468
+ listener = fn;
469
+ return () => {
470
+ window.removeEventListener(PopStateEventType, handlePop);
471
+ listener = null;
472
+ };
473
+ },
474
+
475
+ createHref(to) {
476
+ return createHref(window, to);
477
+ },
478
+
479
+ push,
480
+ replace,
481
+
482
+ go(n) {
483
+ return globalHistory.go(n);
484
+ }
485
+
486
+ };
487
+ return history;
488
+ } //#endregion
489
+
490
+ /**
491
+ * Matches the given routes to a location and returns the match data.
492
+ *
493
+ * @see https://reactrouter.com/docs/en/v6/utils/match-routes
494
+ */
495
+ function matchRoutes(routes, locationArg, basename) {
496
+ if (basename === void 0) {
497
+ basename = "/";
498
+ }
499
+
500
+ let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
501
+ let pathname = stripBasename(location.pathname || "/", basename);
502
+
503
+ if (pathname == null) {
504
+ return null;
505
+ }
506
+
507
+ let branches = flattenRoutes(routes);
508
+ rankRouteBranches(branches);
509
+ let matches = null;
510
+
511
+ for (let i = 0; matches == null && i < branches.length; ++i) {
512
+ matches = matchRouteBranch(branches[i], pathname);
513
+ }
514
+
515
+ return matches;
516
+ }
517
+
518
+ function flattenRoutes(routes, branches, parentsMeta, parentPath) {
519
+ if (branches === void 0) {
520
+ branches = [];
521
+ }
522
+
523
+ if (parentsMeta === void 0) {
524
+ parentsMeta = [];
525
+ }
526
+
527
+ if (parentPath === void 0) {
528
+ parentPath = "";
529
+ }
530
+
531
+ routes.forEach((route, index) => {
532
+ let meta = {
533
+ relativePath: route.path || "",
534
+ caseSensitive: route.caseSensitive === true,
535
+ childrenIndex: index,
536
+ route
537
+ };
538
+
539
+ if (meta.relativePath.startsWith("/")) {
540
+ !meta.relativePath.startsWith(parentPath) ? process.env.NODE_ENV !== "production" ? invariant(false, "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.") : invariant(false) : void 0;
541
+ meta.relativePath = meta.relativePath.slice(parentPath.length);
542
+ }
543
+
544
+ let path = joinPaths([parentPath, meta.relativePath]);
545
+ let routesMeta = parentsMeta.concat(meta); // Add the children before adding this route to the array so we traverse the
546
+ // route tree depth-first and child routes appear before their parents in
547
+ // the "flattened" version.
548
+
549
+ if (route.children && route.children.length > 0) {
550
+ !(route.index !== true) ? process.env.NODE_ENV !== "production" ? invariant(false, "Index routes must not have child routes. Please remove " + ("all child routes from route path \"" + path + "\".")) : invariant(false) : void 0;
551
+ flattenRoutes(route.children, branches, routesMeta, path);
552
+ } // Routes without a path shouldn't ever match by themselves unless they are
553
+ // index routes, so don't add them to the list of possible branches.
554
+
555
+
556
+ if (route.path == null && !route.index) {
557
+ return;
558
+ }
559
+
560
+ branches.push({
561
+ path,
562
+ score: computeScore(path, route.index),
563
+ routesMeta
564
+ });
565
+ });
566
+ return branches;
567
+ }
568
+
569
+ function rankRouteBranches(branches) {
570
+ branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
571
+ : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
572
+ }
573
+
574
+ const paramRe = /^:\w+$/;
575
+ const dynamicSegmentValue = 3;
576
+ const indexRouteValue = 2;
577
+ const emptySegmentValue = 1;
578
+ const staticSegmentValue = 10;
579
+ const splatPenalty = -2;
580
+
581
+ const isSplat = s => s === "*";
582
+
583
+ function computeScore(path, index) {
584
+ let segments = path.split("/");
585
+ let initialScore = segments.length;
586
+
587
+ if (segments.some(isSplat)) {
588
+ initialScore += splatPenalty;
589
+ }
590
+
591
+ if (index) {
592
+ initialScore += indexRouteValue;
593
+ }
594
+
595
+ return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
596
+ }
597
+
598
+ function compareIndexes(a, b) {
599
+ let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
600
+ return siblings ? // If two routes are siblings, we should try to match the earlier sibling
601
+ // first. This allows people to have fine-grained control over the matching
602
+ // behavior by simply putting routes with identical paths in the order they
603
+ // want them tried.
604
+ a[a.length - 1] - b[b.length - 1] : // Otherwise, it doesn't really make sense to rank non-siblings by index,
605
+ // so they sort equally.
606
+ 0;
607
+ }
608
+
609
+ function matchRouteBranch(branch, pathname) {
610
+ let {
611
+ routesMeta
612
+ } = branch;
613
+ let matchedParams = {};
614
+ let matchedPathname = "/";
615
+ let matches = [];
616
+
617
+ for (let i = 0; i < routesMeta.length; ++i) {
618
+ let meta = routesMeta[i];
619
+ let end = i === routesMeta.length - 1;
620
+ let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
621
+ let match = matchPath({
622
+ path: meta.relativePath,
623
+ caseSensitive: meta.caseSensitive,
624
+ end
625
+ }, remainingPathname);
626
+ if (!match) return null;
627
+ Object.assign(matchedParams, match.params);
628
+ let route = meta.route;
629
+ matches.push({
630
+ // TODO: Can this as be avoided?
631
+ params: matchedParams,
632
+ pathname: joinPaths([matchedPathname, match.pathname]),
633
+ pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),
634
+ route
635
+ });
636
+
637
+ if (match.pathnameBase !== "/") {
638
+ matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
639
+ }
640
+ }
641
+
642
+ return matches;
643
+ }
644
+ /**
645
+ * Returns a path with params interpolated.
646
+ *
647
+ * @see https://reactrouter.com/docs/en/v6/utils/generate-path
648
+ */
649
+
650
+
651
+ function generatePath(path, params) {
652
+ if (params === void 0) {
653
+ params = {};
654
+ }
655
+
656
+ return path.replace(/:(\w+)/g, (_, key) => {
657
+ !(params[key] != null) ? process.env.NODE_ENV !== "production" ? invariant(false, "Missing \":" + key + "\" param") : invariant(false) : void 0;
658
+ return params[key];
659
+ }).replace(/\/*\*$/, _ => params["*"] == null ? "" : params["*"].replace(/^\/*/, "/"));
660
+ }
661
+ /**
662
+ * A PathPattern is used to match on some portion of a URL pathname.
663
+ */
664
+
665
+ /**
666
+ * Performs pattern matching on a URL pathname and returns information about
667
+ * the match.
668
+ *
669
+ * @see https://reactrouter.com/docs/en/v6/utils/match-path
670
+ */
671
+ function matchPath(pattern, pathname) {
672
+ if (typeof pattern === "string") {
673
+ pattern = {
674
+ path: pattern,
675
+ caseSensitive: false,
676
+ end: true
677
+ };
678
+ }
679
+
680
+ let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
681
+ let match = pathname.match(matcher);
682
+ if (!match) return null;
683
+ let matchedPathname = match[0];
684
+ let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
685
+ let captureGroups = match.slice(1);
686
+ let params = paramNames.reduce((memo, paramName, index) => {
687
+ // We need to compute the pathnameBase here using the raw splat value
688
+ // instead of using params["*"] later because it will be decoded then
689
+ if (paramName === "*") {
690
+ let splatValue = captureGroups[index] || "";
691
+ pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
692
+ }
693
+
694
+ memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || "", paramName);
695
+ return memo;
696
+ }, {});
697
+ return {
698
+ params,
699
+ pathname: matchedPathname,
700
+ pathnameBase,
701
+ pattern
702
+ };
703
+ }
704
+
705
+ function compilePath(path, caseSensitive, end) {
706
+ if (caseSensitive === void 0) {
707
+ caseSensitive = false;
708
+ }
709
+
710
+ if (end === void 0) {
711
+ end = true;
712
+ }
713
+
714
+ process.env.NODE_ENV !== "production" ? warning$1(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(/\*$/, "/*") + "\".")) : void 0;
715
+ let paramNames = [];
716
+ let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
717
+ .replace(/^\/*/, "/") // Make sure it has a leading /
718
+ .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
719
+ .replace(/:(\w+)/g, (_, paramName) => {
720
+ paramNames.push(paramName);
721
+ return "([^\\/]+)";
722
+ });
723
+
724
+ if (path.endsWith("*")) {
725
+ paramNames.push("*");
726
+ regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
727
+ : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
728
+ } else {
729
+ regexpSource += end ? "\\/*$" // When matching to the end, ignore trailing slashes
730
+ : // Otherwise, match a word boundary or a proceeding /. The word boundary restricts
731
+ // parent routes to matching only their own words and nothing more, e.g. parent
732
+ // route "/home" should not match "/home2".
733
+ // Additionally, allow paths starting with `.`, `-`, `~`, and url-encoded entities,
734
+ // but do not consume the character in the matched path so they can match against
735
+ // nested paths.
736
+ "(?:(?=[@.~-]|%[0-9A-F]{2})|\\b|\\/|$)";
737
+ }
738
+
739
+ let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
740
+ return [matcher, paramNames];
741
+ }
742
+
743
+ function safelyDecodeURIComponent(value, paramName) {
744
+ try {
745
+ return decodeURIComponent(value);
746
+ } catch (error) {
747
+ process.env.NODE_ENV !== "production" ? warning$1(false, "The value for the URL param \"" + paramName + "\" will not be decoded because" + (" the string \"" + value + "\" is a malformed URL segment. This is probably") + (" due to a bad percent encoding (" + error + ").")) : void 0;
748
+ return value;
749
+ }
750
+ }
751
+ /**
752
+ * @private
753
+ */
754
+
755
+
756
+ function stripBasename(pathname, basename) {
757
+ if (basename === "/") return pathname;
758
+
759
+ if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
760
+ return null;
761
+ }
762
+
763
+ let nextChar = pathname.charAt(basename.length);
764
+
765
+ if (nextChar && nextChar !== "/") {
766
+ // pathname does not start with basename/
767
+ return null;
768
+ }
769
+
770
+ return pathname.slice(basename.length) || "/";
771
+ }
772
+ /**
773
+ * @private
774
+ */
775
+
776
+ function invariant(value, message) {
777
+ if (value === false || value === null || typeof value === "undefined") {
778
+ throw new Error(message);
779
+ }
780
+ }
781
+ /**
782
+ * @private
783
+ */
784
+
785
+ function warning$1(cond, message) {
786
+ if (!cond) {
787
+ // eslint-disable-next-line no-console
788
+ if (typeof console !== "undefined") console.warn(message);
789
+
790
+ try {
791
+ // Welcome to debugging React Router!
792
+ //
793
+ // This error is thrown as a convenience so you can more easily
794
+ // find the source for a warning that appears in the console by
795
+ // enabling "pause on exceptions" in your JavaScript debugger.
796
+ throw new Error(message); // eslint-disable-next-line no-empty
797
+ } catch (e) {}
798
+ }
799
+ }
800
+ /**
801
+ * Returns a resolved path object relative to the given pathname.
802
+ *
803
+ * @see https://reactrouter.com/docs/en/v6/utils/resolve-path
804
+ */
805
+
806
+ function resolvePath(to, fromPathname) {
807
+ if (fromPathname === void 0) {
808
+ fromPathname = "/";
809
+ }
810
+
811
+ let {
812
+ pathname: toPathname,
813
+ search = "",
814
+ hash = ""
815
+ } = typeof to === "string" ? parsePath(to) : to;
816
+ let pathname = toPathname ? toPathname.startsWith("/") ? toPathname : resolvePathname(toPathname, fromPathname) : fromPathname;
817
+ return {
818
+ pathname,
819
+ search: normalizeSearch(search),
820
+ hash: normalizeHash(hash)
821
+ };
822
+ }
823
+
824
+ function resolvePathname(relativePath, fromPathname) {
825
+ let segments = fromPathname.replace(/\/+$/, "").split("/");
826
+ let relativeSegments = relativePath.split("/");
827
+ relativeSegments.forEach(segment => {
828
+ if (segment === "..") {
829
+ // Keep the root "" segment so the pathname starts at /
830
+ if (segments.length > 1) segments.pop();
831
+ } else if (segment !== ".") {
832
+ segments.push(segment);
833
+ }
834
+ });
835
+ return segments.length > 1 ? segments.join("/") : "/";
836
+ }
837
+ /**
838
+ * @private
839
+ */
840
+
841
+
842
+ function resolveTo(toArg, routePathnames, locationPathname) {
843
+ let to = typeof toArg === "string" ? parsePath(toArg) : toArg;
844
+ let toPathname = toArg === "" || to.pathname === "" ? "/" : to.pathname; // If a pathname is explicitly provided in `to`, it should be relative to the
845
+ // route context. This is explained in `Note on `<Link to>` values` in our
846
+ // migration guide from v5 as a means of disambiguation between `to` values
847
+ // that begin with `/` and those that do not. However, this is problematic for
848
+ // `to` values that do not provide a pathname. `to` can simply be a search or
849
+ // hash string, in which case we should assume that the navigation is relative
850
+ // to the current location's pathname and *not* the route pathname.
851
+
852
+ let from;
853
+
854
+ if (toPathname == null) {
855
+ from = locationPathname;
856
+ } else {
857
+ let routePathnameIndex = routePathnames.length - 1;
858
+
859
+ if (toPathname.startsWith("..")) {
860
+ let toSegments = toPathname.split("/"); // Each leading .. segment means "go up one route" instead of "go up one
861
+ // URL segment". This is a key difference from how <a href> works and a
862
+ // major reason we call this a "to" value instead of a "href".
863
+
864
+ while (toSegments[0] === "..") {
865
+ toSegments.shift();
866
+ routePathnameIndex -= 1;
867
+ }
868
+
869
+ to.pathname = toSegments.join("/");
870
+ } // If there are more ".." segments than parent routes, resolve relative to
871
+ // the root / URL.
872
+
873
+
874
+ from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
875
+ }
876
+
877
+ let path = resolvePath(to, from); // Ensure the pathname has a trailing slash if the original to value had one.
878
+
879
+ if (toPathname && toPathname !== "/" && toPathname.endsWith("/") && !path.pathname.endsWith("/")) {
880
+ path.pathname += "/";
881
+ }
882
+
883
+ return path;
884
+ }
885
+ /**
886
+ * @private
887
+ */
888
+
889
+ function getToPathname(to) {
890
+ // Empty strings should be treated the same as / paths
891
+ return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
892
+ }
893
+ /**
894
+ * @private
895
+ */
896
+
897
+ const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
898
+ /**
899
+ * @private
900
+ */
901
+
902
+ const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
903
+ /**
904
+ * @private
905
+ */
906
+
907
+ const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
908
+ /**
909
+ * @private
910
+ */
911
+
912
+ const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
913
+
914
+ /**
915
+ * This is a shortcut for creating `application/json` responses. Converts `data`
916
+ * to JSON and sets the `Content-Type` header.
917
+ */
918
+ const json = function json(data, init) {
919
+ if (init === void 0) {
920
+ init = {};
921
+ }
922
+
923
+ let responseInit = typeof init === "number" ? {
924
+ status: init
925
+ } : init;
926
+ let headers = new Headers(responseInit.headers);
927
+
928
+ if (!headers.has("Content-Type")) {
929
+ headers.set("Content-Type", "application/json; charset=utf-8");
930
+ }
931
+
932
+ return new Response(JSON.stringify(data), _extends({}, responseInit, {
933
+ headers
934
+ }));
935
+ };
936
+
937
+ /**
938
+ * A redirect response. Sets the status code and the `Location` header.
939
+ * Defaults to "302 Found".
940
+ */
941
+ const redirect = function redirect(url, init) {
942
+ if (init === void 0) {
943
+ init = 302;
944
+ }
945
+
946
+ let responseInit = init;
947
+
948
+ if (typeof responseInit === "number") {
949
+ responseInit = {
950
+ status: responseInit
951
+ };
952
+ } else if (typeof responseInit.status === "undefined") {
953
+ responseInit.status = 302;
954
+ }
955
+
956
+ let headers = new Headers(responseInit.headers);
957
+ headers.set("Location", url);
958
+ return new Response(null, _extends({}, responseInit, {
959
+ headers
960
+ }));
961
+ };
962
+ /**
963
+ * @private
964
+ * Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
965
+ */
966
+
967
+ class ErrorResponse {
968
+ constructor(status, statusText, data) {
969
+ this.status = status;
970
+ this.statusText = statusText || "";
971
+ this.data = data;
972
+ }
973
+
974
+ }
975
+ /**
976
+ * Check if the given error is an ErrorResponse generated from a 4xx/5xx
977
+ * Response throw from an action/loader
978
+ */
979
+
980
+ function isRouteErrorResponse(e) {
981
+ return e instanceof ErrorResponse;
982
+ }
983
+
984
+ //#region Types and Constants
985
+ ////////////////////////////////////////////////////////////////////////////////
986
+
987
+ /**
988
+ * Map of routeId -> data returned from a loader/action/error
989
+ */
990
+
991
+ var ResultType;
992
+ /**
993
+ * Successful result from a loader or action
994
+ */
995
+
996
+ (function (ResultType) {
997
+ ResultType["data"] = "data";
998
+ ResultType["redirect"] = "redirect";
999
+ ResultType["error"] = "error";
1000
+ })(ResultType || (ResultType = {}));
1001
+
1002
+ const IDLE_NAVIGATION = {
1003
+ state: "idle",
1004
+ location: undefined,
1005
+ formMethod: undefined,
1006
+ formAction: undefined,
1007
+ formEncType: undefined,
1008
+ formData: undefined
1009
+ };
1010
+ const IDLE_FETCHER = {
1011
+ state: "idle",
1012
+ data: undefined,
1013
+ formMethod: undefined,
1014
+ formAction: undefined,
1015
+ formEncType: undefined,
1016
+ formData: undefined
1017
+ }; //#endregion
1018
+ ////////////////////////////////////////////////////////////////////////////////
1019
+ //#region createRouter
1020
+ ////////////////////////////////////////////////////////////////////////////////
1021
+
1022
+ /**
1023
+ * Create a router and listen to history POP navigations
1024
+ */
1025
+
1026
+ function createRouter(init) {
1027
+ var _init$hydrationData, _init$hydrationData2, _init$hydrationData4, _init$hydrationData5, _init$hydrationData6;
1028
+
1029
+ !(init.routes.length > 0) ? process.env.NODE_ENV !== "production" ? invariant(false, "You must provide a non-empty routes array to use Data Routers") : invariant(false) : void 0;
1030
+ let dataRoutes = convertRoutesToDataRoutes(init.routes); // Cleanup function for history
1031
+
1032
+ let unlistenHistory = null; // Externally-provided function to call on all state changes
1033
+
1034
+ let subscriber = null; // Externally-provided object to hold scroll restoration locations during routing
1035
+
1036
+ let savedScrollPositions = null; // Externally-provided function to get scroll restoration keys
1037
+
1038
+ let getScrollRestorationKey = null; // Externally-provided function to get current scroll position
1039
+
1040
+ let getScrollPosition = null; // One-time flag to control the initial hydration scroll restoration. Because
1041
+ // we don't get the saved positions from <ScrollRestoration /> until _after_
1042
+ // the initial render, we need to manually trigger a separate updateState to
1043
+ // send along the restoreScrollPosition
1044
+
1045
+ let initialScrollRestored = false;
1046
+ let initialMatches = matchRoutes(dataRoutes, init.history.location) || getNotFoundMatches(dataRoutes); // If we received hydration data without errors - detect if any matched
1047
+ // routes with loaders did not get provided loaderData, and if so launch an
1048
+ // initial data re-load to fetch everything
1049
+
1050
+ let foundMissingHydrationData = ((_init$hydrationData = init.hydrationData) == null ? void 0 : _init$hydrationData.errors) == null && ((_init$hydrationData2 = init.hydrationData) == null ? void 0 : _init$hydrationData2.loaderData) != null && initialMatches.filter(m => m.route.loader).some(m => {
1051
+ var _init$hydrationData3, _init$hydrationData3$;
1052
+
1053
+ return ((_init$hydrationData3 = init.hydrationData) == null ? void 0 : (_init$hydrationData3$ = _init$hydrationData3.loaderData) == null ? void 0 : _init$hydrationData3$[m.route.id]) === undefined;
1054
+ });
1055
+
1056
+ if (foundMissingHydrationData) {
1057
+ console.warn("The provided hydration data did not find loaderData for all matched " + "routes with loaders. Performing a full initial data load");
1058
+ }
1059
+
1060
+ let router;
1061
+ let state = {
1062
+ historyAction: init.history.action,
1063
+ location: init.history.location,
1064
+ // If we do not match a user-provided-route, fall back to the root
1065
+ // to allow the errorElement to take over
1066
+ matches: initialMatches,
1067
+ initialized: init.hydrationData != null && !foundMissingHydrationData,
1068
+ navigation: IDLE_NAVIGATION,
1069
+ restoreScrollPosition: null,
1070
+ resetScrollPosition: true,
1071
+ revalidation: "idle",
1072
+ loaderData: foundMissingHydrationData ? {} : ((_init$hydrationData4 = init.hydrationData) == null ? void 0 : _init$hydrationData4.loaderData) || {},
1073
+ actionData: ((_init$hydrationData5 = init.hydrationData) == null ? void 0 : _init$hydrationData5.actionData) || null,
1074
+ errors: ((_init$hydrationData6 = init.hydrationData) == null ? void 0 : _init$hydrationData6.errors) || null,
1075
+ fetchers: new Map()
1076
+ }; // -- Stateful internal variables to manage navigations --
1077
+ // Current navigation in progress (to be committed in completeNavigation)
1078
+
1079
+ let pendingAction = null; // AbortController for the active navigation
1080
+
1081
+ let pendingNavigationController; // We use this to avoid touching history in completeNavigation if a
1082
+ // revalidation is entirely uninterrupted
1083
+
1084
+ let isUninterruptedRevalidation = false; // Use this internal flag to force revalidation of all loaders:
1085
+ // - submissions (completed or interrupted)
1086
+ // - useRevalidate()
1087
+ // - X-Remix-Revalidate (from redirect)
1088
+
1089
+ let isRevalidationRequired = false; // AbortControllers for any in-flight fetchers
1090
+
1091
+ let fetchControllers = new Map(); // Track loads based on the order in which they started
1092
+
1093
+ let incrementingLoadId = 0; // Track the outstanding pending navigation data load to be compared against
1094
+ // the globally incrementing load when a fetcher load lands after a completed
1095
+ // navigation
1096
+
1097
+ let pendingNavigationLoadId = -1; // Fetchers that triggered data reloads as a result of their actions
1098
+
1099
+ let fetchReloadIds = new Map(); // Fetchers that triggered redirect navigations from their actions
1100
+
1101
+ let fetchRedirectIds = new Set(); // Most recent href/match for fetcher.load calls for fetchers
1102
+
1103
+ let fetchLoadMatches = new Map(); // Initialize the router, all side effects should be kicked off from here.
1104
+ // Implemented as a Fluent API for ease of:
1105
+ // let router = createRouter(init).initialize();
1106
+
1107
+ function initialize() {
1108
+ // If history informs us of a POP navigation, start the navigation but do not update
1109
+ // state. We'll update our own state once the navigation completes
1110
+ unlistenHistory = init.history.listen(_ref => {
1111
+ let {
1112
+ action: historyAction,
1113
+ location
1114
+ } = _ref;
1115
+ return startNavigation(historyAction, location);
1116
+ }); // Kick off initial data load if needed. Use Pop to avoid modifying history
1117
+
1118
+ if (!state.initialized) {
1119
+ startNavigation(Action.Pop, state.location);
1120
+ }
1121
+
1122
+ return router;
1123
+ } // Clean up a router and it's side effects
1124
+
1125
+
1126
+ function dispose() {
1127
+ var _pendingNavigationCon;
1128
+
1129
+ if (unlistenHistory) {
1130
+ unlistenHistory();
1131
+ }
1132
+
1133
+ subscriber = null;
1134
+ (_pendingNavigationCon = pendingNavigationController) == null ? void 0 : _pendingNavigationCon.abort();
1135
+
1136
+ for (let [, controller] of fetchControllers) {
1137
+ controller.abort();
1138
+ }
1139
+ } // Subscribe to state updates for the router
1140
+
1141
+
1142
+ function subscribe(fn) {
1143
+ if (subscriber) {
1144
+ throw new Error("A router only accepts one active subscriber");
1145
+ }
1146
+
1147
+ subscriber = fn;
1148
+ return () => {
1149
+ subscriber = null;
1150
+ };
1151
+ } // Update our state and notify the calling context of the change
1152
+
1153
+
1154
+ function updateState(newState) {
1155
+ state = _extends({}, state, newState);
1156
+ subscriber == null ? void 0 : subscriber(state);
1157
+ } // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
1158
+ // and setting state.[historyAction/location/matches] to the new route.
1159
+ // - HistoryAction and Location are required params
1160
+ // - Navigation will always be set to IDLE_NAVIGATION
1161
+ // - Can pass any other state in newState
1162
+
1163
+
1164
+ function completeNavigation(historyAction, location, newState) {
1165
+ var _location$state;
1166
+
1167
+ // Deduce if we're in a loading/actionReload state:
1168
+ // - We have committed actionData in the store
1169
+ // - The current navigation was a submission
1170
+ // - We're past the submitting state and into the loading state
1171
+ // - This should not be susceptible to false positives for
1172
+ // loading/submissionRedirect since there would not be actionData in the
1173
+ // state since the prior action would have returned a redirect response
1174
+ // and short circuited
1175
+ let isActionReload = state.actionData != null && state.navigation.formMethod != null && state.navigation.state === "loading";
1176
+ updateState(_extends({}, isActionReload ? {} : {
1177
+ actionData: null
1178
+ }, newState, {
1179
+ historyAction,
1180
+ location,
1181
+ initialized: true,
1182
+ navigation: IDLE_NAVIGATION,
1183
+ revalidation: "idle",
1184
+ // Always preserve any existing loaderData from re-used routes
1185
+ loaderData: mergeLoaderData(state, newState),
1186
+ // Don't restore on submission navigations
1187
+ restoreScrollPosition: state.navigation.formData ? false : getSavedScrollPosition(location, newState.matches || state.matches),
1188
+ // Always reset scroll unless explicitly told not to
1189
+ resetScrollPosition: ((_location$state = location.state) == null ? void 0 : _location$state.__resetScrollPosition) !== false
1190
+ }));
1191
+
1192
+ if (isUninterruptedRevalidation) ; else if (historyAction === Action.Pop) ; else if (historyAction === Action.Push) {
1193
+ init.history.push(location, location.state);
1194
+ } else if (historyAction === Action.Replace) {
1195
+ init.history.replace(location, location.state);
1196
+ } // Reset stateful navigation vars
1197
+
1198
+
1199
+ pendingAction = null;
1200
+ isUninterruptedRevalidation = false;
1201
+ isRevalidationRequired = false;
1202
+ } // Trigger a navigation event, which can either be a numerical POP or a PUSH
1203
+ // replace with an optional submission
1204
+
1205
+
1206
+ async function navigate(path, opts) {
1207
+ if (typeof path === "number") {
1208
+ init.history.go(path);
1209
+ return;
1210
+ }
1211
+
1212
+ let location = createLocation(state.location, path, opts == null ? void 0 : opts.state);
1213
+ let historyAction = opts != null && opts.replace ? Action.Replace : Action.Push;
1214
+
1215
+ if (isSubmissionNavigation(opts)) {
1216
+ return await startNavigation(historyAction, location, {
1217
+ submission: {
1218
+ formMethod: opts.formMethod || "get",
1219
+ formAction: createHref(location),
1220
+ formEncType: (opts == null ? void 0 : opts.formEncType) || "application/x-www-form-urlencoded",
1221
+ formData: opts.formData
1222
+ }
1223
+ });
1224
+ }
1225
+
1226
+ return await startNavigation(historyAction, location);
1227
+ } // Revalidate all current loaders. If a navigation is in progress or if this
1228
+ // is interrupted by a navigation, allow this to "succeed" by calling all
1229
+ // loaders during the next loader round
1230
+
1231
+
1232
+ function revalidate() {
1233
+ // Toggle isRevalidationRequired so the next data load will call all loaders,
1234
+ // and mark us in a revalidating state
1235
+ isRevalidationRequired = true;
1236
+ updateState({
1237
+ revalidation: "loading"
1238
+ }); // If we're currently submitting an action, we don't need to start a new
1239
+ // navigation, we'll just let the follow up loader execution call all loaders
1240
+
1241
+ if (state.navigation.state === "submitting" && state.navigation.formMethod !== "get") {
1242
+ return;
1243
+ } // If we're currently in an idle state, start a new navigation for the current
1244
+ // action/location and mark it as uninterrupted, which will skip the history
1245
+ // update in completeNavigation
1246
+
1247
+
1248
+ if (state.navigation.state === "idle") {
1249
+ startNavigation(state.historyAction, state.location, {
1250
+ startUninterruptedRevalidation: true
1251
+ });
1252
+ return;
1253
+ } // Otherwise, if we're currently in a loading state, just start a new
1254
+ // navigation to the navigation.location but do not trigger an uninterrupted
1255
+ // revalidation so that history correctly updates once the navigation completes
1256
+
1257
+
1258
+ startNavigation(pendingAction || state.historyAction, state.navigation.location, {
1259
+ overrideNavigation: state.navigation
1260
+ });
1261
+ } // Start a navigation to the given action/location. Can optionally provide a
1262
+ // overrideNavigation which will override the normalLoad in the case of a redirect
1263
+ // navigation
1264
+
1265
+
1266
+ async function startNavigation(historyAction, location, opts) {
1267
+ var _pendingNavigationCon2;
1268
+
1269
+ // Abort any in-progress navigations and start a new one
1270
+ (_pendingNavigationCon2 = pendingNavigationController) == null ? void 0 : _pendingNavigationCon2.abort();
1271
+ pendingAction = historyAction; // Unset any ongoing uninterrupted revalidations (unless told otherwise),
1272
+ // since we want this new navigation to update history normally
1273
+
1274
+ isUninterruptedRevalidation = (opts == null ? void 0 : opts.startUninterruptedRevalidation) === true; // Save the current scroll position every time we start a new navigation
1275
+
1276
+ saveScrollPosition(state.location, state.matches);
1277
+ let loadingNavigation = opts == null ? void 0 : opts.overrideNavigation;
1278
+ let matches = matchRoutes(dataRoutes, location); // Short circuit with a 404 on the root error boundary if we match nothing
1279
+
1280
+ if (!matches) {
1281
+ completeNavigation(historyAction, location, {
1282
+ matches: getNotFoundMatches(dataRoutes),
1283
+ errors: {
1284
+ [dataRoutes[0].id]: new Response(null, {
1285
+ status: 404
1286
+ })
1287
+ }
1288
+ });
1289
+ return;
1290
+ } // Short circuit if it's only a hash change
1291
+
1292
+
1293
+ if (isHashChangeOnly(state.location, location)) {
1294
+ completeNavigation(historyAction, location, {
1295
+ matches
1296
+ });
1297
+ return;
1298
+ } // Call action if we received an action submission
1299
+
1300
+
1301
+ let pendingActionData = null;
1302
+ let pendingActionError = null;
1303
+
1304
+ if (opts != null && opts.submission && isActionSubmission(opts.submission)) {
1305
+ let actionOutput = await handleAction(historyAction, location, opts.submission, matches);
1306
+
1307
+ if (actionOutput.shortCircuited) {
1308
+ return;
1309
+ }
1310
+
1311
+ pendingActionData = actionOutput.pendingActionData || null;
1312
+ pendingActionError = actionOutput.pendingActionError || null;
1313
+
1314
+ let navigation = _extends({
1315
+ state: "loading",
1316
+ location
1317
+ }, opts.submission);
1318
+
1319
+ loadingNavigation = navigation;
1320
+ } // Call loaders
1321
+
1322
+
1323
+ let {
1324
+ shortCircuited,
1325
+ loaderData,
1326
+ errors
1327
+ } = await handleLoaders(historyAction, location, opts == null ? void 0 : opts.submission, matches, loadingNavigation, pendingActionData, pendingActionError);
1328
+
1329
+ if (shortCircuited) {
1330
+ return;
1331
+ }
1332
+
1333
+ completeNavigation(historyAction, location, {
1334
+ matches,
1335
+ loaderData,
1336
+ errors
1337
+ });
1338
+ } // Call the action matched by the leaf route for this navigation and handle
1339
+ // redirects/errors
1340
+
1341
+
1342
+ async function handleAction(historyAction, location, submission, matches) {
1343
+ isRevalidationRequired = true;
1344
+
1345
+ if (matches[matches.length - 1].route.index && !hasNakedIndexQuery(location.search)) {
1346
+ // Note: OK to mutate this in-place since it's a scoped var inside
1347
+ // handleAction and mutation will not impact the startNavigation matches
1348
+ // variable that we use for revalidation
1349
+ matches = matches.slice(0, -1);
1350
+ } // Put us in a submitting state
1351
+
1352
+
1353
+ let navigation = _extends({
1354
+ state: "submitting",
1355
+ location
1356
+ }, submission);
1357
+
1358
+ updateState({
1359
+ navigation
1360
+ }); // Call our action and get the result
1361
+
1362
+ let result;
1363
+ let actionMatch = matches.slice(-1)[0];
1364
+
1365
+ if (!actionMatch.route.action) {
1366
+ if (process.env.NODE_ENV !== "production") {
1367
+ console.warn("You're trying to submit to a route that does not have an action. To " + "fix this, please add an `action` function to the route for " + ("[" + createHref(location) + "]"));
1368
+ }
1369
+
1370
+ result = {
1371
+ type: ResultType.error,
1372
+ error: new Response(null, {
1373
+ status: 405
1374
+ })
1375
+ };
1376
+ } else {
1377
+ // Create a controller for this data load
1378
+ let actionAbortController = new AbortController();
1379
+ pendingNavigationController = actionAbortController;
1380
+ result = await callLoaderOrAction(actionMatch, location, actionAbortController.signal, submission);
1381
+
1382
+ if (actionAbortController.signal.aborted) {
1383
+ return {
1384
+ shortCircuited: true
1385
+ };
1386
+ } // Clean up now that the loaders have completed. We do do not clean up if
1387
+ // we short circuited because pendingNavigationController will have already
1388
+ // been assigned to a new controller for the next navigation
1389
+
1390
+
1391
+ pendingNavigationController = null;
1392
+ } // If the action threw a redirect Response, start a new REPLACE navigation
1393
+
1394
+
1395
+ if (isRedirectResult(result)) {
1396
+ let redirectNavigation = _extends({
1397
+ state: "loading",
1398
+ location: createLocation(state.location, result.location)
1399
+ }, submission);
1400
+
1401
+ await startRedirectNavigation(result, redirectNavigation);
1402
+ return {
1403
+ shortCircuited: true
1404
+ };
1405
+ }
1406
+
1407
+ if (isErrorResult(result)) {
1408
+ // Store off the pending error - we use it to determine which loaders
1409
+ // to call and will commit it when we complete the navigation
1410
+ let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
1411
+ return {
1412
+ pendingActionError: {
1413
+ [boundaryMatch.route.id]: result.error
1414
+ }
1415
+ };
1416
+ }
1417
+
1418
+ return {
1419
+ pendingActionData: {
1420
+ [actionMatch.route.id]: result.data
1421
+ }
1422
+ };
1423
+ } // Call all applicable loaders for the given matches, handling redirects,
1424
+ // errors, etc.
1425
+
1426
+
1427
+ async function handleLoaders(historyAction, location, submission, matches, overrideNavigation, pendingActionData, pendingActionError) {
1428
+ // Figure out the right navigation we want to use for data loading
1429
+ let loadingNavigation;
1430
+
1431
+ if (overrideNavigation) {
1432
+ loadingNavigation = overrideNavigation;
1433
+ } else if ((submission == null ? void 0 : submission.formMethod) === "get") {
1434
+ let navigation = _extends({
1435
+ state: "submitting",
1436
+ location
1437
+ }, submission);
1438
+
1439
+ loadingNavigation = navigation;
1440
+ } else {
1441
+ let navigation = {
1442
+ state: "loading",
1443
+ location,
1444
+ formMethod: undefined,
1445
+ formAction: undefined,
1446
+ formEncType: undefined,
1447
+ formData: undefined
1448
+ };
1449
+ loadingNavigation = navigation;
1450
+ }
1451
+
1452
+ let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(state, matches, submission, location, isRevalidationRequired, pendingActionData, pendingActionError, fetchLoadMatches); // Short circuit if we have no loaders to run
1453
+
1454
+ if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
1455
+ completeNavigation(historyAction, location, {
1456
+ matches,
1457
+ // Commit pending action error if we're short circuiting
1458
+ errors: pendingActionError || null,
1459
+ actionData: pendingActionData || null
1460
+ });
1461
+ return {
1462
+ shortCircuited: true
1463
+ };
1464
+ } // If this is an uninterrupted revalidation, remain in our current idle state.
1465
+ // Otherwise, switch to our loading state and load data, preserving any
1466
+ // new action data or existing action data (in the case of a revalidation
1467
+ // interrupting an actionReload)
1468
+
1469
+
1470
+ if (!isUninterruptedRevalidation) {
1471
+ revalidatingFetchers.forEach(_ref2 => {
1472
+ var _state$fetchers$get;
1473
+
1474
+ let [key] = _ref2;
1475
+ let revalidatingFetcher = {
1476
+ state: "loading",
1477
+ data: (_state$fetchers$get = state.fetchers.get(key)) == null ? void 0 : _state$fetchers$get.data,
1478
+ formMethod: undefined,
1479
+ formAction: undefined,
1480
+ formEncType: undefined,
1481
+ formData: undefined
1482
+ };
1483
+ state.fetchers.set(key, revalidatingFetcher);
1484
+ });
1485
+ updateState(_extends({
1486
+ navigation: loadingNavigation,
1487
+ actionData: pendingActionData || state.actionData || null
1488
+ }, revalidatingFetchers.length > 0 ? {
1489
+ fetchers: new Map(state.fetchers)
1490
+ } : {}));
1491
+ } // Start the data load
1492
+
1493
+
1494
+ let abortController = new AbortController();
1495
+ pendingNavigationController = abortController;
1496
+ pendingNavigationLoadId = ++incrementingLoadId;
1497
+ revalidatingFetchers.forEach(_ref3 => {
1498
+ let [key] = _ref3;
1499
+ return fetchControllers.set(key, abortController);
1500
+ }); // Call all navigation loaders and revalidating fetcher loaders in parallel,
1501
+ // then slice off the results into separate arrays so we can handle them
1502
+ // accordingly
1503
+
1504
+ let results = await Promise.all([...matchesToLoad.map(m => callLoaderOrAction(m, location, abortController.signal)), ...revalidatingFetchers.map(_ref4 => {
1505
+ let [, href, match] = _ref4;
1506
+ return callLoaderOrAction(match, href, abortController.signal);
1507
+ })]);
1508
+ let navigationResults = results.slice(0, matchesToLoad.length);
1509
+ let fetcherResults = results.slice(matchesToLoad.length);
1510
+
1511
+ if (abortController.signal.aborted) {
1512
+ return {
1513
+ shortCircuited: true
1514
+ };
1515
+ } // Clean up now that the loaders have completed. We do do not clean up if
1516
+ // we short circuited because pendingNavigationController will have already
1517
+ // been assigned to a new controller for the next navigation
1518
+
1519
+
1520
+ pendingNavigationController = null;
1521
+ revalidatingFetchers.forEach(key => fetchControllers.delete(key)); // If any loaders returned a redirect Response, start a new REPLACE navigation
1522
+
1523
+ let redirect = findRedirect(results);
1524
+
1525
+ if (redirect) {
1526
+ let redirectNavigation = getLoaderRedirect(state, redirect);
1527
+ await startRedirectNavigation(redirect, redirectNavigation);
1528
+ return {
1529
+ shortCircuited: true
1530
+ };
1531
+ } // Process and commit output from loaders
1532
+
1533
+
1534
+ let {
1535
+ loaderData,
1536
+ errors
1537
+ } = processLoaderData(state, matches, matchesToLoad, navigationResults, pendingActionError, revalidatingFetchers, fetcherResults);
1538
+ markFetchRedirectsDone();
1539
+ let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
1540
+ return _extends({
1541
+ loaderData,
1542
+ errors
1543
+ }, didAbortFetchLoads || revalidatingFetchers.length > 0 ? {
1544
+ fetchers: new Map(state.fetchers)
1545
+ } : {});
1546
+ }
1547
+
1548
+ function getFetcher(key) {
1549
+ return state.fetchers.get(key) || IDLE_FETCHER;
1550
+ } // Trigger a fetcher load/submit for the given fetcher key
1551
+
1552
+
1553
+ function fetch(key, href, opts) {
1554
+ var _state$fetchers$get3;
1555
+
1556
+ if (typeof AbortController === "undefined") {
1557
+ 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.");
1558
+ }
1559
+
1560
+ let matches = matchRoutes(dataRoutes, href);
1561
+ !matches ? process.env.NODE_ENV !== "production" ? invariant(false, "No matches found for fetch url: " + href) : invariant(false) : void 0;
1562
+ if (fetchControllers.has(key)) abortFetcher(key);
1563
+ let match = matches[matches.length - 1].route.index && !hasNakedIndexQuery(parsePath(href).search || "") ? matches.slice(-2)[0] : matches.slice(-1)[0];
1564
+
1565
+ if (isSubmissionNavigation(opts)) {
1566
+ var _state$fetchers$get2;
1567
+
1568
+ let submission = {
1569
+ formMethod: opts.formMethod || "get",
1570
+ formAction: href,
1571
+ formEncType: opts.formEncType || "application/x-www-form-urlencoded",
1572
+ formData: opts.formData
1573
+ };
1574
+
1575
+ if (isActionSubmission(submission)) {
1576
+ handleFetcherAction(key, href, match, submission);
1577
+ return;
1578
+ }
1579
+
1580
+ let loadingFetcher = _extends({
1581
+ state: "submitting"
1582
+ }, submission, {
1583
+ data: ((_state$fetchers$get2 = state.fetchers.get(key)) == null ? void 0 : _state$fetchers$get2.data) || undefined
1584
+ });
1585
+
1586
+ handleFetcherLoader(key, href, match, loadingFetcher);
1587
+ return;
1588
+ }
1589
+
1590
+ let loadingFetcher = {
1591
+ state: "loading",
1592
+ formMethod: undefined,
1593
+ formAction: undefined,
1594
+ formEncType: undefined,
1595
+ formData: undefined,
1596
+ data: ((_state$fetchers$get3 = state.fetchers.get(key)) == null ? void 0 : _state$fetchers$get3.data) || undefined
1597
+ };
1598
+ handleFetcherLoader(key, href, match, loadingFetcher);
1599
+ } // Call the action for the matched fetcher.submit(), and then handle redirects,
1600
+ // errors, and revalidation
1601
+
1602
+
1603
+ async function handleFetcherAction(key, href, match, submission) {
1604
+ var _state$fetchers$get4;
1605
+
1606
+ isRevalidationRequired = true;
1607
+ fetchLoadMatches.delete(key); // Put this fetcher into it's submitting state
1608
+
1609
+ let fetcher = _extends({
1610
+ state: "submitting"
1611
+ }, submission, {
1612
+ data: ((_state$fetchers$get4 = state.fetchers.get(key)) == null ? void 0 : _state$fetchers$get4.data) || undefined
1613
+ });
1614
+
1615
+ state.fetchers.set(key, fetcher);
1616
+ updateState({
1617
+ fetchers: new Map(state.fetchers)
1618
+ }); // Call the action for the fetcher
1619
+
1620
+ let abortController = new AbortController();
1621
+ fetchControllers.set(key, abortController);
1622
+ let actionResult = await callLoaderOrAction(match, href, abortController.signal, submission);
1623
+
1624
+ if (abortController.signal.aborted) {
1625
+ return;
1626
+ }
1627
+
1628
+ if (isRedirectResult(actionResult)) {
1629
+ fetchRedirectIds.add(key);
1630
+
1631
+ let loadingFetcher = _extends({
1632
+ state: "loading"
1633
+ }, submission, {
1634
+ data: undefined
1635
+ });
1636
+
1637
+ state.fetchers.set(key, loadingFetcher);
1638
+ updateState({
1639
+ fetchers: new Map(state.fetchers)
1640
+ });
1641
+
1642
+ let redirectNavigation = _extends({
1643
+ state: "loading",
1644
+ location: createLocation(state.location, actionResult.location)
1645
+ }, submission);
1646
+
1647
+ await startRedirectNavigation(actionResult, redirectNavigation);
1648
+ return;
1649
+ } // Process any non-redirect errors thrown
1650
+
1651
+
1652
+ if (isErrorResult(actionResult)) {
1653
+ let boundaryMatch = findNearestBoundary(state.matches, match.route.id);
1654
+ state.fetchers.delete(key);
1655
+ updateState({
1656
+ fetchers: new Map(state.fetchers),
1657
+ errors: {
1658
+ [boundaryMatch.route.id]: actionResult.error
1659
+ }
1660
+ });
1661
+ return;
1662
+ } // Start the data load for current matches, or the next location if we're
1663
+ // in the middle of a navigation
1664
+
1665
+
1666
+ let nextLocation = state.navigation.location || state.location;
1667
+ let matches = state.navigation.state !== "idle" ? matchRoutes(dataRoutes, state.navigation.location) : state.matches;
1668
+ !matches ? process.env.NODE_ENV !== "production" ? invariant(false, "Didn't find any matches after fetcher action") : invariant(false) : void 0;
1669
+ let loadId = ++incrementingLoadId;
1670
+ fetchReloadIds.set(key, loadId);
1671
+
1672
+ let loadFetcher = _extends({
1673
+ state: "loading",
1674
+ data: actionResult.data
1675
+ }, submission);
1676
+
1677
+ state.fetchers.set(key, loadFetcher);
1678
+ let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(state, matches, submission, nextLocation, isRevalidationRequired, null, null, fetchLoadMatches); // Put all revalidating fetchers into the loading state, except for the
1679
+ // current fetcher which we want to keep in it's current loading state which
1680
+ // contains it's action submission info + action data
1681
+
1682
+ revalidatingFetchers.filter(_ref5 => {
1683
+ let [staleKey] = _ref5;
1684
+ return staleKey !== key;
1685
+ }).forEach(_ref6 => {
1686
+ var _state$fetchers$get5;
1687
+
1688
+ let [staleKey] = _ref6;
1689
+ let revalidatingFetcher = {
1690
+ state: "loading",
1691
+ data: (_state$fetchers$get5 = state.fetchers.get(key)) == null ? void 0 : _state$fetchers$get5.data,
1692
+ formMethod: undefined,
1693
+ formAction: undefined,
1694
+ formEncType: undefined,
1695
+ formData: undefined
1696
+ };
1697
+ state.fetchers.set(staleKey, revalidatingFetcher);
1698
+ fetchControllers.set(staleKey, abortController);
1699
+ });
1700
+ updateState({
1701
+ fetchers: new Map(state.fetchers)
1702
+ }); // Call all navigation loaders and revalidating fetcher loaders in parallel,
1703
+ // then slice off the results into separate arrays so we can handle them
1704
+ // accordingly
1705
+
1706
+ let results = await Promise.all([...matchesToLoad.map(m => callLoaderOrAction(m, nextLocation, abortController.signal)), ...revalidatingFetchers.map(_ref7 => {
1707
+ let [, href, match] = _ref7;
1708
+ return callLoaderOrAction(match, href, abortController.signal);
1709
+ })]);
1710
+ let loaderResults = results.slice(0, matchesToLoad.length);
1711
+ let fetcherResults = results.slice(matchesToLoad.length);
1712
+
1713
+ if (abortController.signal.aborted) {
1714
+ return;
1715
+ }
1716
+
1717
+ fetchReloadIds.delete(key);
1718
+ fetchControllers.delete(key);
1719
+ revalidatingFetchers.forEach(staleKey => fetchControllers.delete(staleKey));
1720
+ let loaderRedirect = findRedirect(loaderResults);
1721
+
1722
+ if (loaderRedirect) {
1723
+ let redirectNavigation = getLoaderRedirect(state, loaderRedirect);
1724
+ await startRedirectNavigation(loaderRedirect, redirectNavigation);
1725
+ return;
1726
+ } // Process and commit output from loaders
1727
+
1728
+
1729
+ let {
1730
+ loaderData,
1731
+ errors
1732
+ } = processLoaderData(state, state.matches, matchesToLoad, loaderResults, null, revalidatingFetchers, fetcherResults);
1733
+ let doneFetcher = {
1734
+ state: "idle",
1735
+ data: actionResult.data,
1736
+ formMethod: undefined,
1737
+ formAction: undefined,
1738
+ formEncType: undefined,
1739
+ formData: undefined
1740
+ };
1741
+ state.fetchers.set(key, doneFetcher);
1742
+ let didAbortFetchLoads = abortStaleFetchLoads(loadId); // If we are currently in a navigation loading state and this fetcher is
1743
+ // more recent than the navigation, we want the newer data so abort the
1744
+ // navigation and complete it with the fetcher data
1745
+
1746
+ if (state.navigation.state === "loading" && loadId > pendingNavigationLoadId) {
1747
+ var _pendingNavigationCon3;
1748
+
1749
+ !pendingAction ? process.env.NODE_ENV !== "production" ? invariant(false, "Expected pending action") : invariant(false) : void 0;
1750
+ (_pendingNavigationCon3 = pendingNavigationController) == null ? void 0 : _pendingNavigationCon3.abort();
1751
+ completeNavigation(pendingAction, state.navigation.location, {
1752
+ matches,
1753
+ loaderData,
1754
+ errors,
1755
+ fetchers: new Map(state.fetchers)
1756
+ });
1757
+ } else {
1758
+ // otherwise just update with the fetcher data
1759
+ updateState(_extends({
1760
+ errors,
1761
+ loaderData
1762
+ }, didAbortFetchLoads ? {
1763
+ fetchers: new Map(state.fetchers)
1764
+ } : {}));
1765
+ isRevalidationRequired = false;
1766
+ }
1767
+ } // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
1768
+
1769
+
1770
+ async function handleFetcherLoader(key, href, match, loadingFetcher) {
1771
+ // Put this fetcher into it's loading state
1772
+ state.fetchers.set(key, loadingFetcher);
1773
+ updateState({
1774
+ fetchers: new Map(state.fetchers)
1775
+ }); // Store off the match so we can call it's shouldRevalidate on subsequent
1776
+ // revalidations
1777
+
1778
+ fetchLoadMatches.set(key, [href, match]); // Call the loader for this fetcher route match
1779
+
1780
+ let abortController = new AbortController();
1781
+ fetchControllers.set(key, abortController);
1782
+ let result = await callLoaderOrAction(match, href, abortController.signal);
1783
+ if (abortController.signal.aborted) return;
1784
+ fetchControllers.delete(key); // If the loader threw a redirect Response, start a new REPLACE navigation
1785
+
1786
+ if (isRedirectResult(result)) {
1787
+ let redirectNavigation = getLoaderRedirect(state, result);
1788
+ await startRedirectNavigation(result, redirectNavigation);
1789
+ return;
1790
+ } // Process any non-redirect errors thrown
1791
+
1792
+
1793
+ if (isErrorResult(result)) {
1794
+ let boundaryMatch = findNearestBoundary(state.matches, match.route.id);
1795
+ state.fetchers.delete(key); // TODO: In remix, this would reset to IDLE_NAVIGATION if it was a catch -
1796
+ // do we need to behave any differently with our non-redirect errors?
1797
+ // What if it was a non-redirect Response?
1798
+
1799
+ updateState({
1800
+ fetchers: new Map(state.fetchers),
1801
+ errors: {
1802
+ [boundaryMatch.route.id]: result.error
1803
+ }
1804
+ });
1805
+ return;
1806
+ } // Put the fetcher back into an idle state
1807
+
1808
+
1809
+ let doneFetcher = {
1810
+ state: "idle",
1811
+ data: result.data,
1812
+ formMethod: undefined,
1813
+ formAction: undefined,
1814
+ formEncType: undefined,
1815
+ formData: undefined
1816
+ };
1817
+ state.fetchers.set(key, doneFetcher);
1818
+ updateState({
1819
+ fetchers: new Map(state.fetchers)
1820
+ });
1821
+ } // Utility function to handle redirects returned from an action or loader
1822
+
1823
+
1824
+ async function startRedirectNavigation(redirect, navigation) {
1825
+ if (redirect.revalidate) {
1826
+ isRevalidationRequired = true;
1827
+ }
1828
+
1829
+ !navigation.location ? process.env.NODE_ENV !== "production" ? invariant(false, "Expected a location on the redirect navigation") : invariant(false) : void 0;
1830
+ await startNavigation(Action.Replace, navigation.location, {
1831
+ overrideNavigation: navigation
1832
+ });
1833
+ }
1834
+
1835
+ function deleteFetcher(key) {
1836
+ if (fetchControllers.has(key)) abortFetcher(key);
1837
+ fetchLoadMatches.delete(key);
1838
+ fetchReloadIds.delete(key);
1839
+ fetchRedirectIds.delete(key);
1840
+ state.fetchers.delete(key);
1841
+ }
1842
+
1843
+ function abortFetcher(key) {
1844
+ let controller = fetchControllers.get(key);
1845
+ !controller ? process.env.NODE_ENV !== "production" ? invariant(false, "Expected fetch controller: " + key) : invariant(false) : void 0;
1846
+ controller.abort();
1847
+ fetchControllers.delete(key);
1848
+ }
1849
+
1850
+ function markFetchersDone(keys) {
1851
+ for (let key of keys) {
1852
+ let fetcher = getFetcher(key);
1853
+ let doneFetcher = {
1854
+ state: "idle",
1855
+ data: fetcher.data,
1856
+ formMethod: undefined,
1857
+ formAction: undefined,
1858
+ formEncType: undefined,
1859
+ formData: undefined
1860
+ };
1861
+ state.fetchers.set(key, doneFetcher);
1862
+ }
1863
+ }
1864
+
1865
+ function markFetchRedirectsDone() {
1866
+ let doneKeys = [];
1867
+
1868
+ for (let key of fetchRedirectIds) {
1869
+ let fetcher = state.fetchers.get(key);
1870
+ !fetcher ? process.env.NODE_ENV !== "production" ? invariant(false, "Expected fetcher: " + key) : invariant(false) : void 0;
1871
+
1872
+ if (fetcher.state === "loading") {
1873
+ fetchRedirectIds.delete(key);
1874
+ doneKeys.push(key);
1875
+ }
1876
+ }
1877
+
1878
+ markFetchersDone(doneKeys);
1879
+ }
1880
+
1881
+ function abortStaleFetchLoads(landedId) {
1882
+ let yeetedKeys = [];
1883
+
1884
+ for (let [key, id] of fetchReloadIds) {
1885
+ if (id < landedId) {
1886
+ let fetcher = state.fetchers.get(key);
1887
+ !fetcher ? process.env.NODE_ENV !== "production" ? invariant(false, "Expected fetcher: " + key) : invariant(false) : void 0;
1888
+
1889
+ if (fetcher.state === "loading") {
1890
+ abortFetcher(key);
1891
+ fetchReloadIds.delete(key);
1892
+ yeetedKeys.push(key);
1893
+ }
1894
+ }
1895
+ }
1896
+
1897
+ markFetchersDone(yeetedKeys);
1898
+ return yeetedKeys.length > 0;
1899
+ } // Opt in to capturing and reporting scroll positions during navigations,
1900
+ // used by the <ScrollRestoration> component
1901
+
1902
+
1903
+ function enableScrollRestoration(positions, getPosition, getKey) {
1904
+ savedScrollPositions = positions;
1905
+ getScrollPosition = getPosition;
1906
+
1907
+ getScrollRestorationKey = getKey || (location => location.key); // Perform initial hydration scroll restoration, since we miss the boat on
1908
+ // the initial updateState() because we've not yet rendered <ScrollRestoration/>
1909
+ // and therefore have no savedScrollPositions available
1910
+
1911
+
1912
+ if (!initialScrollRestored && state.navigation === IDLE_NAVIGATION) {
1913
+ initialScrollRestored = true;
1914
+ let y = getSavedScrollPosition(state.location, state.matches);
1915
+
1916
+ if (y != null) {
1917
+ updateState({
1918
+ restoreScrollPosition: y
1919
+ });
1920
+ }
1921
+ }
1922
+
1923
+ return () => {
1924
+ savedScrollPositions = null;
1925
+ getScrollPosition = null;
1926
+ getScrollRestorationKey = null;
1927
+ };
1928
+ }
1929
+
1930
+ function saveScrollPosition(location, matches) {
1931
+ if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
1932
+ let key = getScrollRestorationKey(location, matches) || location.key;
1933
+ savedScrollPositions[key] = getScrollPosition();
1934
+ }
1935
+ }
1936
+
1937
+ function getSavedScrollPosition(location, matches) {
1938
+ if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
1939
+ let key = getScrollRestorationKey(location, matches) || location.key;
1940
+ let y = savedScrollPositions[key];
1941
+
1942
+ if (typeof y === "number") {
1943
+ return y;
1944
+ }
1945
+ }
1946
+
1947
+ return null;
1948
+ }
1949
+
1950
+ router = {
1951
+ get state() {
1952
+ return state;
1953
+ },
1954
+
1955
+ initialize,
1956
+ subscribe,
1957
+ enableScrollRestoration,
1958
+ navigate,
1959
+ fetch,
1960
+ revalidate,
1961
+ createHref,
1962
+ getFetcher,
1963
+ deleteFetcher,
1964
+ dispose,
1965
+ _internalFetchControllers: fetchControllers
1966
+ };
1967
+ return router;
1968
+ } //#endregion
1969
+ ////////////////////////////////////////////////////////////////////////////////
1970
+ //#region Helpers
1971
+ ////////////////////////////////////////////////////////////////////////////////
1972
+
1973
+ function convertRoutesToDataRoutes(routes, parentPath, allIds) {
1974
+ if (parentPath === void 0) {
1975
+ parentPath = [];
1976
+ }
1977
+
1978
+ if (allIds === void 0) {
1979
+ allIds = new Set();
1980
+ }
1981
+
1982
+ return routes.map((route, index) => {
1983
+ let treePath = [...parentPath, index];
1984
+ let id = typeof route.id === "string" ? route.id : treePath.join("-");
1985
+ !!allIds.has(id) ? process.env.NODE_ENV !== "production" ? invariant(false, "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages") : invariant(false) : void 0;
1986
+ allIds.add(id);
1987
+
1988
+ let dataRoute = _extends({}, route, {
1989
+ id,
1990
+ children: route.children ? convertRoutesToDataRoutes(route.children, treePath, allIds) : undefined
1991
+ });
1992
+
1993
+ return dataRoute;
1994
+ });
1995
+ }
1996
+
1997
+ function getLoaderRedirect(state, redirect) {
1998
+ let {
1999
+ formMethod,
2000
+ formAction,
2001
+ formEncType,
2002
+ formData
2003
+ } = state.navigation;
2004
+ let navigation = {
2005
+ state: "loading",
2006
+ location: createLocation(state.location, redirect.location),
2007
+ formMethod: formMethod || undefined,
2008
+ formAction: formAction || undefined,
2009
+ formEncType: formEncType || undefined,
2010
+ formData: formData || undefined
2011
+ };
2012
+ return navigation;
2013
+ }
2014
+
2015
+ function getMatchesToLoad(state, matches, submission, location, isRevalidationRequired, pendingActionData, pendingActionError, revalidatingFetcherMatches) {
2016
+ // Determine which routes to run loaders for, filter out all routes below
2017
+ // any caught action error as they aren't going to render so we don't
2018
+ // need to load them
2019
+ let deepestRenderableMatchIndex = pendingActionError ? matches.findIndex(m => m.route.id === Object.keys(pendingActionError)[0]) : matches.length;
2020
+ let actionResult = pendingActionError ? Object.values(pendingActionError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : null; // Pick navigation matches that are net-new or qualify for revalidation
2021
+
2022
+ let navigationMatches = matches.filter((match, index) => {
2023
+ if (!match.route.loader || index >= deepestRenderableMatchIndex) {
2024
+ return false;
2025
+ }
2026
+
2027
+ return isNewLoader(state.loaderData, state.matches[index], match) || shouldRevalidateLoader(state.location, state.matches[index], submission, location, match, isRevalidationRequired, actionResult);
2028
+ }); // If revalidation is required, pick fetchers that qualify
2029
+
2030
+ let revalidatingFetchers = [];
2031
+
2032
+ if (isRevalidationRequired) {
2033
+ for (let entry of revalidatingFetcherMatches.entries()) {
2034
+ let [key, [href, match]] = entry;
2035
+ let shouldRevalidate = shouldRevalidateLoader(href, match, submission, href, match, isRevalidationRequired, actionResult);
2036
+
2037
+ if (shouldRevalidate) {
2038
+ revalidatingFetchers.push([key, href, match]);
2039
+ }
2040
+ }
2041
+ }
2042
+
2043
+ return [navigationMatches, revalidatingFetchers];
2044
+ }
2045
+
2046
+ function isNewLoader(currentLoaderData, currentMatch, match) {
2047
+ let isNew = // [a] -> [a, b]
2048
+ !currentMatch || // [a, b] -> [a, c]
2049
+ match.route.id !== currentMatch.route.id; // Handle the case that we don't have data for a re-used route, potentially
2050
+ // from a prior error
2051
+
2052
+ let isMissingData = currentLoaderData[match.route.id] === undefined; // Always load if this is a net-new route or we don't yet have data
2053
+
2054
+ return isNew || isMissingData;
2055
+ }
2056
+
2057
+ function shouldRevalidateLoader(currentLocation, currentMatch, submission, location, match, isRevalidationRequired, actionResult) {
2058
+ var _currentMatch$route$p;
2059
+
2060
+ let currentUrl = createURL(currentLocation);
2061
+ let currentParams = currentMatch.params;
2062
+ let nextUrl = createURL(location);
2063
+ let nextParams = match.params; // This is the default implementation as to when we revalidate. If the route
2064
+ // provides it's own implementation, then we give them full control but
2065
+ // provide this value so they can leverage it if needed after they check
2066
+ // their own specific use cases
2067
+ // Note that fetchers always provide the same current/next locations so the
2068
+ // URL-based checks here don't apply to fetcher shouldRevalidate calls
2069
+
2070
+ let defaultShouldRevalidate = // param change for this match, /users/123 -> /users/456
2071
+ currentMatch.pathname !== match.pathname || // splat param changed, which is not present in match.path
2072
+ // e.g. /files/images/avatar.jpg -> files/finances.xls
2073
+ ((_currentMatch$route$p = currentMatch.route.path) == null ? void 0 : _currentMatch$route$p.endsWith("*")) && currentMatch.params["*"] !== match.params["*"] || // Clicked the same link, resubmitted a GET form
2074
+ currentUrl.toString() === nextUrl.toString() || // Search params affect all loaders
2075
+ currentUrl.search !== nextUrl.search || // Forced revalidation due to submission, useRevalidate, or X-Remix-Revalidate
2076
+ isRevalidationRequired;
2077
+
2078
+ if (match.route.shouldRevalidate) {
2079
+ return match.route.shouldRevalidate(_extends({
2080
+ currentUrl,
2081
+ currentParams,
2082
+ nextUrl,
2083
+ nextParams
2084
+ }, submission, {
2085
+ actionResult,
2086
+ defaultShouldRevalidate
2087
+ }));
2088
+ }
2089
+
2090
+ return defaultShouldRevalidate;
2091
+ }
2092
+
2093
+ async function callLoaderOrAction(match, location, signal, actionSubmission) {
2094
+ let resultType = ResultType.data;
2095
+ let result;
2096
+
2097
+ try {
2098
+ let type = actionSubmission ? "action" : "loader";
2099
+ let handler = match.route[type];
2100
+ !handler ? process.env.NODE_ENV !== "production" ? invariant(false, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route") : invariant(false) : void 0;
2101
+ result = await handler({
2102
+ params: match.params,
2103
+ request: createRequest(location, actionSubmission),
2104
+ signal
2105
+ });
2106
+ } catch (e) {
2107
+ resultType = ResultType.error;
2108
+ result = e;
2109
+ }
2110
+
2111
+ if (result instanceof Response) {
2112
+ var _result$headers$get;
2113
+
2114
+ // Process redirects
2115
+ let status = result.status;
2116
+ let location = result.headers.get("Location");
2117
+
2118
+ if (status >= 300 && status <= 399 && location != null) {
2119
+ return {
2120
+ type: ResultType.redirect,
2121
+ status,
2122
+ location,
2123
+ revalidate: result.headers.get("X-Remix-Revalidate") !== null
2124
+ };
2125
+ }
2126
+
2127
+ let data;
2128
+
2129
+ if ((_result$headers$get = result.headers.get("Content-Type")) != null && _result$headers$get.startsWith("application/json")) {
2130
+ data = await result.json();
2131
+ } else {
2132
+ data = await result.text();
2133
+ }
2134
+
2135
+ if (resultType === ResultType.error) {
2136
+ return {
2137
+ type: resultType,
2138
+ error: new ErrorResponse(status, result.statusText, data)
2139
+ };
2140
+ }
2141
+
2142
+ return {
2143
+ type: resultType,
2144
+ data
2145
+ };
2146
+ }
2147
+
2148
+ if (resultType === ResultType.error) {
2149
+ return {
2150
+ type: resultType,
2151
+ error: result
2152
+ };
2153
+ }
2154
+
2155
+ return {
2156
+ type: resultType,
2157
+ data: result
2158
+ };
2159
+ }
2160
+
2161
+ function createRequest(location, actionSubmission) {
2162
+ let init = undefined;
2163
+
2164
+ if (actionSubmission) {
2165
+ let {
2166
+ formMethod,
2167
+ formEncType,
2168
+ formData
2169
+ } = actionSubmission;
2170
+ let body = formData; // If we're submitting application/x-www-form-urlencoded, then body should
2171
+ // be of type URLSearchParams
2172
+
2173
+ if (formEncType === "application/x-www-form-urlencoded") {
2174
+ body = new URLSearchParams();
2175
+
2176
+ for (let [key, value] of formData.entries()) {
2177
+ !(typeof value === "string") ? process.env.NODE_ENV !== "production" ? invariant(false, 'File inputs are not supported with encType "application/x-www-form-urlencoded", ' + 'please use "multipart/form-data" instead.') : invariant(false) : void 0;
2178
+ body.append(key, value);
2179
+ }
2180
+ }
2181
+
2182
+ init = {
2183
+ method: formMethod.toUpperCase(),
2184
+ headers: {
2185
+ "Content-Type": formEncType
2186
+ },
2187
+ body
2188
+ };
2189
+ }
2190
+
2191
+ let url = createURL(location).toString();
2192
+ return new Request(url, init);
2193
+ }
2194
+
2195
+ function processLoaderData(state, matches, matchesToLoad, results, pendingActionError, revalidatingFetchers, fetcherResults) {
2196
+ // Fill in loaderData/errors from our loaders
2197
+ let loaderData = {};
2198
+ let errors = null; // Process loader results into state.loaderData/state.errors
2199
+
2200
+ results.forEach((result, index) => {
2201
+ let id = matchesToLoad[index].route.id;
2202
+ !!isRedirectResult(result) ? process.env.NODE_ENV !== "production" ? invariant(false, "Cannot handle redirect results in processLoaderData") : invariant(false) : void 0;
2203
+
2204
+ if (isErrorResult(result)) {
2205
+ // Look upwards from the matched route for the closest ancestor
2206
+ // errorElement, defaulting to the root match
2207
+ let boundaryMatch = findNearestBoundary(matches, id);
2208
+ let error = result.error; // If we have a pending action error, we report it at the highest-route
2209
+ // that throws a loader error, and then clear it out to indicate that
2210
+ // it was consumed
2211
+
2212
+ if (pendingActionError) {
2213
+ error = Object.values(pendingActionError)[0];
2214
+ pendingActionError = null;
2215
+ }
2216
+
2217
+ errors = Object.assign(errors || {}, {
2218
+ [boundaryMatch.route.id]: error
2219
+ });
2220
+ } else {
2221
+ loaderData[id] = result.data;
2222
+ }
2223
+ }); // If we didn't consume the pending action error (i.e., all loaders
2224
+ // resolved), then consume it here
2225
+
2226
+ if (pendingActionError) {
2227
+ errors = pendingActionError;
2228
+ } // Process results from our revalidating fetchers
2229
+
2230
+
2231
+ revalidatingFetchers.forEach((_ref8, index) => {
2232
+ let [key, href, match] = _ref8;
2233
+ let result = fetcherResults[index]; // Process fetcher non-redirect errors
2234
+
2235
+ if (isErrorResult(result)) {
2236
+ var _errors;
2237
+
2238
+ let boundaryMatch = findNearestBoundary(state.matches, match.route.id);
2239
+
2240
+ if (!((_errors = errors) != null && _errors[boundaryMatch.route.id])) {
2241
+ errors = _extends({}, errors, {
2242
+ [boundaryMatch.route.id]: result.error
2243
+ });
2244
+ }
2245
+
2246
+ state.fetchers.delete(key);
2247
+ } else if (isRedirectResult(result)) {
2248
+ // Should never get here, redirects should get processed above, but we
2249
+ // keep this to type narrow to a success result in the else
2250
+ process.env.NODE_ENV !== "production" ? invariant(false, "Unhandled fetcher revalidation redirect") : invariant(false) ;
2251
+ } else {
2252
+ let doneFetcher = {
2253
+ state: "idle",
2254
+ data: result.data,
2255
+ formMethod: undefined,
2256
+ formAction: undefined,
2257
+ formEncType: undefined,
2258
+ formData: undefined
2259
+ };
2260
+ state.fetchers.set(key, doneFetcher);
2261
+ }
2262
+ });
2263
+ return {
2264
+ loaderData,
2265
+ errors
2266
+ };
2267
+ }
2268
+
2269
+ function mergeLoaderData(state, newState) {
2270
+ // Identify active routes that have current loaderData and didn't receive new
2271
+ // loaderData
2272
+ let reusedRoutesWithData = (newState.matches || state.matches).filter(match => {
2273
+ var _newState$loaderData;
2274
+
2275
+ return state.loaderData[match.route.id] !== undefined && ((_newState$loaderData = newState.loaderData) == null ? void 0 : _newState$loaderData[match.route.id]) === undefined;
2276
+ });
2277
+ return _extends({}, newState.loaderData, reusedRoutesWithData.reduce((acc, match) => Object.assign(acc, {
2278
+ [match.route.id]: state.loaderData[match.route.id]
2279
+ }), {}));
2280
+ } // Find the nearest error boundary, looking upwards from the matched route
2281
+ // for the closest ancestor errorElement, defaulting to the root match
2282
+
2283
+
2284
+ function findNearestBoundary(matches, routeId) {
2285
+ return matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1).reverse().find(m => m.route.errorElement) || matches[0];
2286
+ }
2287
+
2288
+ function getNotFoundMatches(routes) {
2289
+ return [{
2290
+ params: {},
2291
+ pathname: "",
2292
+ pathnameBase: "",
2293
+ route: routes[0]
2294
+ }];
2295
+ } // Find any returned redirect errors, starting from the lowest match
2296
+
2297
+
2298
+ function findRedirect(results) {
2299
+ for (let i = results.length - 1; i >= 0; i--) {
2300
+ let result = results[i];
2301
+
2302
+ if (isRedirectResult(result)) {
2303
+ return result;
2304
+ }
2305
+ }
2306
+ } // Create an href to represent a "server" URL without the hash
2307
+
2308
+
2309
+ function createHref(location) {
2310
+ return location.pathname + location.search;
2311
+ }
2312
+
2313
+ function isHashChangeOnly(a, b) {
2314
+ return a.pathname === b.pathname && a.search === b.search && a.hash !== b.hash;
2315
+ }
2316
+
2317
+ function isErrorResult(result) {
2318
+ return result.type === ResultType.error;
2319
+ }
2320
+
2321
+ function isRedirectResult(result) {
2322
+ return (result == null ? void 0 : result.type) === ResultType.redirect;
2323
+ }
2324
+
2325
+ function isSubmissionNavigation(opts) {
2326
+ return opts != null && "formData" in opts && opts.formData != null;
2327
+ }
2328
+
2329
+ function isActionSubmission(submission) {
2330
+ return submission && submission.formMethod !== "get";
2331
+ }
2332
+
2333
+ function hasNakedIndexQuery(search) {
2334
+ return new URLSearchParams(search).getAll("index").some(v => v === "");
2335
+ }
2336
+
2337
+ function createURL(location) {
2338
+ let base = typeof window !== "undefined" && typeof window.location !== "undefined" ? window.location.origin : "unknown://unknown";
2339
+ let href = typeof location === "string" ? location : createHref(location);
2340
+ return new URL(href, base);
2341
+ } //#endregion
2342
+
2343
+ const _excluded = ["initialEntries", "initialIndex"],
2344
+ _excluded2 = ["window"],
2345
+ _excluded3 = ["window"];
2346
+
2347
+ function createMemoryRouter(_ref) {
2348
+ let {
2349
+ initialEntries,
2350
+ initialIndex
2351
+ } = _ref,
2352
+ routerInit = _objectWithoutPropertiesLoose(_ref, _excluded);
2353
+
2354
+ let history = createMemoryHistory({
2355
+ initialEntries,
2356
+ initialIndex
2357
+ });
2358
+ return createRouter(_extends({
2359
+ history
2360
+ }, routerInit));
2361
+ }
2362
+
2363
+ function createBrowserRouter(_ref2) {
2364
+ let {
2365
+ window
2366
+ } = _ref2,
2367
+ routerInit = _objectWithoutPropertiesLoose(_ref2, _excluded2);
2368
+
2369
+ let history = createBrowserHistory({
2370
+ window
2371
+ });
2372
+ return createRouter(_extends({
2373
+ history
2374
+ }, routerInit));
2375
+ }
2376
+
2377
+ function createHashRouter(_ref3) {
2378
+ let {
2379
+ window
2380
+ } = _ref3,
2381
+ routerInit = _objectWithoutPropertiesLoose(_ref3, _excluded3);
2382
+
2383
+ let history = createHashHistory({
2384
+ window
2385
+ });
2386
+ return createRouter(_extends({
2387
+ history
2388
+ }, routerInit));
2389
+ }
2390
+
2391
+ export { Action, IDLE_FETCHER, IDLE_NAVIGATION, createBrowserHistory, createBrowserRouter, createHashHistory, createHashRouter, createMemoryHistory, createMemoryRouter, createPath, createRouter, generatePath, getToPathname, invariant, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, resolvePath, resolveTo, stripBasename, warning$1 as warning };
2392
+ //# sourceMappingURL=index.js.map