source: node_modules/@remix-run/router/dist/router.umd.js@ d24f17c

main
Last change on this file since d24f17c was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

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