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

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

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