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