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