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

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 2 weeks ago

Fix frontend appearance

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