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

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

Update repo after prototype presentation

  • Property mode set to 100644
File size: 189.0 KB
RevLine 
[d565449]1/**
2 * @remix-run/router v1.19.0
3 *
4 * Copyright (c) Remix Software Inc.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE.md file in the root directory of this source tree.
8 *
9 * @license MIT
10 */
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/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/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/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}
889/**
890 * Returns a resolved path object relative to the given pathname.
891 *
892 * @see https://reactrouter.com/utils/resolve-path
893 */
894function resolvePath(to, fromPathname) {
895 if (fromPathname === void 0) {
896 fromPathname = "/";
897 }
898 let {
899 pathname: toPathname,
900 search = "",
901 hash = ""
902 } = typeof to === "string" ? parsePath(to) : to;
903 let pathname = toPathname ? toPathname.startsWith("/") ? toPathname : resolvePathname(toPathname, fromPathname) : fromPathname;
904 return {
905 pathname,
906 search: normalizeSearch(search),
907 hash: normalizeHash(hash)
908 };
909}
910function resolvePathname(relativePath, fromPathname) {
911 let segments = fromPathname.replace(/\/+$/, "").split("/");
912 let relativeSegments = relativePath.split("/");
913 relativeSegments.forEach(segment => {
914 if (segment === "..") {
915 // Keep the root "" segment so the pathname starts at /
916 if (segments.length > 1) segments.pop();
917 } else if (segment !== ".") {
918 segments.push(segment);
919 }
920 });
921 return segments.length > 1 ? segments.join("/") : "/";
922}
923function getInvalidPathError(char, field, dest, path) {
924 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.";
925}
926/**
927 * @private
928 *
929 * When processing relative navigation we want to ignore ancestor routes that
930 * do not contribute to the path, such that index/pathless layout routes don't
931 * interfere.
932 *
933 * For example, when moving a route element into an index route and/or a
934 * pathless layout route, relative link behavior contained within should stay
935 * the same. Both of the following examples should link back to the root:
936 *
937 * <Route path="/">
938 * <Route path="accounts" element={<Link to=".."}>
939 * </Route>
940 *
941 * <Route path="/">
942 * <Route path="accounts">
943 * <Route element={<AccountsLayout />}> // <-- Does not contribute
944 * <Route index element={<Link to=".."} /> // <-- Does not contribute
945 * </Route
946 * </Route>
947 * </Route>
948 */
949function getPathContributingMatches(matches) {
950 return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
951}
952// Return the array of pathnames for the current route matches - used to
953// generate the routePathnames input for resolveTo()
954function getResolveToMatches(matches, v7_relativeSplatPath) {
955 let pathMatches = getPathContributingMatches(matches);
956 // When v7_relativeSplatPath is enabled, use the full pathname for the leaf
957 // match so we include splat values for "." links. See:
958 // https://github.com/remix-run/react-router/issues/11052#issuecomment-1836589329
959 if (v7_relativeSplatPath) {
960 return pathMatches.map((match, idx) => idx === pathMatches.length - 1 ? match.pathname : match.pathnameBase);
961 }
962 return pathMatches.map(match => match.pathnameBase);
963}
964/**
965 * @private
966 */
967function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
968 if (isPathRelative === void 0) {
969 isPathRelative = false;
970 }
971 let to;
972 if (typeof toArg === "string") {
973 to = parsePath(toArg);
974 } else {
975 to = _extends({}, toArg);
976 invariant(!to.pathname || !to.pathname.includes("?"), getInvalidPathError("?", "pathname", "search", to));
977 invariant(!to.pathname || !to.pathname.includes("#"), getInvalidPathError("#", "pathname", "hash", to));
978 invariant(!to.search || !to.search.includes("#"), getInvalidPathError("#", "search", "hash", to));
979 }
980 let isEmptyPath = toArg === "" || to.pathname === "";
981 let toPathname = isEmptyPath ? "/" : to.pathname;
982 let from;
983 // Routing is relative to the current pathname if explicitly requested.
984 //
985 // If a pathname is explicitly provided in `to`, it should be relative to the
986 // route context. This is explained in `Note on `<Link to>` values` in our
987 // migration guide from v5 as a means of disambiguation between `to` values
988 // that begin with `/` and those that do not. However, this is problematic for
989 // `to` values that do not provide a pathname. `to` can simply be a search or
990 // hash string, in which case we should assume that the navigation is relative
991 // to the current location's pathname and *not* the route pathname.
992 if (toPathname == null) {
993 from = locationPathname;
994 } else {
995 let routePathnameIndex = routePathnames.length - 1;
996 // With relative="route" (the default), each leading .. segment means
997 // "go up one route" instead of "go up one URL segment". This is a key
998 // difference from how <a href> works and a major reason we call this a
999 // "to" value instead of a "href".
1000 if (!isPathRelative && toPathname.startsWith("..")) {
1001 let toSegments = toPathname.split("/");
1002 while (toSegments[0] === "..") {
1003 toSegments.shift();
1004 routePathnameIndex -= 1;
1005 }
1006 to.pathname = toSegments.join("/");
1007 }
1008 from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
1009 }
1010 let path = resolvePath(to, from);
1011 // Ensure the pathname has a trailing slash if the original "to" had one
1012 let hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/");
1013 // Or if this was a link to the current path which has a trailing slash
1014 let hasCurrentTrailingSlash = (isEmptyPath || toPathname === ".") && locationPathname.endsWith("/");
1015 if (!path.pathname.endsWith("/") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) {
1016 path.pathname += "/";
1017 }
1018 return path;
1019}
1020/**
1021 * @private
1022 */
1023function getToPathname(to) {
1024 // Empty strings should be treated the same as / paths
1025 return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
1026}
1027/**
1028 * @private
1029 */
1030const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
1031/**
1032 * @private
1033 */
1034const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
1035/**
1036 * @private
1037 */
1038const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
1039/**
1040 * @private
1041 */
1042const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
1043/**
1044 * This is a shortcut for creating `application/json` responses. Converts `data`
1045 * to JSON and sets the `Content-Type` header.
1046 */
1047const json = function json(data, init) {
1048 if (init === void 0) {
1049 init = {};
1050 }
1051 let responseInit = typeof init === "number" ? {
1052 status: init
1053 } : init;
1054 let headers = new Headers(responseInit.headers);
1055 if (!headers.has("Content-Type")) {
1056 headers.set("Content-Type", "application/json; charset=utf-8");
1057 }
1058 return new Response(JSON.stringify(data), _extends({}, responseInit, {
1059 headers
1060 }));
1061};
1062class DataWithResponseInit {
1063 constructor(data, init) {
1064 this.type = "DataWithResponseInit";
1065 this.data = data;
1066 this.init = init || null;
1067 }
1068}
1069/**
1070 * Create "responses" that contain `status`/`headers` without forcing
1071 * serialization into an actual `Response` - used by Remix single fetch
1072 */
1073function data(data, init) {
1074 return new DataWithResponseInit(data, typeof init === "number" ? {
1075 status: init
1076 } : init);
1077}
1078class AbortedDeferredError extends Error {}
1079class DeferredData {
1080 constructor(data, responseInit) {
1081 this.pendingKeysSet = new Set();
1082 this.subscribers = new Set();
1083 this.deferredKeys = [];
1084 invariant(data && typeof data === "object" && !Array.isArray(data), "defer() only accepts plain objects");
1085 // Set up an AbortController + Promise we can race against to exit early
1086 // cancellation
1087 let reject;
1088 this.abortPromise = new Promise((_, r) => reject = r);
1089 this.controller = new AbortController();
1090 let onAbort = () => reject(new AbortedDeferredError("Deferred data aborted"));
1091 this.unlistenAbortSignal = () => this.controller.signal.removeEventListener("abort", onAbort);
1092 this.controller.signal.addEventListener("abort", onAbort);
1093 this.data = Object.entries(data).reduce((acc, _ref2) => {
1094 let [key, value] = _ref2;
1095 return Object.assign(acc, {
1096 [key]: this.trackPromise(key, value)
1097 });
1098 }, {});
1099 if (this.done) {
1100 // All incoming values were resolved
1101 this.unlistenAbortSignal();
1102 }
1103 this.init = responseInit;
1104 }
1105 trackPromise(key, value) {
1106 if (!(value instanceof Promise)) {
1107 return value;
1108 }
1109 this.deferredKeys.push(key);
1110 this.pendingKeysSet.add(key);
1111 // We store a little wrapper promise that will be extended with
1112 // _data/_error props upon resolve/reject
1113 let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, undefined, data), error => this.onSettle(promise, key, error));
1114 // Register rejection listeners to avoid uncaught promise rejections on
1115 // errors or aborted deferred values
1116 promise.catch(() => {});
1117 Object.defineProperty(promise, "_tracked", {
1118 get: () => true
1119 });
1120 return promise;
1121 }
1122 onSettle(promise, key, error, data) {
1123 if (this.controller.signal.aborted && error instanceof AbortedDeferredError) {
1124 this.unlistenAbortSignal();
1125 Object.defineProperty(promise, "_error", {
1126 get: () => error
1127 });
1128 return Promise.reject(error);
1129 }
1130 this.pendingKeysSet.delete(key);
1131 if (this.done) {
1132 // Nothing left to abort!
1133 this.unlistenAbortSignal();
1134 }
1135 // If the promise was resolved/rejected with undefined, we'll throw an error as you
1136 // should always resolve with a value or null
1137 if (error === undefined && data === undefined) {
1138 let undefinedError = new Error("Deferred data for key \"" + key + "\" resolved/rejected with `undefined`, " + "you must resolve/reject with a value or `null`.");
1139 Object.defineProperty(promise, "_error", {
1140 get: () => undefinedError
1141 });
1142 this.emit(false, key);
1143 return Promise.reject(undefinedError);
1144 }
1145 if (data === undefined) {
1146 Object.defineProperty(promise, "_error", {
1147 get: () => error
1148 });
1149 this.emit(false, key);
1150 return Promise.reject(error);
1151 }
1152 Object.defineProperty(promise, "_data", {
1153 get: () => data
1154 });
1155 this.emit(false, key);
1156 return data;
1157 }
1158 emit(aborted, settledKey) {
1159 this.subscribers.forEach(subscriber => subscriber(aborted, settledKey));
1160 }
1161 subscribe(fn) {
1162 this.subscribers.add(fn);
1163 return () => this.subscribers.delete(fn);
1164 }
1165 cancel() {
1166 this.controller.abort();
1167 this.pendingKeysSet.forEach((v, k) => this.pendingKeysSet.delete(k));
1168 this.emit(true);
1169 }
1170 async resolveData(signal) {
1171 let aborted = false;
1172 if (!this.done) {
1173 let onAbort = () => this.cancel();
1174 signal.addEventListener("abort", onAbort);
1175 aborted = await new Promise(resolve => {
1176 this.subscribe(aborted => {
1177 signal.removeEventListener("abort", onAbort);
1178 if (aborted || this.done) {
1179 resolve(aborted);
1180 }
1181 });
1182 });
1183 }
1184 return aborted;
1185 }
1186 get done() {
1187 return this.pendingKeysSet.size === 0;
1188 }
1189 get unwrappedData() {
1190 invariant(this.data !== null && this.done, "Can only unwrap data on initialized and settled deferreds");
1191 return Object.entries(this.data).reduce((acc, _ref3) => {
1192 let [key, value] = _ref3;
1193 return Object.assign(acc, {
1194 [key]: unwrapTrackedPromise(value)
1195 });
1196 }, {});
1197 }
1198 get pendingKeys() {
1199 return Array.from(this.pendingKeysSet);
1200 }
1201}
1202function isTrackedPromise(value) {
1203 return value instanceof Promise && value._tracked === true;
1204}
1205function unwrapTrackedPromise(value) {
1206 if (!isTrackedPromise(value)) {
1207 return value;
1208 }
1209 if (value._error) {
1210 throw value._error;
1211 }
1212 return value._data;
1213}
1214const defer = function defer(data, init) {
1215 if (init === void 0) {
1216 init = {};
1217 }
1218 let responseInit = typeof init === "number" ? {
1219 status: init
1220 } : init;
1221 return new DeferredData(data, responseInit);
1222};
1223/**
1224 * A redirect response. Sets the status code and the `Location` header.
1225 * Defaults to "302 Found".
1226 */
1227const redirect = function redirect(url, init) {
1228 if (init === void 0) {
1229 init = 302;
1230 }
1231 let responseInit = init;
1232 if (typeof responseInit === "number") {
1233 responseInit = {
1234 status: responseInit
1235 };
1236 } else if (typeof responseInit.status === "undefined") {
1237 responseInit.status = 302;
1238 }
1239 let headers = new Headers(responseInit.headers);
1240 headers.set("Location", url);
1241 return new Response(null, _extends({}, responseInit, {
1242 headers
1243 }));
1244};
1245/**
1246 * A redirect response that will force a document reload to the new location.
1247 * Sets the status code and the `Location` header.
1248 * Defaults to "302 Found".
1249 */
1250const redirectDocument = (url, init) => {
1251 let response = redirect(url, init);
1252 response.headers.set("X-Remix-Reload-Document", "true");
1253 return response;
1254};
1255/**
1256 * A redirect response that will perform a `history.replaceState` instead of a
1257 * `history.pushState` for client-side navigation redirects.
1258 * Sets the status code and the `Location` header.
1259 * Defaults to "302 Found".
1260 */
1261const replace = (url, init) => {
1262 let response = redirect(url, init);
1263 response.headers.set("X-Remix-Replace", "true");
1264 return response;
1265};
1266/**
1267 * @private
1268 * Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
1269 *
1270 * We don't export the class for public use since it's an implementation
1271 * detail, but we export the interface above so folks can build their own
1272 * abstractions around instances via isRouteErrorResponse()
1273 */
1274class ErrorResponseImpl {
1275 constructor(status, statusText, data, internal) {
1276 if (internal === void 0) {
1277 internal = false;
1278 }
1279 this.status = status;
1280 this.statusText = statusText || "";
1281 this.internal = internal;
1282 if (data instanceof Error) {
1283 this.data = data.toString();
1284 this.error = data;
1285 } else {
1286 this.data = data;
1287 }
1288 }
1289}
1290/**
1291 * Check if the given error is an ErrorResponse generated from a 4xx/5xx
1292 * Response thrown from an action/loader
1293 */
1294function isRouteErrorResponse(error) {
1295 return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
1296}
1297
1298const validMutationMethodsArr = ["post", "put", "patch", "delete"];
1299const validMutationMethods = new Set(validMutationMethodsArr);
1300const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
1301const validRequestMethods = new Set(validRequestMethodsArr);
1302const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
1303const redirectPreserveMethodStatusCodes = new Set([307, 308]);
1304const IDLE_NAVIGATION = {
1305 state: "idle",
1306 location: undefined,
1307 formMethod: undefined,
1308 formAction: undefined,
1309 formEncType: undefined,
1310 formData: undefined,
1311 json: undefined,
1312 text: undefined
1313};
1314const IDLE_FETCHER = {
1315 state: "idle",
1316 data: undefined,
1317 formMethod: undefined,
1318 formAction: undefined,
1319 formEncType: undefined,
1320 formData: undefined,
1321 json: undefined,
1322 text: undefined
1323};
1324const IDLE_BLOCKER = {
1325 state: "unblocked",
1326 proceed: undefined,
1327 reset: undefined,
1328 location: undefined
1329};
1330const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
1331const defaultMapRouteProperties = route => ({
1332 hasErrorBoundary: Boolean(route.hasErrorBoundary)
1333});
1334const TRANSITIONS_STORAGE_KEY = "remix-router-transitions";
1335//#endregion
1336////////////////////////////////////////////////////////////////////////////////
1337//#region createRouter
1338////////////////////////////////////////////////////////////////////////////////
1339/**
1340 * Create a router and listen to history POP navigations
1341 */
1342function createRouter(init) {
1343 const routerWindow = init.window ? init.window : typeof window !== "undefined" ? window : undefined;
1344 const isBrowser = typeof routerWindow !== "undefined" && typeof routerWindow.document !== "undefined" && typeof routerWindow.document.createElement !== "undefined";
1345 const isServer = !isBrowser;
1346 invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
1347 let mapRouteProperties;
1348 if (init.mapRouteProperties) {
1349 mapRouteProperties = init.mapRouteProperties;
1350 } else if (init.detectErrorBoundary) {
1351 // If they are still using the deprecated version, wrap it with the new API
1352 let detectErrorBoundary = init.detectErrorBoundary;
1353 mapRouteProperties = route => ({
1354 hasErrorBoundary: detectErrorBoundary(route)
1355 });
1356 } else {
1357 mapRouteProperties = defaultMapRouteProperties;
1358 }
1359 // Routes keyed by ID
1360 let manifest = {};
1361 // Routes in tree format for matching
1362 let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
1363 let inFlightDataRoutes;
1364 let basename = init.basename || "/";
1365 let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
1366 let patchRoutesOnMissImpl = init.unstable_patchRoutesOnMiss;
1367 // Config driven behavior flags
1368 let future = _extends({
1369 v7_fetcherPersist: false,
1370 v7_normalizeFormMethod: false,
1371 v7_partialHydration: false,
1372 v7_prependBasename: false,
1373 v7_relativeSplatPath: false,
1374 v7_skipActionErrorRevalidation: false
1375 }, init.future);
1376 // Cleanup function for history
1377 let unlistenHistory = null;
1378 // Externally-provided functions to call on all state changes
1379 let subscribers = new Set();
1380 // Externally-provided object to hold scroll restoration locations during routing
1381 let savedScrollPositions = null;
1382 // Externally-provided function to get scroll restoration keys
1383 let getScrollRestorationKey = null;
1384 // Externally-provided function to get current scroll position
1385 let getScrollPosition = null;
1386 // One-time flag to control the initial hydration scroll restoration. Because
1387 // we don't get the saved positions from <ScrollRestoration /> until _after_
1388 // the initial render, we need to manually trigger a separate updateState to
1389 // send along the restoreScrollPosition
1390 // Set to true if we have `hydrationData` since we assume we were SSR'd and that
1391 // SSR did the initial scroll restoration.
1392 let initialScrollRestored = init.hydrationData != null;
1393 let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
1394 let initialErrors = null;
1395 if (initialMatches == null && !patchRoutesOnMissImpl) {
1396 // If we do not match a user-provided-route, fall back to the root
1397 // to allow the error boundary to take over
1398 let error = getInternalRouterError(404, {
1399 pathname: init.history.location.pathname
1400 });
1401 let {
1402 matches,
1403 route
1404 } = getShortCircuitMatches(dataRoutes);
1405 initialMatches = matches;
1406 initialErrors = {
1407 [route.id]: error
1408 };
1409 }
1410 // In SPA apps, if the user provided a patchRoutesOnMiss implementation and
1411 // our initial match is a splat route, clear them out so we run through lazy
1412 // discovery on hydration in case there's a more accurate lazy route match.
1413 // In SSR apps (with `hydrationData`), we expect that the server will send
1414 // up the proper matched routes so we don't want to run lazy discovery on
1415 // initial hydration and want to hydrate into the splat route.
1416 if (initialMatches && !init.hydrationData) {
1417 let fogOfWar = checkFogOfWar(initialMatches, dataRoutes, init.history.location.pathname);
1418 if (fogOfWar.active) {
1419 initialMatches = null;
1420 }
1421 }
1422 let initialized;
1423 if (!initialMatches) {
1424 initialized = false;
1425 initialMatches = [];
1426 // If partial hydration and fog of war is enabled, we will be running
1427 // `patchRoutesOnMiss` during hydration so include any partial matches as
1428 // the initial matches so we can properly render `HydrateFallback`'s
1429 if (future.v7_partialHydration) {
1430 let fogOfWar = checkFogOfWar(null, dataRoutes, init.history.location.pathname);
1431 if (fogOfWar.active && fogOfWar.matches) {
1432 initialMatches = fogOfWar.matches;
1433 }
1434 }
1435 } else if (initialMatches.some(m => m.route.lazy)) {
1436 // All initialMatches need to be loaded before we're ready. If we have lazy
1437 // functions around still then we'll need to run them in initialize()
1438 initialized = false;
1439 } else if (!initialMatches.some(m => m.route.loader)) {
1440 // If we've got no loaders to run, then we're good to go
1441 initialized = true;
1442 } else if (future.v7_partialHydration) {
1443 // If partial hydration is enabled, we're initialized so long as we were
1444 // provided with hydrationData for every route with a loader, and no loaders
1445 // were marked for explicit hydration
1446 let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
1447 let errors = init.hydrationData ? init.hydrationData.errors : null;
1448 let isRouteInitialized = m => {
1449 // No loader, nothing to initialize
1450 if (!m.route.loader) {
1451 return true;
1452 }
1453 // Explicitly opting-in to running on hydration
1454 if (typeof m.route.loader === "function" && m.route.loader.hydrate === true) {
1455 return false;
1456 }
1457 // Otherwise, initialized if hydrated with data or an error
1458 return loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined;
1459 };
1460 // If errors exist, don't consider routes below the boundary
1461 if (errors) {
1462 let idx = initialMatches.findIndex(m => errors[m.route.id] !== undefined);
1463 initialized = initialMatches.slice(0, idx + 1).every(isRouteInitialized);
1464 } else {
1465 initialized = initialMatches.every(isRouteInitialized);
1466 }
1467 } else {
1468 // Without partial hydration - we're initialized if we were provided any
1469 // hydrationData - which is expected to be complete
1470 initialized = init.hydrationData != null;
1471 }
1472 let router;
1473 let state = {
1474 historyAction: init.history.action,
1475 location: init.history.location,
1476 matches: initialMatches,
1477 initialized,
1478 navigation: IDLE_NAVIGATION,
1479 // Don't restore on initial updateState() if we were SSR'd
1480 restoreScrollPosition: init.hydrationData != null ? false : null,
1481 preventScrollReset: false,
1482 revalidation: "idle",
1483 loaderData: init.hydrationData && init.hydrationData.loaderData || {},
1484 actionData: init.hydrationData && init.hydrationData.actionData || null,
1485 errors: init.hydrationData && init.hydrationData.errors || initialErrors,
1486 fetchers: new Map(),
1487 blockers: new Map()
1488 };
1489 // -- Stateful internal variables to manage navigations --
1490 // Current navigation in progress (to be committed in completeNavigation)
1491 let pendingAction = Action.Pop;
1492 // Should the current navigation prevent the scroll reset if scroll cannot
1493 // be restored?
1494 let pendingPreventScrollReset = false;
1495 // AbortController for the active navigation
1496 let pendingNavigationController;
1497 // Should the current navigation enable document.startViewTransition?
1498 let pendingViewTransitionEnabled = false;
1499 // Store applied view transitions so we can apply them on POP
1500 let appliedViewTransitions = new Map();
1501 // Cleanup function for persisting applied transitions to sessionStorage
1502 let removePageHideEventListener = null;
1503 // We use this to avoid touching history in completeNavigation if a
1504 // revalidation is entirely uninterrupted
1505 let isUninterruptedRevalidation = false;
1506 // Use this internal flag to force revalidation of all loaders:
1507 // - submissions (completed or interrupted)
1508 // - useRevalidator()
1509 // - X-Remix-Revalidate (from redirect)
1510 let isRevalidationRequired = false;
1511 // Use this internal array to capture routes that require revalidation due
1512 // to a cancelled deferred on action submission
1513 let cancelledDeferredRoutes = [];
1514 // Use this internal array to capture fetcher loads that were cancelled by an
1515 // action navigation and require revalidation
1516 let cancelledFetcherLoads = new Set();
1517 // AbortControllers for any in-flight fetchers
1518 let fetchControllers = new Map();
1519 // Track loads based on the order in which they started
1520 let incrementingLoadId = 0;
1521 // Track the outstanding pending navigation data load to be compared against
1522 // the globally incrementing load when a fetcher load lands after a completed
1523 // navigation
1524 let pendingNavigationLoadId = -1;
1525 // Fetchers that triggered data reloads as a result of their actions
1526 let fetchReloadIds = new Map();
1527 // Fetchers that triggered redirect navigations
1528 let fetchRedirectIds = new Set();
1529 // Most recent href/match for fetcher.load calls for fetchers
1530 let fetchLoadMatches = new Map();
1531 // Ref-count mounted fetchers so we know when it's ok to clean them up
1532 let activeFetchers = new Map();
1533 // Fetchers that have requested a delete when using v7_fetcherPersist,
1534 // they'll be officially removed after they return to idle
1535 let deletedFetchers = new Set();
1536 // Store DeferredData instances for active route matches. When a
1537 // route loader returns defer() we stick one in here. Then, when a nested
1538 // promise resolves we update loaderData. If a new navigation starts we
1539 // cancel active deferreds for eliminated routes.
1540 let activeDeferreds = new Map();
1541 // Store blocker functions in a separate Map outside of router state since
1542 // we don't need to update UI state if they change
1543 let blockerFunctions = new Map();
1544 // Map of pending patchRoutesOnMiss() promises (keyed by path/matches) so
1545 // that we only kick them off once for a given combo
1546 let pendingPatchRoutes = new Map();
1547 // Flag to ignore the next history update, so we can revert the URL change on
1548 // a POP navigation that was blocked by the user without touching router state
1549 let ignoreNextHistoryUpdate = false;
1550 // Initialize the router, all side effects should be kicked off from here.
1551 // Implemented as a Fluent API for ease of:
1552 // let router = createRouter(init).initialize();
1553 function initialize() {
1554 // If history informs us of a POP navigation, start the navigation but do not update
1555 // state. We'll update our own state once the navigation completes
1556 unlistenHistory = init.history.listen(_ref => {
1557 let {
1558 action: historyAction,
1559 location,
1560 delta
1561 } = _ref;
1562 // Ignore this event if it was just us resetting the URL from a
1563 // blocked POP navigation
1564 if (ignoreNextHistoryUpdate) {
1565 ignoreNextHistoryUpdate = false;
1566 return;
1567 }
1568 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.");
1569 let blockerKey = shouldBlockNavigation({
1570 currentLocation: state.location,
1571 nextLocation: location,
1572 historyAction
1573 });
1574 if (blockerKey && delta != null) {
1575 // Restore the URL to match the current UI, but don't update router state
1576 ignoreNextHistoryUpdate = true;
1577 init.history.go(delta * -1);
1578 // Put the blocker into a blocked state
1579 updateBlocker(blockerKey, {
1580 state: "blocked",
1581 location,
1582 proceed() {
1583 updateBlocker(blockerKey, {
1584 state: "proceeding",
1585 proceed: undefined,
1586 reset: undefined,
1587 location
1588 });
1589 // Re-do the same POP navigation we just blocked
1590 init.history.go(delta);
1591 },
1592 reset() {
1593 let blockers = new Map(state.blockers);
1594 blockers.set(blockerKey, IDLE_BLOCKER);
1595 updateState({
1596 blockers
1597 });
1598 }
1599 });
1600 return;
1601 }
1602 return startNavigation(historyAction, location);
1603 });
1604 if (isBrowser) {
1605 // FIXME: This feels gross. How can we cleanup the lines between
1606 // scrollRestoration/appliedTransitions persistance?
1607 restoreAppliedTransitions(routerWindow, appliedViewTransitions);
1608 let _saveAppliedTransitions = () => persistAppliedTransitions(routerWindow, appliedViewTransitions);
1609 routerWindow.addEventListener("pagehide", _saveAppliedTransitions);
1610 removePageHideEventListener = () => routerWindow.removeEventListener("pagehide", _saveAppliedTransitions);
1611 }
1612 // Kick off initial data load if needed. Use Pop to avoid modifying history
1613 // Note we don't do any handling of lazy here. For SPA's it'll get handled
1614 // in the normal navigation flow. For SSR it's expected that lazy modules are
1615 // resolved prior to router creation since we can't go into a fallbackElement
1616 // UI for SSR'd apps
1617 if (!state.initialized) {
1618 startNavigation(Action.Pop, state.location, {
1619 initialHydration: true
1620 });
1621 }
1622 return router;
1623 }
1624 // Clean up a router and it's side effects
1625 function dispose() {
1626 if (unlistenHistory) {
1627 unlistenHistory();
1628 }
1629 if (removePageHideEventListener) {
1630 removePageHideEventListener();
1631 }
1632 subscribers.clear();
1633 pendingNavigationController && pendingNavigationController.abort();
1634 state.fetchers.forEach((_, key) => deleteFetcher(key));
1635 state.blockers.forEach((_, key) => deleteBlocker(key));
1636 }
1637 // Subscribe to state updates for the router
1638 function subscribe(fn) {
1639 subscribers.add(fn);
1640 return () => subscribers.delete(fn);
1641 }
1642 // Update our state and notify the calling context of the change
1643 function updateState(newState, opts) {
1644 if (opts === void 0) {
1645 opts = {};
1646 }
1647 state = _extends({}, state, newState);
1648 // Prep fetcher cleanup so we can tell the UI which fetcher data entries
1649 // can be removed
1650 let completedFetchers = [];
1651 let deletedFetchersKeys = [];
1652 if (future.v7_fetcherPersist) {
1653 state.fetchers.forEach((fetcher, key) => {
1654 if (fetcher.state === "idle") {
1655 if (deletedFetchers.has(key)) {
1656 // Unmounted from the UI and can be totally removed
1657 deletedFetchersKeys.push(key);
1658 } else {
1659 // Returned to idle but still mounted in the UI, so semi-remains for
1660 // revalidations and such
1661 completedFetchers.push(key);
1662 }
1663 }
1664 });
1665 }
1666 // Iterate over a local copy so that if flushSync is used and we end up
1667 // removing and adding a new subscriber due to the useCallback dependencies,
1668 // we don't get ourselves into a loop calling the new subscriber immediately
1669 [...subscribers].forEach(subscriber => subscriber(state, {
1670 deletedFetchers: deletedFetchersKeys,
1671 unstable_viewTransitionOpts: opts.viewTransitionOpts,
1672 unstable_flushSync: opts.flushSync === true
1673 }));
1674 // Remove idle fetchers from state since we only care about in-flight fetchers.
1675 if (future.v7_fetcherPersist) {
1676 completedFetchers.forEach(key => state.fetchers.delete(key));
1677 deletedFetchersKeys.forEach(key => deleteFetcher(key));
1678 }
1679 }
1680 // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
1681 // and setting state.[historyAction/location/matches] to the new route.
1682 // - Location is a required param
1683 // - Navigation will always be set to IDLE_NAVIGATION
1684 // - Can pass any other state in newState
1685 function completeNavigation(location, newState, _temp) {
1686 var _location$state, _location$state2;
1687 let {
1688 flushSync
1689 } = _temp === void 0 ? {} : _temp;
1690 // Deduce if we're in a loading/actionReload state:
1691 // - We have committed actionData in the store
1692 // - The current navigation was a mutation submission
1693 // - We're past the submitting state and into the loading state
1694 // - The location being loaded is not the result of a redirect
1695 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;
1696 let actionData;
1697 if (newState.actionData) {
1698 if (Object.keys(newState.actionData).length > 0) {
1699 actionData = newState.actionData;
1700 } else {
1701 // Empty actionData -> clear prior actionData due to an action error
1702 actionData = null;
1703 }
1704 } else if (isActionReload) {
1705 // Keep the current data if we're wrapping up the action reload
1706 actionData = state.actionData;
1707 } else {
1708 // Clear actionData on any other completed navigations
1709 actionData = null;
1710 }
1711 // Always preserve any existing loaderData from re-used routes
1712 let loaderData = newState.loaderData ? mergeLoaderData(state.loaderData, newState.loaderData, newState.matches || [], newState.errors) : state.loaderData;
1713 // On a successful navigation we can assume we got through all blockers
1714 // so we can start fresh
1715 let blockers = state.blockers;
1716 if (blockers.size > 0) {
1717 blockers = new Map(blockers);
1718 blockers.forEach((_, k) => blockers.set(k, IDLE_BLOCKER));
1719 }
1720 // Always respect the user flag. Otherwise don't reset on mutation
1721 // submission navigations unless they redirect
1722 let preventScrollReset = pendingPreventScrollReset === true || state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && ((_location$state2 = location.state) == null ? void 0 : _location$state2._isRedirect) !== true;
1723 // Commit any in-flight routes at the end of the HMR revalidation "navigation"
1724 if (inFlightDataRoutes) {
1725 dataRoutes = inFlightDataRoutes;
1726 inFlightDataRoutes = undefined;
1727 }
1728 if (isUninterruptedRevalidation) ; else if (pendingAction === Action.Pop) ; else if (pendingAction === Action.Push) {
1729 init.history.push(location, location.state);
1730 } else if (pendingAction === Action.Replace) {
1731 init.history.replace(location, location.state);
1732 }
1733 let viewTransitionOpts;
1734 // On POP, enable transitions if they were enabled on the original navigation
1735 if (pendingAction === Action.Pop) {
1736 // Forward takes precedence so they behave like the original navigation
1737 let priorPaths = appliedViewTransitions.get(state.location.pathname);
1738 if (priorPaths && priorPaths.has(location.pathname)) {
1739 viewTransitionOpts = {
1740 currentLocation: state.location,
1741 nextLocation: location
1742 };
1743 } else if (appliedViewTransitions.has(location.pathname)) {
1744 // If we don't have a previous forward nav, assume we're popping back to
1745 // the new location and enable if that location previously enabled
1746 viewTransitionOpts = {
1747 currentLocation: location,
1748 nextLocation: state.location
1749 };
1750 }
1751 } else if (pendingViewTransitionEnabled) {
1752 // Store the applied transition on PUSH/REPLACE
1753 let toPaths = appliedViewTransitions.get(state.location.pathname);
1754 if (toPaths) {
1755 toPaths.add(location.pathname);
1756 } else {
1757 toPaths = new Set([location.pathname]);
1758 appliedViewTransitions.set(state.location.pathname, toPaths);
1759 }
1760 viewTransitionOpts = {
1761 currentLocation: state.location,
1762 nextLocation: location
1763 };
1764 }
1765 updateState(_extends({}, newState, {
1766 actionData,
1767 loaderData,
1768 historyAction: pendingAction,
1769 location,
1770 initialized: true,
1771 navigation: IDLE_NAVIGATION,
1772 revalidation: "idle",
1773 restoreScrollPosition: getSavedScrollPosition(location, newState.matches || state.matches),
1774 preventScrollReset,
1775 blockers
1776 }), {
1777 viewTransitionOpts,
1778 flushSync: flushSync === true
1779 });
1780 // Reset stateful navigation vars
1781 pendingAction = Action.Pop;
1782 pendingPreventScrollReset = false;
1783 pendingViewTransitionEnabled = false;
1784 isUninterruptedRevalidation = false;
1785 isRevalidationRequired = false;
1786 cancelledDeferredRoutes = [];
1787 }
1788 // Trigger a navigation event, which can either be a numerical POP or a PUSH
1789 // replace with an optional submission
1790 async function navigate(to, opts) {
1791 if (typeof to === "number") {
1792 init.history.go(to);
1793 return;
1794 }
1795 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);
1796 let {
1797 path,
1798 submission,
1799 error
1800 } = normalizeNavigateOptions(future.v7_normalizeFormMethod, false, normalizedPath, opts);
1801 let currentLocation = state.location;
1802 let nextLocation = createLocation(state.location, path, opts && opts.state);
1803 // When using navigate as a PUSH/REPLACE we aren't reading an already-encoded
1804 // URL from window.location, so we need to encode it here so the behavior
1805 // remains the same as POP and non-data-router usages. new URL() does all
1806 // the same encoding we'd get from a history.pushState/window.location read
1807 // without having to touch history
1808 nextLocation = _extends({}, nextLocation, init.history.encodeLocation(nextLocation));
1809 let userReplace = opts && opts.replace != null ? opts.replace : undefined;
1810 let historyAction = Action.Push;
1811 if (userReplace === true) {
1812 historyAction = Action.Replace;
1813 } else if (userReplace === false) ; else if (submission != null && isMutationMethod(submission.formMethod) && submission.formAction === state.location.pathname + state.location.search) {
1814 // By default on submissions to the current location we REPLACE so that
1815 // users don't have to double-click the back button to get to the prior
1816 // location. If the user redirects to a different location from the
1817 // action/loader this will be ignored and the redirect will be a PUSH
1818 historyAction = Action.Replace;
1819 }
1820 let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
1821 let flushSync = (opts && opts.unstable_flushSync) === true;
1822 let blockerKey = shouldBlockNavigation({
1823 currentLocation,
1824 nextLocation,
1825 historyAction
1826 });
1827 if (blockerKey) {
1828 // Put the blocker into a blocked state
1829 updateBlocker(blockerKey, {
1830 state: "blocked",
1831 location: nextLocation,
1832 proceed() {
1833 updateBlocker(blockerKey, {
1834 state: "proceeding",
1835 proceed: undefined,
1836 reset: undefined,
1837 location: nextLocation
1838 });
1839 // Send the same navigation through
1840 navigate(to, opts);
1841 },
1842 reset() {
1843 let blockers = new Map(state.blockers);
1844 blockers.set(blockerKey, IDLE_BLOCKER);
1845 updateState({
1846 blockers
1847 });
1848 }
1849 });
1850 return;
1851 }
1852 return await startNavigation(historyAction, nextLocation, {
1853 submission,
1854 // Send through the formData serialization error if we have one so we can
1855 // render at the right error boundary after we match routes
1856 pendingError: error,
1857 preventScrollReset,
1858 replace: opts && opts.replace,
1859 enableViewTransition: opts && opts.unstable_viewTransition,
1860 flushSync
1861 });
1862 }
1863 // Revalidate all current loaders. If a navigation is in progress or if this
1864 // is interrupted by a navigation, allow this to "succeed" by calling all
1865 // loaders during the next loader round
1866 function revalidate() {
1867 interruptActiveLoads();
1868 updateState({
1869 revalidation: "loading"
1870 });
1871 // If we're currently submitting an action, we don't need to start a new
1872 // navigation, we'll just let the follow up loader execution call all loaders
1873 if (state.navigation.state === "submitting") {
1874 return;
1875 }
1876 // If we're currently in an idle state, start a new navigation for the current
1877 // action/location and mark it as uninterrupted, which will skip the history
1878 // update in completeNavigation
1879 if (state.navigation.state === "idle") {
1880 startNavigation(state.historyAction, state.location, {
1881 startUninterruptedRevalidation: true
1882 });
1883 return;
1884 }
1885 // Otherwise, if we're currently in a loading state, just start a new
1886 // navigation to the navigation.location but do not trigger an uninterrupted
1887 // revalidation so that history correctly updates once the navigation completes
1888 startNavigation(pendingAction || state.historyAction, state.navigation.location, {
1889 overrideNavigation: state.navigation
1890 });
1891 }
1892 // Start a navigation to the given action/location. Can optionally provide a
1893 // overrideNavigation which will override the normalLoad in the case of a redirect
1894 // navigation
1895 async function startNavigation(historyAction, location, opts) {
1896 // Abort any in-progress navigations and start a new one. Unset any ongoing
1897 // uninterrupted revalidations unless told otherwise, since we want this
1898 // new navigation to update history normally
1899 pendingNavigationController && pendingNavigationController.abort();
1900 pendingNavigationController = null;
1901 pendingAction = historyAction;
1902 isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true;
1903 // Save the current scroll position every time we start a new navigation,
1904 // and track whether we should reset scroll on completion
1905 saveScrollPosition(state.location, state.matches);
1906 pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
1907 pendingViewTransitionEnabled = (opts && opts.enableViewTransition) === true;
1908 let routesToUse = inFlightDataRoutes || dataRoutes;
1909 let loadingNavigation = opts && opts.overrideNavigation;
1910 let matches = matchRoutes(routesToUse, location, basename);
1911 let flushSync = (opts && opts.flushSync) === true;
1912 let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);
1913 if (fogOfWar.active && fogOfWar.matches) {
1914 matches = fogOfWar.matches;
1915 }
1916 // Short circuit with a 404 on the root error boundary if we match nothing
1917 if (!matches) {
1918 let {
1919 error,
1920 notFoundMatches,
1921 route
1922 } = handleNavigational404(location.pathname);
1923 completeNavigation(location, {
1924 matches: notFoundMatches,
1925 loaderData: {},
1926 errors: {
1927 [route.id]: error
1928 }
1929 }, {
1930 flushSync
1931 });
1932 return;
1933 }
1934 // Short circuit if it's only a hash change and not a revalidation or
1935 // mutation submission.
1936 //
1937 // Ignore on initial page loads because since the initial load will always
1938 // be "same hash". For example, on /page#hash and submit a <Form method="post">
1939 // which will default to a navigation to /page
1940 if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
1941 completeNavigation(location, {
1942 matches
1943 }, {
1944 flushSync
1945 });
1946 return;
1947 }
1948 // Create a controller/Request for this navigation
1949 pendingNavigationController = new AbortController();
1950 let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.submission);
1951 let pendingActionResult;
1952 if (opts && opts.pendingError) {
1953 // If we have a pendingError, it means the user attempted a GET submission
1954 // with binary FormData so assign here and skip to handleLoaders. That
1955 // way we handle calling loaders above the boundary etc. It's not really
1956 // different from an actionError in that sense.
1957 pendingActionResult = [findNearestBoundary(matches).route.id, {
1958 type: ResultType.error,
1959 error: opts.pendingError
1960 }];
1961 } else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
1962 // Call action if we received an action submission
1963 let actionResult = await handleAction(request, location, opts.submission, matches, fogOfWar.active, {
1964 replace: opts.replace,
1965 flushSync
1966 });
1967 if (actionResult.shortCircuited) {
1968 return;
1969 }
1970 // If we received a 404 from handleAction, it's because we couldn't lazily
1971 // discover the destination route so we don't want to call loaders
1972 if (actionResult.pendingActionResult) {
1973 let [routeId, result] = actionResult.pendingActionResult;
1974 if (isErrorResult(result) && isRouteErrorResponse(result.error) && result.error.status === 404) {
1975 pendingNavigationController = null;
1976 completeNavigation(location, {
1977 matches: actionResult.matches,
1978 loaderData: {},
1979 errors: {
1980 [routeId]: result.error
1981 }
1982 });
1983 return;
1984 }
1985 }
1986 matches = actionResult.matches || matches;
1987 pendingActionResult = actionResult.pendingActionResult;
1988 loadingNavigation = getLoadingNavigation(location, opts.submission);
1989 flushSync = false;
1990 // No need to do fog of war matching again on loader execution
1991 fogOfWar.active = false;
1992 // Create a GET request for the loaders
1993 request = createClientSideRequest(init.history, request.url, request.signal);
1994 }
1995 // Call loaders
1996 let {
1997 shortCircuited,
1998 matches: updatedMatches,
1999 loaderData,
2000 errors
2001 } = await handleLoaders(request, location, matches, fogOfWar.active, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionResult);
2002 if (shortCircuited) {
2003 return;
2004 }
2005 // Clean up now that the action/loaders have completed. Don't clean up if
2006 // we short circuited because pendingNavigationController will have already
2007 // been assigned to a new controller for the next navigation
2008 pendingNavigationController = null;
2009 completeNavigation(location, _extends({
2010 matches: updatedMatches || matches
2011 }, getActionDataForCommit(pendingActionResult), {
2012 loaderData,
2013 errors
2014 }));
2015 }
2016 // Call the action matched by the leaf route for this navigation and handle
2017 // redirects/errors
2018 async function handleAction(request, location, submission, matches, isFogOfWar, opts) {
2019 if (opts === void 0) {
2020 opts = {};
2021 }
2022 interruptActiveLoads();
2023 // Put us in a submitting state
2024 let navigation = getSubmittingNavigation(location, submission);
2025 updateState({
2026 navigation
2027 }, {
2028 flushSync: opts.flushSync === true
2029 });
2030 if (isFogOfWar) {
2031 let discoverResult = await discoverRoutes(matches, location.pathname, request.signal);
2032 if (discoverResult.type === "aborted") {
2033 return {
2034 shortCircuited: true
2035 };
2036 } else if (discoverResult.type === "error") {
2037 let {
2038 boundaryId,
2039 error
2040 } = handleDiscoverRouteError(location.pathname, discoverResult);
2041 return {
2042 matches: discoverResult.partialMatches,
2043 pendingActionResult: [boundaryId, {
2044 type: ResultType.error,
2045 error
2046 }]
2047 };
2048 } else if (!discoverResult.matches) {
2049 let {
2050 notFoundMatches,
2051 error,
2052 route
2053 } = handleNavigational404(location.pathname);
2054 return {
2055 matches: notFoundMatches,
2056 pendingActionResult: [route.id, {
2057 type: ResultType.error,
2058 error
2059 }]
2060 };
2061 } else {
2062 matches = discoverResult.matches;
2063 }
2064 }
2065 // Call our action and get the result
2066 let result;
2067 let actionMatch = getTargetMatch(matches, location);
2068 if (!actionMatch.route.action && !actionMatch.route.lazy) {
2069 result = {
2070 type: ResultType.error,
2071 error: getInternalRouterError(405, {
2072 method: request.method,
2073 pathname: location.pathname,
2074 routeId: actionMatch.route.id
2075 })
2076 };
2077 } else {
2078 let results = await callDataStrategy("action", request, [actionMatch], matches);
2079 result = results[0];
2080 if (request.signal.aborted) {
2081 return {
2082 shortCircuited: true
2083 };
2084 }
2085 }
2086 if (isRedirectResult(result)) {
2087 let replace;
2088 if (opts && opts.replace != null) {
2089 replace = opts.replace;
2090 } else {
2091 // If the user didn't explicity indicate replace behavior, replace if
2092 // we redirected to the exact same location we're currently at to avoid
2093 // double back-buttons
2094 let location = normalizeRedirectLocation(result.response.headers.get("Location"), new URL(request.url), basename);
2095 replace = location === state.location.pathname + state.location.search;
2096 }
2097 await startRedirectNavigation(request, result, {
2098 submission,
2099 replace
2100 });
2101 return {
2102 shortCircuited: true
2103 };
2104 }
2105 if (isDeferredResult(result)) {
2106 throw getInternalRouterError(400, {
2107 type: "defer-action"
2108 });
2109 }
2110 if (isErrorResult(result)) {
2111 // Store off the pending error - we use it to determine which loaders
2112 // to call and will commit it when we complete the navigation
2113 let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
2114 // By default, all submissions to the current location are REPLACE
2115 // navigations, but if the action threw an error that'll be rendered in
2116 // an errorElement, we fall back to PUSH so that the user can use the
2117 // back button to get back to the pre-submission form location to try
2118 // again
2119 if ((opts && opts.replace) !== true) {
2120 pendingAction = Action.Push;
2121 }
2122 return {
2123 matches,
2124 pendingActionResult: [boundaryMatch.route.id, result]
2125 };
2126 }
2127 return {
2128 matches,
2129 pendingActionResult: [actionMatch.route.id, result]
2130 };
2131 }
2132 // Call all applicable loaders for the given matches, handling redirects,
2133 // errors, etc.
2134 async function handleLoaders(request, location, matches, isFogOfWar, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync, pendingActionResult) {
2135 // Figure out the right navigation we want to use for data loading
2136 let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
2137 // If this was a redirect from an action we don't have a "submission" but
2138 // we have it on the loading navigation so use that if available
2139 let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);
2140 // If this is an uninterrupted revalidation, we remain in our current idle
2141 // state. If not, we need to switch to our loading state and load data,
2142 // preserving any new action data or existing action data (in the case of
2143 // a revalidation interrupting an actionReload)
2144 // If we have partialHydration enabled, then don't update the state for the
2145 // initial data load since it's not a "navigation"
2146 let shouldUpdateNavigationState = !isUninterruptedRevalidation && (!future.v7_partialHydration || !initialHydration);
2147 // When fog of war is enabled, we enter our `loading` state earlier so we
2148 // can discover new routes during the `loading` state. We skip this if
2149 // we've already run actions since we would have done our matching already.
2150 // If the children() function threw then, we want to proceed with the
2151 // partial matches it discovered.
2152 if (isFogOfWar) {
2153 if (shouldUpdateNavigationState) {
2154 let actionData = getUpdatedActionData(pendingActionResult);
2155 updateState(_extends({
2156 navigation: loadingNavigation
2157 }, actionData !== undefined ? {
2158 actionData
2159 } : {}), {
2160 flushSync
2161 });
2162 }
2163 let discoverResult = await discoverRoutes(matches, location.pathname, request.signal);
2164 if (discoverResult.type === "aborted") {
2165 return {
2166 shortCircuited: true
2167 };
2168 } else if (discoverResult.type === "error") {
2169 let {
2170 boundaryId,
2171 error
2172 } = handleDiscoverRouteError(location.pathname, discoverResult);
2173 return {
2174 matches: discoverResult.partialMatches,
2175 loaderData: {},
2176 errors: {
2177 [boundaryId]: error
2178 }
2179 };
2180 } else if (!discoverResult.matches) {
2181 let {
2182 error,
2183 notFoundMatches,
2184 route
2185 } = handleNavigational404(location.pathname);
2186 return {
2187 matches: notFoundMatches,
2188 loaderData: {},
2189 errors: {
2190 [route.id]: error
2191 }
2192 };
2193 } else {
2194 matches = discoverResult.matches;
2195 }
2196 }
2197 let routesToUse = inFlightDataRoutes || dataRoutes;
2198 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);
2199 // Cancel pending deferreds for no-longer-matched routes or routes we're
2200 // about to reload. Note that if this is an action reload we would have
2201 // already cancelled all pending deferreds so this would be a no-op
2202 cancelActiveDeferreds(routeId => !(matches && matches.some(m => m.route.id === routeId)) || matchesToLoad && matchesToLoad.some(m => m.route.id === routeId));
2203 pendingNavigationLoadId = ++incrementingLoadId;
2204 // Short circuit if we have no loaders to run
2205 if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
2206 let updatedFetchers = markFetchRedirectsDone();
2207 completeNavigation(location, _extends({
2208 matches,
2209 loaderData: {},
2210 // Commit pending error if we're short circuiting
2211 errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
2212 [pendingActionResult[0]]: pendingActionResult[1].error
2213 } : null
2214 }, getActionDataForCommit(pendingActionResult), updatedFetchers ? {
2215 fetchers: new Map(state.fetchers)
2216 } : {}), {
2217 flushSync
2218 });
2219 return {
2220 shortCircuited: true
2221 };
2222 }
2223 if (shouldUpdateNavigationState) {
2224 let updates = {};
2225 if (!isFogOfWar) {
2226 // Only update navigation/actionNData if we didn't already do it above
2227 updates.navigation = loadingNavigation;
2228 let actionData = getUpdatedActionData(pendingActionResult);
2229 if (actionData !== undefined) {
2230 updates.actionData = actionData;
2231 }
2232 }
2233 if (revalidatingFetchers.length > 0) {
2234 updates.fetchers = getUpdatedRevalidatingFetchers(revalidatingFetchers);
2235 }
2236 updateState(updates, {
2237 flushSync
2238 });
2239 }
2240 revalidatingFetchers.forEach(rf => {
2241 if (fetchControllers.has(rf.key)) {
2242 abortFetcher(rf.key);
2243 }
2244 if (rf.controller) {
2245 // Fetchers use an independent AbortController so that aborting a fetcher
2246 // (via deleteFetcher) does not abort the triggering navigation that
2247 // triggered the revalidation
2248 fetchControllers.set(rf.key, rf.controller);
2249 }
2250 });
2251 // Proxy navigation abort through to revalidation fetchers
2252 let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(f => abortFetcher(f.key));
2253 if (pendingNavigationController) {
2254 pendingNavigationController.signal.addEventListener("abort", abortPendingFetchRevalidations);
2255 }
2256 let {
2257 loaderResults,
2258 fetcherResults
2259 } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
2260 if (request.signal.aborted) {
2261 return {
2262 shortCircuited: true
2263 };
2264 }
2265 // Clean up _after_ loaders have completed. Don't clean up if we short
2266 // circuited because fetchControllers would have been aborted and
2267 // reassigned to new controllers for the next navigation
2268 if (pendingNavigationController) {
2269 pendingNavigationController.signal.removeEventListener("abort", abortPendingFetchRevalidations);
2270 }
2271 revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key));
2272 // If any loaders returned a redirect Response, start a new REPLACE navigation
2273 let redirect = findRedirect([...loaderResults, ...fetcherResults]);
2274 if (redirect) {
2275 if (redirect.idx >= matchesToLoad.length) {
2276 // If this redirect came from a fetcher make sure we mark it in
2277 // fetchRedirectIds so it doesn't get revalidated on the next set of
2278 // loader executions
2279 let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
2280 fetchRedirectIds.add(fetcherKey);
2281 }
2282 await startRedirectNavigation(request, redirect.result, {
2283 replace
2284 });
2285 return {
2286 shortCircuited: true
2287 };
2288 }
2289 // Process and commit output from loaders
2290 let {
2291 loaderData,
2292 errors
2293 } = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2294 // Wire up subscribers to update loaderData as promises settle
2295 activeDeferreds.forEach((deferredData, routeId) => {
2296 deferredData.subscribe(aborted => {
2297 // Note: No need to updateState here since the TrackedPromise on
2298 // loaderData is stable across resolve/reject
2299 // Remove this instance if we were aborted or if promises have settled
2300 if (aborted || deferredData.done) {
2301 activeDeferreds.delete(routeId);
2302 }
2303 });
2304 });
2305 // During partial hydration, preserve SSR errors for routes that don't re-run
2306 if (future.v7_partialHydration && initialHydration && state.errors) {
2307 Object.entries(state.errors).filter(_ref2 => {
2308 let [id] = _ref2;
2309 return !matchesToLoad.some(m => m.route.id === id);
2310 }).forEach(_ref3 => {
2311 let [routeId, error] = _ref3;
2312 errors = Object.assign(errors || {}, {
2313 [routeId]: error
2314 });
2315 });
2316 }
2317 let updatedFetchers = markFetchRedirectsDone();
2318 let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
2319 let shouldUpdateFetchers = updatedFetchers || didAbortFetchLoads || revalidatingFetchers.length > 0;
2320 return _extends({
2321 matches,
2322 loaderData,
2323 errors
2324 }, shouldUpdateFetchers ? {
2325 fetchers: new Map(state.fetchers)
2326 } : {});
2327 }
2328 function getUpdatedActionData(pendingActionResult) {
2329 if (pendingActionResult && !isErrorResult(pendingActionResult[1])) {
2330 // This is cast to `any` currently because `RouteData`uses any and it
2331 // would be a breaking change to use any.
2332 // TODO: v7 - change `RouteData` to use `unknown` instead of `any`
2333 return {
2334 [pendingActionResult[0]]: pendingActionResult[1].data
2335 };
2336 } else if (state.actionData) {
2337 if (Object.keys(state.actionData).length === 0) {
2338 return null;
2339 } else {
2340 return state.actionData;
2341 }
2342 }
2343 }
2344 function getUpdatedRevalidatingFetchers(revalidatingFetchers) {
2345 revalidatingFetchers.forEach(rf => {
2346 let fetcher = state.fetchers.get(rf.key);
2347 let revalidatingFetcher = getLoadingFetcher(undefined, fetcher ? fetcher.data : undefined);
2348 state.fetchers.set(rf.key, revalidatingFetcher);
2349 });
2350 return new Map(state.fetchers);
2351 }
2352 // Trigger a fetcher load/submit for the given fetcher key
2353 function fetch(key, routeId, href, opts) {
2354 if (isServer) {
2355 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.");
2356 }
2357 if (fetchControllers.has(key)) abortFetcher(key);
2358 let flushSync = (opts && opts.unstable_flushSync) === true;
2359 let routesToUse = inFlightDataRoutes || dataRoutes;
2360 let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
2361 let matches = matchRoutes(routesToUse, normalizedPath, basename);
2362 let fogOfWar = checkFogOfWar(matches, routesToUse, normalizedPath);
2363 if (fogOfWar.active && fogOfWar.matches) {
2364 matches = fogOfWar.matches;
2365 }
2366 if (!matches) {
2367 setFetcherError(key, routeId, getInternalRouterError(404, {
2368 pathname: normalizedPath
2369 }), {
2370 flushSync
2371 });
2372 return;
2373 }
2374 let {
2375 path,
2376 submission,
2377 error
2378 } = normalizeNavigateOptions(future.v7_normalizeFormMethod, true, normalizedPath, opts);
2379 if (error) {
2380 setFetcherError(key, routeId, error, {
2381 flushSync
2382 });
2383 return;
2384 }
2385 let match = getTargetMatch(matches, path);
2386 pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2387 if (submission && isMutationMethod(submission.formMethod)) {
2388 handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2389 return;
2390 }
2391 // Store off the match so we can call it's shouldRevalidate on subsequent
2392 // revalidations
2393 fetchLoadMatches.set(key, {
2394 routeId,
2395 path
2396 });
2397 handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2398 }
2399 // Call the action for the matched fetcher.submit(), and then handle redirects,
2400 // errors, and revalidation
2401 async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, submission) {
2402 interruptActiveLoads();
2403 fetchLoadMatches.delete(key);
2404 function detectAndHandle405Error(m) {
2405 if (!m.route.action && !m.route.lazy) {
2406 let error = getInternalRouterError(405, {
2407 method: submission.formMethod,
2408 pathname: path,
2409 routeId: routeId
2410 });
2411 setFetcherError(key, routeId, error, {
2412 flushSync
2413 });
2414 return true;
2415 }
2416 return false;
2417 }
2418 if (!isFogOfWar && detectAndHandle405Error(match)) {
2419 return;
2420 }
2421 // Put this fetcher into it's submitting state
2422 let existingFetcher = state.fetchers.get(key);
2423 updateFetcherState(key, getSubmittingFetcher(submission, existingFetcher), {
2424 flushSync
2425 });
2426 let abortController = new AbortController();
2427 let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2428 if (isFogOfWar) {
2429 let discoverResult = await discoverRoutes(requestMatches, path, fetchRequest.signal);
2430 if (discoverResult.type === "aborted") {
2431 return;
2432 } else if (discoverResult.type === "error") {
2433 let {
2434 error
2435 } = handleDiscoverRouteError(path, discoverResult);
2436 setFetcherError(key, routeId, error, {
2437 flushSync
2438 });
2439 return;
2440 } else if (!discoverResult.matches) {
2441 setFetcherError(key, routeId, getInternalRouterError(404, {
2442 pathname: path
2443 }), {
2444 flushSync
2445 });
2446 return;
2447 } else {
2448 requestMatches = discoverResult.matches;
2449 match = getTargetMatch(requestMatches, path);
2450 if (detectAndHandle405Error(match)) {
2451 return;
2452 }
2453 }
2454 }
2455 // Call the action for the fetcher
2456 fetchControllers.set(key, abortController);
2457 let originatingLoadId = incrementingLoadId;
2458 let actionResults = await callDataStrategy("action", fetchRequest, [match], requestMatches);
2459 let actionResult = actionResults[0];
2460 if (fetchRequest.signal.aborted) {
2461 // We can delete this so long as we weren't aborted by our own fetcher
2462 // re-submit which would have put _new_ controller is in fetchControllers
2463 if (fetchControllers.get(key) === abortController) {
2464 fetchControllers.delete(key);
2465 }
2466 return;
2467 }
2468 // When using v7_fetcherPersist, we don't want errors bubbling up to the UI
2469 // or redirects processed for unmounted fetchers so we just revert them to
2470 // idle
2471 if (future.v7_fetcherPersist && deletedFetchers.has(key)) {
2472 if (isRedirectResult(actionResult) || isErrorResult(actionResult)) {
2473 updateFetcherState(key, getDoneFetcher(undefined));
2474 return;
2475 }
2476 // Let SuccessResult's fall through for revalidation
2477 } else {
2478 if (isRedirectResult(actionResult)) {
2479 fetchControllers.delete(key);
2480 if (pendingNavigationLoadId > originatingLoadId) {
2481 // A new navigation was kicked off after our action started, so that
2482 // should take precedence over this redirect navigation. We already
2483 // set isRevalidationRequired so all loaders for the new route should
2484 // fire unless opted out via shouldRevalidate
2485 updateFetcherState(key, getDoneFetcher(undefined));
2486 return;
2487 } else {
2488 fetchRedirectIds.add(key);
2489 updateFetcherState(key, getLoadingFetcher(submission));
2490 return startRedirectNavigation(fetchRequest, actionResult, {
2491 fetcherSubmission: submission
2492 });
2493 }
2494 }
2495 // Process any non-redirect errors thrown
2496 if (isErrorResult(actionResult)) {
2497 setFetcherError(key, routeId, actionResult.error);
2498 return;
2499 }
2500 }
2501 if (isDeferredResult(actionResult)) {
2502 throw getInternalRouterError(400, {
2503 type: "defer-action"
2504 });
2505 }
2506 // Start the data load for current matches, or the next location if we're
2507 // in the middle of a navigation
2508 let nextLocation = state.navigation.location || state.location;
2509 let revalidationRequest = createClientSideRequest(init.history, nextLocation, abortController.signal);
2510 let routesToUse = inFlightDataRoutes || dataRoutes;
2511 let matches = state.navigation.state !== "idle" ? matchRoutes(routesToUse, state.navigation.location, basename) : state.matches;
2512 invariant(matches, "Didn't find any matches after fetcher action");
2513 let loadId = ++incrementingLoadId;
2514 fetchReloadIds.set(key, loadId);
2515 let loadFetcher = getLoadingFetcher(submission, actionResult.data);
2516 state.fetchers.set(key, loadFetcher);
2517 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]);
2518 // Put all revalidating fetchers into the loading state, except for the
2519 // current fetcher which we want to keep in it's current loading state which
2520 // contains it's action submission info + action data
2521 revalidatingFetchers.filter(rf => rf.key !== key).forEach(rf => {
2522 let staleKey = rf.key;
2523 let existingFetcher = state.fetchers.get(staleKey);
2524 let revalidatingFetcher = getLoadingFetcher(undefined, existingFetcher ? existingFetcher.data : undefined);
2525 state.fetchers.set(staleKey, revalidatingFetcher);
2526 if (fetchControllers.has(staleKey)) {
2527 abortFetcher(staleKey);
2528 }
2529 if (rf.controller) {
2530 fetchControllers.set(staleKey, rf.controller);
2531 }
2532 });
2533 updateState({
2534 fetchers: new Map(state.fetchers)
2535 });
2536 let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(rf => abortFetcher(rf.key));
2537 abortController.signal.addEventListener("abort", abortPendingFetchRevalidations);
2538 let {
2539 loaderResults,
2540 fetcherResults
2541 } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
2542 if (abortController.signal.aborted) {
2543 return;
2544 }
2545 abortController.signal.removeEventListener("abort", abortPendingFetchRevalidations);
2546 fetchReloadIds.delete(key);
2547 fetchControllers.delete(key);
2548 revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2549 let redirect = findRedirect([...loaderResults, ...fetcherResults]);
2550 if (redirect) {
2551 if (redirect.idx >= matchesToLoad.length) {
2552 // If this redirect came from a fetcher make sure we mark it in
2553 // fetchRedirectIds so it doesn't get revalidated on the next set of
2554 // loader executions
2555 let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
2556 fetchRedirectIds.add(fetcherKey);
2557 }
2558 return startRedirectNavigation(revalidationRequest, redirect.result);
2559 }
2560 // Process and commit output from loaders
2561 let {
2562 loaderData,
2563 errors
2564 } = processLoaderData(state, state.matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
2565 // Since we let revalidations complete even if the submitting fetcher was
2566 // deleted, only put it back to idle if it hasn't been deleted
2567 if (state.fetchers.has(key)) {
2568 let doneFetcher = getDoneFetcher(actionResult.data);
2569 state.fetchers.set(key, doneFetcher);
2570 }
2571 abortStaleFetchLoads(loadId);
2572 // If we are currently in a navigation loading state and this fetcher is
2573 // more recent than the navigation, we want the newer data so abort the
2574 // navigation and complete it with the fetcher data
2575 if (state.navigation.state === "loading" && loadId > pendingNavigationLoadId) {
2576 invariant(pendingAction, "Expected pending action");
2577 pendingNavigationController && pendingNavigationController.abort();
2578 completeNavigation(state.navigation.location, {
2579 matches,
2580 loaderData,
2581 errors,
2582 fetchers: new Map(state.fetchers)
2583 });
2584 } else {
2585 // otherwise just update with the fetcher data, preserving any existing
2586 // loaderData for loaders that did not need to reload. We have to
2587 // manually merge here since we aren't going through completeNavigation
2588 updateState({
2589 errors,
2590 loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors),
2591 fetchers: new Map(state.fetchers)
2592 });
2593 isRevalidationRequired = false;
2594 }
2595 }
2596 // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
2597 async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, submission) {
2598 let existingFetcher = state.fetchers.get(key);
2599 updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
2600 flushSync
2601 });
2602 let abortController = new AbortController();
2603 let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2604 if (isFogOfWar) {
2605 let discoverResult = await discoverRoutes(matches, path, fetchRequest.signal);
2606 if (discoverResult.type === "aborted") {
2607 return;
2608 } else if (discoverResult.type === "error") {
2609 let {
2610 error
2611 } = handleDiscoverRouteError(path, discoverResult);
2612 setFetcherError(key, routeId, error, {
2613 flushSync
2614 });
2615 return;
2616 } else if (!discoverResult.matches) {
2617 setFetcherError(key, routeId, getInternalRouterError(404, {
2618 pathname: path
2619 }), {
2620 flushSync
2621 });
2622 return;
2623 } else {
2624 matches = discoverResult.matches;
2625 match = getTargetMatch(matches, path);
2626 }
2627 }
2628 // Call the loader for this fetcher route match
2629 fetchControllers.set(key, abortController);
2630 let originatingLoadId = incrementingLoadId;
2631 let results = await callDataStrategy("loader", fetchRequest, [match], matches);
2632 let result = results[0];
2633 // Deferred isn't supported for fetcher loads, await everything and treat it
2634 // as a normal load. resolveDeferredData will return undefined if this
2635 // fetcher gets aborted, so we just leave result untouched and short circuit
2636 // below if that happens
2637 if (isDeferredResult(result)) {
2638 result = (await resolveDeferredData(result, fetchRequest.signal, true)) || result;
2639 }
2640 // We can delete this so long as we weren't aborted by our our own fetcher
2641 // re-load which would have put _new_ controller is in fetchControllers
2642 if (fetchControllers.get(key) === abortController) {
2643 fetchControllers.delete(key);
2644 }
2645 if (fetchRequest.signal.aborted) {
2646 return;
2647 }
2648 // We don't want errors bubbling up or redirects followed for unmounted
2649 // fetchers, so short circuit here if it was removed from the UI
2650 if (deletedFetchers.has(key)) {
2651 updateFetcherState(key, getDoneFetcher(undefined));
2652 return;
2653 }
2654 // If the loader threw a redirect Response, start a new REPLACE navigation
2655 if (isRedirectResult(result)) {
2656 if (pendingNavigationLoadId > originatingLoadId) {
2657 // A new navigation was kicked off after our loader started, so that
2658 // should take precedence over this redirect navigation
2659 updateFetcherState(key, getDoneFetcher(undefined));
2660 return;
2661 } else {
2662 fetchRedirectIds.add(key);
2663 await startRedirectNavigation(fetchRequest, result);
2664 return;
2665 }
2666 }
2667 // Process any non-redirect errors thrown
2668 if (isErrorResult(result)) {
2669 setFetcherError(key, routeId, result.error);
2670 return;
2671 }
2672 invariant(!isDeferredResult(result), "Unhandled fetcher deferred data");
2673 // Put the fetcher back into an idle state
2674 updateFetcherState(key, getDoneFetcher(result.data));
2675 }
2676 /**
2677 * Utility function to handle redirects returned from an action or loader.
2678 * Normally, a redirect "replaces" the navigation that triggered it. So, for
2679 * example:
2680 *
2681 * - user is on /a
2682 * - user clicks a link to /b
2683 * - loader for /b redirects to /c
2684 *
2685 * In a non-JS app the browser would track the in-flight navigation to /b and
2686 * then replace it with /c when it encountered the redirect response. In
2687 * the end it would only ever update the URL bar with /c.
2688 *
2689 * In client-side routing using pushState/replaceState, we aim to emulate
2690 * this behavior and we also do not update history until the end of the
2691 * navigation (including processed redirects). This means that we never
2692 * actually touch history until we've processed redirects, so we just use
2693 * the history action from the original navigation (PUSH or REPLACE).
2694 */
2695 async function startRedirectNavigation(request, redirect, _temp2) {
2696 let {
2697 submission,
2698 fetcherSubmission,
2699 replace
2700 } = _temp2 === void 0 ? {} : _temp2;
2701 if (redirect.response.headers.has("X-Remix-Revalidate")) {
2702 isRevalidationRequired = true;
2703 }
2704 let location = redirect.response.headers.get("Location");
2705 invariant(location, "Expected a Location header on the redirect Response");
2706 location = normalizeRedirectLocation(location, new URL(request.url), basename);
2707 let redirectLocation = createLocation(state.location, location, {
2708 _isRedirect: true
2709 });
2710 if (isBrowser) {
2711 let isDocumentReload = false;
2712 if (redirect.response.headers.has("X-Remix-Reload-Document")) {
2713 // Hard reload if the response contained X-Remix-Reload-Document
2714 isDocumentReload = true;
2715 } else if (ABSOLUTE_URL_REGEX.test(location)) {
2716 const url = init.history.createURL(location);
2717 isDocumentReload =
2718 // Hard reload if it's an absolute URL to a new origin
2719 url.origin !== routerWindow.location.origin ||
2720 // Hard reload if it's an absolute URL that does not match our basename
2721 stripBasename(url.pathname, basename) == null;
2722 }
2723 if (isDocumentReload) {
2724 if (replace) {
2725 routerWindow.location.replace(location);
2726 } else {
2727 routerWindow.location.assign(location);
2728 }
2729 return;
2730 }
2731 }
2732 // There's no need to abort on redirects, since we don't detect the
2733 // redirect until the action/loaders have settled
2734 pendingNavigationController = null;
2735 let redirectHistoryAction = replace === true || redirect.response.headers.has("X-Remix-Replace") ? Action.Replace : Action.Push;
2736 // Use the incoming submission if provided, fallback on the active one in
2737 // state.navigation
2738 let {
2739 formMethod,
2740 formAction,
2741 formEncType
2742 } = state.navigation;
2743 if (!submission && !fetcherSubmission && formMethod && formAction && formEncType) {
2744 submission = getSubmissionFromNavigation(state.navigation);
2745 }
2746 // If this was a 307/308 submission we want to preserve the HTTP method and
2747 // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
2748 // redirected location
2749 let activeSubmission = submission || fetcherSubmission;
2750 if (redirectPreserveMethodStatusCodes.has(redirect.response.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
2751 await startNavigation(redirectHistoryAction, redirectLocation, {
2752 submission: _extends({}, activeSubmission, {
2753 formAction: location
2754 }),
2755 // Preserve this flag across redirects
2756 preventScrollReset: pendingPreventScrollReset
2757 });
2758 } else {
2759 // If we have a navigation submission, we will preserve it through the
2760 // redirect navigation
2761 let overrideNavigation = getLoadingNavigation(redirectLocation, submission);
2762 await startNavigation(redirectHistoryAction, redirectLocation, {
2763 overrideNavigation,
2764 // Send fetcher submissions through for shouldRevalidate
2765 fetcherSubmission,
2766 // Preserve this flag across redirects
2767 preventScrollReset: pendingPreventScrollReset
2768 });
2769 }
2770 }
2771 // Utility wrapper for calling dataStrategy client-side without having to
2772 // pass around the manifest, mapRouteProperties, etc.
2773 async function callDataStrategy(type, request, matchesToLoad, matches) {
2774 try {
2775 let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties);
2776 return await Promise.all(results.map((result, i) => {
2777 if (isRedirectHandlerResult(result)) {
2778 let response = result.result;
2779 return {
2780 type: ResultType.redirect,
2781 response: normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath)
2782 };
2783 }
2784 return convertHandlerResultToDataResult(result);
2785 }));
2786 } catch (e) {
2787 // If the outer dataStrategy method throws, just return the error for all
2788 // matches - and it'll naturally bubble to the root
2789 return matchesToLoad.map(() => ({
2790 type: ResultType.error,
2791 error: e
2792 }));
2793 }
2794 }
2795 async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
2796 let [loaderResults, ...fetcherResults] = await Promise.all([matchesToLoad.length ? callDataStrategy("loader", request, matchesToLoad, matches) : [], ...fetchersToLoad.map(f => {
2797 if (f.matches && f.match && f.controller) {
2798 let fetcherRequest = createClientSideRequest(init.history, f.path, f.controller.signal);
2799 return callDataStrategy("loader", fetcherRequest, [f.match], f.matches).then(r => r[0]);
2800 } else {
2801 return Promise.resolve({
2802 type: ResultType.error,
2803 error: getInternalRouterError(404, {
2804 pathname: f.path
2805 })
2806 });
2807 }
2808 })]);
2809 await Promise.all([resolveDeferredResults(currentMatches, matchesToLoad, loaderResults, loaderResults.map(() => request.signal), false, state.loaderData), resolveDeferredResults(currentMatches, fetchersToLoad.map(f => f.match), fetcherResults, fetchersToLoad.map(f => f.controller ? f.controller.signal : null), true)]);
2810 return {
2811 loaderResults,
2812 fetcherResults
2813 };
2814 }
2815 function interruptActiveLoads() {
2816 // Every interruption triggers a revalidation
2817 isRevalidationRequired = true;
2818 // Cancel pending route-level deferreds and mark cancelled routes for
2819 // revalidation
2820 cancelledDeferredRoutes.push(...cancelActiveDeferreds());
2821 // Abort in-flight fetcher loads
2822 fetchLoadMatches.forEach((_, key) => {
2823 if (fetchControllers.has(key)) {
2824 cancelledFetcherLoads.add(key);
2825 abortFetcher(key);
2826 }
2827 });
2828 }
2829 function updateFetcherState(key, fetcher, opts) {
2830 if (opts === void 0) {
2831 opts = {};
2832 }
2833 state.fetchers.set(key, fetcher);
2834 updateState({
2835 fetchers: new Map(state.fetchers)
2836 }, {
2837 flushSync: (opts && opts.flushSync) === true
2838 });
2839 }
2840 function setFetcherError(key, routeId, error, opts) {
2841 if (opts === void 0) {
2842 opts = {};
2843 }
2844 let boundaryMatch = findNearestBoundary(state.matches, routeId);
2845 deleteFetcher(key);
2846 updateState({
2847 errors: {
2848 [boundaryMatch.route.id]: error
2849 },
2850 fetchers: new Map(state.fetchers)
2851 }, {
2852 flushSync: (opts && opts.flushSync) === true
2853 });
2854 }
2855 function getFetcher(key) {
2856 if (future.v7_fetcherPersist) {
2857 activeFetchers.set(key, (activeFetchers.get(key) || 0) + 1);
2858 // If this fetcher was previously marked for deletion, unmark it since we
2859 // have a new instance
2860 if (deletedFetchers.has(key)) {
2861 deletedFetchers.delete(key);
2862 }
2863 }
2864 return state.fetchers.get(key) || IDLE_FETCHER;
2865 }
2866 function deleteFetcher(key) {
2867 let fetcher = state.fetchers.get(key);
2868 // Don't abort the controller if this is a deletion of a fetcher.submit()
2869 // in it's loading phase since - we don't want to abort the corresponding
2870 // revalidation and want them to complete and land
2871 if (fetchControllers.has(key) && !(fetcher && fetcher.state === "loading" && fetchReloadIds.has(key))) {
2872 abortFetcher(key);
2873 }
2874 fetchLoadMatches.delete(key);
2875 fetchReloadIds.delete(key);
2876 fetchRedirectIds.delete(key);
2877 deletedFetchers.delete(key);
2878 cancelledFetcherLoads.delete(key);
2879 state.fetchers.delete(key);
2880 }
2881 function deleteFetcherAndUpdateState(key) {
2882 if (future.v7_fetcherPersist) {
2883 let count = (activeFetchers.get(key) || 0) - 1;
2884 if (count <= 0) {
2885 activeFetchers.delete(key);
2886 deletedFetchers.add(key);
2887 } else {
2888 activeFetchers.set(key, count);
2889 }
2890 } else {
2891 deleteFetcher(key);
2892 }
2893 updateState({
2894 fetchers: new Map(state.fetchers)
2895 });
2896 }
2897 function abortFetcher(key) {
2898 let controller = fetchControllers.get(key);
2899 invariant(controller, "Expected fetch controller: " + key);
2900 controller.abort();
2901 fetchControllers.delete(key);
2902 }
2903 function markFetchersDone(keys) {
2904 for (let key of keys) {
2905 let fetcher = getFetcher(key);
2906 let doneFetcher = getDoneFetcher(fetcher.data);
2907 state.fetchers.set(key, doneFetcher);
2908 }
2909 }
2910 function markFetchRedirectsDone() {
2911 let doneKeys = [];
2912 let updatedFetchers = false;
2913 for (let key of fetchRedirectIds) {
2914 let fetcher = state.fetchers.get(key);
2915 invariant(fetcher, "Expected fetcher: " + key);
2916 if (fetcher.state === "loading") {
2917 fetchRedirectIds.delete(key);
2918 doneKeys.push(key);
2919 updatedFetchers = true;
2920 }
2921 }
2922 markFetchersDone(doneKeys);
2923 return updatedFetchers;
2924 }
2925 function abortStaleFetchLoads(landedId) {
2926 let yeetedKeys = [];
2927 for (let [key, id] of fetchReloadIds) {
2928 if (id < landedId) {
2929 let fetcher = state.fetchers.get(key);
2930 invariant(fetcher, "Expected fetcher: " + key);
2931 if (fetcher.state === "loading") {
2932 abortFetcher(key);
2933 fetchReloadIds.delete(key);
2934 yeetedKeys.push(key);
2935 }
2936 }
2937 }
2938 markFetchersDone(yeetedKeys);
2939 return yeetedKeys.length > 0;
2940 }
2941 function getBlocker(key, fn) {
2942 let blocker = state.blockers.get(key) || IDLE_BLOCKER;
2943 if (blockerFunctions.get(key) !== fn) {
2944 blockerFunctions.set(key, fn);
2945 }
2946 return blocker;
2947 }
2948 function deleteBlocker(key) {
2949 state.blockers.delete(key);
2950 blockerFunctions.delete(key);
2951 }
2952 // Utility function to update blockers, ensuring valid state transitions
2953 function updateBlocker(key, newBlocker) {
2954 let blocker = state.blockers.get(key) || IDLE_BLOCKER;
2955 // Poor mans state machine :)
2956 // https://mermaid.live/edit#pako:eNqVkc9OwzAMxl8l8nnjAYrEtDIOHEBIgwvKJTReGy3_lDpIqO27k6awMG0XcrLlnz87nwdonESogKXXBuE79rq75XZO3-yHds0RJVuv70YrPlUrCEe2HfrORS3rubqZfuhtpg5C9wk5tZ4VKcRUq88q9Z8RS0-48cE1iHJkL0ugbHuFLus9L6spZy8nX9MP2CNdomVaposqu3fGayT8T8-jJQwhepo_UtpgBQaDEUom04dZhAN1aJBDlUKJBxE1ceB2Smj0Mln-IBW5AFU2dwUiktt_2Qaq2dBfaKdEup85UV7Yd-dKjlnkabl2Pvr0DTkTreM
2957 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);
2958 let blockers = new Map(state.blockers);
2959 blockers.set(key, newBlocker);
2960 updateState({
2961 blockers
2962 });
2963 }
2964 function shouldBlockNavigation(_ref4) {
2965 let {
2966 currentLocation,
2967 nextLocation,
2968 historyAction
2969 } = _ref4;
2970 if (blockerFunctions.size === 0) {
2971 return;
2972 }
2973 // We ony support a single active blocker at the moment since we don't have
2974 // any compelling use cases for multi-blocker yet
2975 if (blockerFunctions.size > 1) {
2976 warning(false, "A router only supports one blocker at a time");
2977 }
2978 let entries = Array.from(blockerFunctions.entries());
2979 let [blockerKey, blockerFunction] = entries[entries.length - 1];
2980 let blocker = state.blockers.get(blockerKey);
2981 if (blocker && blocker.state === "proceeding") {
2982 // If the blocker is currently proceeding, we don't need to re-check
2983 // it and can let this navigation continue
2984 return;
2985 }
2986 // At this point, we know we're unblocked/blocked so we need to check the
2987 // user-provided blocker function
2988 if (blockerFunction({
2989 currentLocation,
2990 nextLocation,
2991 historyAction
2992 })) {
2993 return blockerKey;
2994 }
2995 }
2996 function handleNavigational404(pathname) {
2997 let error = getInternalRouterError(404, {
2998 pathname
2999 });
3000 let routesToUse = inFlightDataRoutes || dataRoutes;
3001 let {
3002 matches,
3003 route
3004 } = getShortCircuitMatches(routesToUse);
3005 // Cancel all pending deferred on 404s since we don't keep any routes
3006 cancelActiveDeferreds();
3007 return {
3008 notFoundMatches: matches,
3009 route,
3010 error
3011 };
3012 }
3013 function handleDiscoverRouteError(pathname, discoverResult) {
3014 return {
3015 boundaryId: findNearestBoundary(discoverResult.partialMatches).route.id,
3016 error: getInternalRouterError(400, {
3017 type: "route-discovery",
3018 pathname,
3019 message: discoverResult.error != null && "message" in discoverResult.error ? discoverResult.error : String(discoverResult.error)
3020 })
3021 };
3022 }
3023 function cancelActiveDeferreds(predicate) {
3024 let cancelledRouteIds = [];
3025 activeDeferreds.forEach((dfd, routeId) => {
3026 if (!predicate || predicate(routeId)) {
3027 // Cancel the deferred - but do not remove from activeDeferreds here -
3028 // we rely on the subscribers to do that so our tests can assert proper
3029 // cleanup via _internalActiveDeferreds
3030 dfd.cancel();
3031 cancelledRouteIds.push(routeId);
3032 activeDeferreds.delete(routeId);
3033 }
3034 });
3035 return cancelledRouteIds;
3036 }
3037 // Opt in to capturing and reporting scroll positions during navigations,
3038 // used by the <ScrollRestoration> component
3039 function enableScrollRestoration(positions, getPosition, getKey) {
3040 savedScrollPositions = positions;
3041 getScrollPosition = getPosition;
3042 getScrollRestorationKey = getKey || null;
3043 // Perform initial hydration scroll restoration, since we miss the boat on
3044 // the initial updateState() because we've not yet rendered <ScrollRestoration/>
3045 // and therefore have no savedScrollPositions available
3046 if (!initialScrollRestored && state.navigation === IDLE_NAVIGATION) {
3047 initialScrollRestored = true;
3048 let y = getSavedScrollPosition(state.location, state.matches);
3049 if (y != null) {
3050 updateState({
3051 restoreScrollPosition: y
3052 });
3053 }
3054 }
3055 return () => {
3056 savedScrollPositions = null;
3057 getScrollPosition = null;
3058 getScrollRestorationKey = null;
3059 };
3060 }
3061 function getScrollKey(location, matches) {
3062 if (getScrollRestorationKey) {
3063 let key = getScrollRestorationKey(location, matches.map(m => convertRouteMatchToUiMatch(m, state.loaderData)));
3064 return key || location.key;
3065 }
3066 return location.key;
3067 }
3068 function saveScrollPosition(location, matches) {
3069 if (savedScrollPositions && getScrollPosition) {
3070 let key = getScrollKey(location, matches);
3071 savedScrollPositions[key] = getScrollPosition();
3072 }
3073 }
3074 function getSavedScrollPosition(location, matches) {
3075 if (savedScrollPositions) {
3076 let key = getScrollKey(location, matches);
3077 let y = savedScrollPositions[key];
3078 if (typeof y === "number") {
3079 return y;
3080 }
3081 }
3082 return null;
3083 }
3084 function checkFogOfWar(matches, routesToUse, pathname) {
3085 if (patchRoutesOnMissImpl) {
3086 if (!matches) {
3087 let fogMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
3088 return {
3089 active: true,
3090 matches: fogMatches || []
3091 };
3092 } else {
3093 let leafRoute = matches[matches.length - 1].route;
3094 if (leafRoute.path && (leafRoute.path === "*" || leafRoute.path.endsWith("/*"))) {
3095 // If we matched a splat, it might only be because we haven't yet fetched
3096 // the children that would match with a higher score, so let's fetch
3097 // around and find out
3098 let partialMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
3099 return {
3100 active: true,
3101 matches: partialMatches
3102 };
3103 }
3104 }
3105 }
3106 return {
3107 active: false,
3108 matches: null
3109 };
3110 }
3111 async function discoverRoutes(matches, pathname, signal) {
3112 let partialMatches = matches;
3113 let route = partialMatches.length > 0 ? partialMatches[partialMatches.length - 1].route : null;
3114 while (true) {
3115 let isNonHMR = inFlightDataRoutes == null;
3116 let routesToUse = inFlightDataRoutes || dataRoutes;
3117 try {
3118 await loadLazyRouteChildren(patchRoutesOnMissImpl, pathname, partialMatches, routesToUse, manifest, mapRouteProperties, pendingPatchRoutes, signal);
3119 } catch (e) {
3120 return {
3121 type: "error",
3122 error: e,
3123 partialMatches
3124 };
3125 } finally {
3126 // If we are not in the middle of an HMR revalidation and we changed the
3127 // routes, provide a new identity so when we `updateState` at the end of
3128 // this navigation/fetch `router.routes` will be a new identity and
3129 // trigger a re-run of memoized `router.routes` dependencies.
3130 // HMR will already update the identity and reflow when it lands
3131 // `inFlightDataRoutes` in `completeNavigation`
3132 if (isNonHMR) {
3133 dataRoutes = [...dataRoutes];
3134 }
3135 }
3136 if (signal.aborted) {
3137 return {
3138 type: "aborted"
3139 };
3140 }
3141 let newMatches = matchRoutes(routesToUse, pathname, basename);
3142 let matchedSplat = false;
3143 if (newMatches) {
3144 let leafRoute = newMatches[newMatches.length - 1].route;
3145 if (leafRoute.index) {
3146 // If we found an index route, we can stop
3147 return {
3148 type: "success",
3149 matches: newMatches
3150 };
3151 }
3152 if (leafRoute.path && leafRoute.path.length > 0) {
3153 if (leafRoute.path === "*") {
3154 // If we found a splat route, we can't be sure there's not a
3155 // higher-scoring route down some partial matches trail so we need
3156 // to check that out
3157 matchedSplat = true;
3158 } else {
3159 // If we found a non-splat route, we can stop
3160 return {
3161 type: "success",
3162 matches: newMatches
3163 };
3164 }
3165 }
3166 }
3167 let newPartialMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
3168 // If we are no longer partially matching anything, this was either a
3169 // legit splat match above, or it's a 404. Also avoid loops if the
3170 // second pass results in the same partial matches
3171 if (!newPartialMatches || partialMatches.map(m => m.route.id).join("-") === newPartialMatches.map(m => m.route.id).join("-")) {
3172 return {
3173 type: "success",
3174 matches: matchedSplat ? newMatches : null
3175 };
3176 }
3177 partialMatches = newPartialMatches;
3178 route = partialMatches[partialMatches.length - 1].route;
3179 if (route.path === "*") {
3180 // The splat is still our most accurate partial, so run with it
3181 return {
3182 type: "success",
3183 matches: partialMatches
3184 };
3185 }
3186 }
3187 }
3188 function _internalSetRoutes(newRoutes) {
3189 manifest = {};
3190 inFlightDataRoutes = convertRoutesToDataRoutes(newRoutes, mapRouteProperties, undefined, manifest);
3191 }
3192 function patchRoutes(routeId, children) {
3193 let isNonHMR = inFlightDataRoutes == null;
3194 let routesToUse = inFlightDataRoutes || dataRoutes;
3195 patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties);
3196 // If we are not in the middle of an HMR revalidation and we changed the
3197 // routes, provide a new identity and trigger a reflow via `updateState`
3198 // to re-run memoized `router.routes` dependencies.
3199 // HMR will already update the identity and reflow when it lands
3200 // `inFlightDataRoutes` in `completeNavigation`
3201 if (isNonHMR) {
3202 dataRoutes = [...dataRoutes];
3203 updateState({});
3204 }
3205 }
3206 router = {
3207 get basename() {
3208 return basename;
3209 },
3210 get future() {
3211 return future;
3212 },
3213 get state() {
3214 return state;
3215 },
3216 get routes() {
3217 return dataRoutes;
3218 },
3219 get window() {
3220 return routerWindow;
3221 },
3222 initialize,
3223 subscribe,
3224 enableScrollRestoration,
3225 navigate,
3226 fetch,
3227 revalidate,
3228 // Passthrough to history-aware createHref used by useHref so we get proper
3229 // hash-aware URLs in DOM paths
3230 createHref: to => init.history.createHref(to),
3231 encodeLocation: to => init.history.encodeLocation(to),
3232 getFetcher,
3233 deleteFetcher: deleteFetcherAndUpdateState,
3234 dispose,
3235 getBlocker,
3236 deleteBlocker,
3237 patchRoutes,
3238 _internalFetchControllers: fetchControllers,
3239 _internalActiveDeferreds: activeDeferreds,
3240 // TODO: Remove setRoutes, it's temporary to avoid dealing with
3241 // updating the tree while validating the update algorithm.
3242 _internalSetRoutes
3243 };
3244 return router;
3245}
3246//#endregion
3247////////////////////////////////////////////////////////////////////////////////
3248//#region createStaticHandler
3249////////////////////////////////////////////////////////////////////////////////
3250const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
3251function createStaticHandler(routes, opts) {
3252 invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
3253 let manifest = {};
3254 let basename = (opts ? opts.basename : null) || "/";
3255 let mapRouteProperties;
3256 if (opts != null && opts.mapRouteProperties) {
3257 mapRouteProperties = opts.mapRouteProperties;
3258 } else if (opts != null && opts.detectErrorBoundary) {
3259 // If they are still using the deprecated version, wrap it with the new API
3260 let detectErrorBoundary = opts.detectErrorBoundary;
3261 mapRouteProperties = route => ({
3262 hasErrorBoundary: detectErrorBoundary(route)
3263 });
3264 } else {
3265 mapRouteProperties = defaultMapRouteProperties;
3266 }
3267 // Config driven behavior flags
3268 let future = _extends({
3269 v7_relativeSplatPath: false,
3270 v7_throwAbortReason: false
3271 }, opts ? opts.future : null);
3272 let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
3273 /**
3274 * The query() method is intended for document requests, in which we want to
3275 * call an optional action and potentially multiple loaders for all nested
3276 * routes. It returns a StaticHandlerContext object, which is very similar
3277 * to the router state (location, loaderData, actionData, errors, etc.) and
3278 * also adds SSR-specific information such as the statusCode and headers
3279 * from action/loaders Responses.
3280 *
3281 * It _should_ never throw and should report all errors through the
3282 * returned context.errors object, properly associating errors to their error
3283 * boundary. Additionally, it tracks _deepestRenderedBoundaryId which can be
3284 * used to emulate React error boundaries during SSr by performing a second
3285 * pass only down to the boundaryId.
3286 *
3287 * The one exception where we do not return a StaticHandlerContext is when a
3288 * redirect response is returned or thrown from any action/loader. We
3289 * propagate that out and return the raw Response so the HTTP server can
3290 * return it directly.
3291 *
3292 * - `opts.requestContext` is an optional server context that will be passed
3293 * to actions/loaders in the `context` parameter
3294 * - `opts.skipLoaderErrorBubbling` is an optional parameter that will prevent
3295 * the bubbling of errors which allows single-fetch-type implementations
3296 * where the client will handle the bubbling and we may need to return data
3297 * for the handling route
3298 */
3299 async function query(request, _temp3) {
3300 let {
3301 requestContext,
3302 skipLoaderErrorBubbling,
3303 unstable_dataStrategy
3304 } = _temp3 === void 0 ? {} : _temp3;
3305 let url = new URL(request.url);
3306 let method = request.method;
3307 let location = createLocation("", createPath(url), null, "default");
3308 let matches = matchRoutes(dataRoutes, location, basename);
3309 // SSR supports HEAD requests while SPA doesn't
3310 if (!isValidMethod(method) && method !== "HEAD") {
3311 let error = getInternalRouterError(405, {
3312 method
3313 });
3314 let {
3315 matches: methodNotAllowedMatches,
3316 route
3317 } = getShortCircuitMatches(dataRoutes);
3318 return {
3319 basename,
3320 location,
3321 matches: methodNotAllowedMatches,
3322 loaderData: {},
3323 actionData: null,
3324 errors: {
3325 [route.id]: error
3326 },
3327 statusCode: error.status,
3328 loaderHeaders: {},
3329 actionHeaders: {},
3330 activeDeferreds: null
3331 };
3332 } else if (!matches) {
3333 let error = getInternalRouterError(404, {
3334 pathname: location.pathname
3335 });
3336 let {
3337 matches: notFoundMatches,
3338 route
3339 } = getShortCircuitMatches(dataRoutes);
3340 return {
3341 basename,
3342 location,
3343 matches: notFoundMatches,
3344 loaderData: {},
3345 actionData: null,
3346 errors: {
3347 [route.id]: error
3348 },
3349 statusCode: error.status,
3350 loaderHeaders: {},
3351 actionHeaders: {},
3352 activeDeferreds: null
3353 };
3354 }
3355 let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, skipLoaderErrorBubbling === true, null);
3356 if (isResponse(result)) {
3357 return result;
3358 }
3359 // When returning StaticHandlerContext, we patch back in the location here
3360 // since we need it for React Context. But this helps keep our submit and
3361 // loadRouteData operating on a Request instead of a Location
3362 return _extends({
3363 location,
3364 basename
3365 }, result);
3366 }
3367 /**
3368 * The queryRoute() method is intended for targeted route requests, either
3369 * for fetch ?_data requests or resource route requests. In this case, we
3370 * are only ever calling a single action or loader, and we are returning the
3371 * returned value directly. In most cases, this will be a Response returned
3372 * from the action/loader, but it may be a primitive or other value as well -
3373 * and in such cases the calling context should handle that accordingly.
3374 *
3375 * We do respect the throw/return differentiation, so if an action/loader
3376 * throws, then this method will throw the value. This is important so we
3377 * can do proper boundary identification in Remix where a thrown Response
3378 * must go to the Catch Boundary but a returned Response is happy-path.
3379 *
3380 * One thing to note is that any Router-initiated Errors that make sense
3381 * to associate with a status code will be thrown as an ErrorResponse
3382 * instance which include the raw Error, such that the calling context can
3383 * serialize the error as they see fit while including the proper response
3384 * code. Examples here are 404 and 405 errors that occur prior to reaching
3385 * any user-defined loaders.
3386 *
3387 * - `opts.routeId` allows you to specify the specific route handler to call.
3388 * If not provided the handler will determine the proper route by matching
3389 * against `request.url`
3390 * - `opts.requestContext` is an optional server context that will be passed
3391 * to actions/loaders in the `context` parameter
3392 */
3393 async function queryRoute(request, _temp4) {
3394 let {
3395 routeId,
3396 requestContext,
3397 unstable_dataStrategy
3398 } = _temp4 === void 0 ? {} : _temp4;
3399 let url = new URL(request.url);
3400 let method = request.method;
3401 let location = createLocation("", createPath(url), null, "default");
3402 let matches = matchRoutes(dataRoutes, location, basename);
3403 // SSR supports HEAD requests while SPA doesn't
3404 if (!isValidMethod(method) && method !== "HEAD" && method !== "OPTIONS") {
3405 throw getInternalRouterError(405, {
3406 method
3407 });
3408 } else if (!matches) {
3409 throw getInternalRouterError(404, {
3410 pathname: location.pathname
3411 });
3412 }
3413 let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);
3414 if (routeId && !match) {
3415 throw getInternalRouterError(403, {
3416 pathname: location.pathname,
3417 routeId
3418 });
3419 } else if (!match) {
3420 // This should never hit I don't think?
3421 throw getInternalRouterError(404, {
3422 pathname: location.pathname
3423 });
3424 }
3425 let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, false, match);
3426 if (isResponse(result)) {
3427 return result;
3428 }
3429 let error = result.errors ? Object.values(result.errors)[0] : undefined;
3430 if (error !== undefined) {
3431 // If we got back result.errors, that means the loader/action threw
3432 // _something_ that wasn't a Response, but it's not guaranteed/required
3433 // to be an `instanceof Error` either, so we have to use throw here to
3434 // preserve the "error" state outside of queryImpl.
3435 throw error;
3436 }
3437 // Pick off the right state value to return
3438 if (result.actionData) {
3439 return Object.values(result.actionData)[0];
3440 }
3441 if (result.loaderData) {
3442 var _result$activeDeferre;
3443 let data = Object.values(result.loaderData)[0];
3444 if ((_result$activeDeferre = result.activeDeferreds) != null && _result$activeDeferre[match.route.id]) {
3445 data[UNSAFE_DEFERRED_SYMBOL] = result.activeDeferreds[match.route.id];
3446 }
3447 return data;
3448 }
3449 return undefined;
3450 }
3451 async function queryImpl(request, location, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch) {
3452 invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
3453 try {
3454 if (isMutationMethod(request.method.toLowerCase())) {
3455 let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch != null);
3456 return result;
3457 }
3458 let result = await loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch);
3459 return isResponse(result) ? result : _extends({}, result, {
3460 actionData: null,
3461 actionHeaders: {}
3462 });
3463 } catch (e) {
3464 // If the user threw/returned a Response in callLoaderOrAction for a
3465 // `queryRoute` call, we throw the `HandlerResult` to bail out early
3466 // and then return or throw the raw Response here accordingly
3467 if (isHandlerResult(e) && isResponse(e.result)) {
3468 if (e.type === ResultType.error) {
3469 throw e.result;
3470 }
3471 return e.result;
3472 }
3473 // Redirects are always returned since they don't propagate to catch
3474 // boundaries
3475 if (isRedirectResponse(e)) {
3476 return e;
3477 }
3478 throw e;
3479 }
3480 }
3481 async function submit(request, matches, actionMatch, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, isRouteRequest) {
3482 let result;
3483 if (!actionMatch.route.action && !actionMatch.route.lazy) {
3484 let error = getInternalRouterError(405, {
3485 method: request.method,
3486 pathname: new URL(request.url).pathname,
3487 routeId: actionMatch.route.id
3488 });
3489 if (isRouteRequest) {
3490 throw error;
3491 }
3492 result = {
3493 type: ResultType.error,
3494 error
3495 };
3496 } else {
3497 let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, unstable_dataStrategy);
3498 result = results[0];
3499 if (request.signal.aborted) {
3500 throwStaticHandlerAbortedError(request, isRouteRequest, future);
3501 }
3502 }
3503 if (isRedirectResult(result)) {
3504 // Uhhhh - this should never happen, we should always throw these from
3505 // callLoaderOrAction, but the type narrowing here keeps TS happy and we
3506 // can get back on the "throw all redirect responses" train here should
3507 // this ever happen :/
3508 throw new Response(null, {
3509 status: result.response.status,
3510 headers: {
3511 Location: result.response.headers.get("Location")
3512 }
3513 });
3514 }
3515 if (isDeferredResult(result)) {
3516 let error = getInternalRouterError(400, {
3517 type: "defer-action"
3518 });
3519 if (isRouteRequest) {
3520 throw error;
3521 }
3522 result = {
3523 type: ResultType.error,
3524 error
3525 };
3526 }
3527 if (isRouteRequest) {
3528 // Note: This should only be non-Response values if we get here, since
3529 // isRouteRequest should throw any Response received in callLoaderOrAction
3530 if (isErrorResult(result)) {
3531 throw result.error;
3532 }
3533 return {
3534 matches: [actionMatch],
3535 loaderData: {},
3536 actionData: {
3537 [actionMatch.route.id]: result.data
3538 },
3539 errors: null,
3540 // Note: statusCode + headers are unused here since queryRoute will
3541 // return the raw Response or value
3542 statusCode: 200,
3543 loaderHeaders: {},
3544 actionHeaders: {},
3545 activeDeferreds: null
3546 };
3547 }
3548 // Create a GET request for the loaders
3549 let loaderRequest = new Request(request.url, {
3550 headers: request.headers,
3551 redirect: request.redirect,
3552 signal: request.signal
3553 });
3554 if (isErrorResult(result)) {
3555 // Store off the pending error - we use it to determine which loaders
3556 // to call and will commit it when we complete the navigation
3557 let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
3558 let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
3559 // action status codes take precedence over loader status codes
3560 return _extends({}, context, {
3561 statusCode: isRouteErrorResponse(result.error) ? result.error.status : result.statusCode != null ? result.statusCode : 500,
3562 actionData: null,
3563 actionHeaders: _extends({}, result.headers ? {
3564 [actionMatch.route.id]: result.headers
3565 } : {})
3566 });
3567 }
3568 let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null);
3569 return _extends({}, context, {
3570 actionData: {
3571 [actionMatch.route.id]: result.data
3572 }
3573 }, result.statusCode ? {
3574 statusCode: result.statusCode
3575 } : {}, {
3576 actionHeaders: result.headers ? {
3577 [actionMatch.route.id]: result.headers
3578 } : {}
3579 });
3580 }
3581 async function loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
3582 let isRouteRequest = routeMatch != null;
3583 // Short circuit if we have no loaders to run (queryRoute())
3584 if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
3585 throw getInternalRouterError(400, {
3586 method: request.method,
3587 pathname: new URL(request.url).pathname,
3588 routeId: routeMatch == null ? void 0 : routeMatch.route.id
3589 });
3590 }
3591 let requestMatches = routeMatch ? [routeMatch] : pendingActionResult && isErrorResult(pendingActionResult[1]) ? getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]) : matches;
3592 let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
3593 // Short circuit if we have no loaders to run (query())
3594 if (matchesToLoad.length === 0) {
3595 return {
3596 matches,
3597 // Add a null for all matched routes for proper revalidation on the client
3598 loaderData: matches.reduce((acc, m) => Object.assign(acc, {
3599 [m.route.id]: null
3600 }), {}),
3601 errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
3602 [pendingActionResult[0]]: pendingActionResult[1].error
3603 } : null,
3604 statusCode: 200,
3605 loaderHeaders: {},
3606 activeDeferreds: null
3607 };
3608 }
3609 let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy);
3610 if (request.signal.aborted) {
3611 throwStaticHandlerAbortedError(request, isRouteRequest, future);
3612 }
3613 // Process and commit output from loaders
3614 let activeDeferreds = new Map();
3615 let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling);
3616 // Add a null for any non-loader matches for proper revalidation on the client
3617 let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
3618 matches.forEach(match => {
3619 if (!executedLoaders.has(match.route.id)) {
3620 context.loaderData[match.route.id] = null;
3621 }
3622 });
3623 return _extends({}, context, {
3624 matches,
3625 activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
3626 });
3627 }
3628 // Utility wrapper for calling dataStrategy server-side without having to
3629 // pass around the manifest, mapRouteProperties, etc.
3630 async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy) {
3631 let results = await callDataStrategyImpl(unstable_dataStrategy || defaultDataStrategy, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext);
3632 return await Promise.all(results.map((result, i) => {
3633 if (isRedirectHandlerResult(result)) {
3634 let response = result.result;
3635 // Throw redirects and let the server handle them with an HTTP redirect
3636 throw normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
3637 }
3638 if (isResponse(result.result) && isRouteRequest) {
3639 // For SSR single-route requests, we want to hand Responses back
3640 // directly without unwrapping
3641 throw result;
3642 }
3643 return convertHandlerResultToDataResult(result);
3644 }));
3645 }
3646 return {
3647 dataRoutes,
3648 query,
3649 queryRoute
3650 };
3651}
3652//#endregion
3653////////////////////////////////////////////////////////////////////////////////
3654//#region Helpers
3655////////////////////////////////////////////////////////////////////////////////
3656/**
3657 * Given an existing StaticHandlerContext and an error thrown at render time,
3658 * provide an updated StaticHandlerContext suitable for a second SSR render
3659 */
3660function getStaticContextFromError(routes, context, error) {
3661 let newContext = _extends({}, context, {
3662 statusCode: isRouteErrorResponse(error) ? error.status : 500,
3663 errors: {
3664 [context._deepestRenderedBoundaryId || routes[0].id]: error
3665 }
3666 });
3667 return newContext;
3668}
3669function throwStaticHandlerAbortedError(request, isRouteRequest, future) {
3670 if (future.v7_throwAbortReason && request.signal.reason !== undefined) {
3671 throw request.signal.reason;
3672 }
3673 let method = isRouteRequest ? "queryRoute" : "query";
3674 throw new Error(method + "() call aborted: " + request.method + " " + request.url);
3675}
3676function isSubmissionNavigation(opts) {
3677 return opts != null && ("formData" in opts && opts.formData != null || "body" in opts && opts.body !== undefined);
3678}
3679function normalizeTo(location, matches, basename, prependBasename, to, v7_relativeSplatPath, fromRouteId, relative) {
3680 let contextualMatches;
3681 let activeRouteMatch;
3682 if (fromRouteId) {
3683 // Grab matches up to the calling route so our route-relative logic is
3684 // relative to the correct source route
3685 contextualMatches = [];
3686 for (let match of matches) {
3687 contextualMatches.push(match);
3688 if (match.route.id === fromRouteId) {
3689 activeRouteMatch = match;
3690 break;
3691 }
3692 }
3693 } else {
3694 contextualMatches = matches;
3695 activeRouteMatch = matches[matches.length - 1];
3696 }
3697 // Resolve the relative path
3698 let path = resolveTo(to ? to : ".", getResolveToMatches(contextualMatches, v7_relativeSplatPath), stripBasename(location.pathname, basename) || location.pathname, relative === "path");
3699 // When `to` is not specified we inherit search/hash from the current
3700 // location, unlike when to="." and we just inherit the path.
3701 // See https://github.com/remix-run/remix/issues/927
3702 if (to == null) {
3703 path.search = location.search;
3704 path.hash = location.hash;
3705 }
3706 // Add an ?index param for matched index routes if we don't already have one
3707 if ((to == null || to === "" || to === ".") && activeRouteMatch && activeRouteMatch.route.index && !hasNakedIndexQuery(path.search)) {
3708 path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
3709 }
3710 // If we're operating within a basename, prepend it to the pathname. If
3711 // this is a root navigation, then just use the raw basename which allows
3712 // the basename to have full control over the presence of a trailing slash
3713 // on root actions
3714 if (prependBasename && basename !== "/") {
3715 path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
3716 }
3717 return createPath(path);
3718}
3719// Normalize navigation options by converting formMethod=GET formData objects to
3720// URLSearchParams so they behave identically to links with query params
3721function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
3722 // Return location verbatim on non-submission navigations
3723 if (!opts || !isSubmissionNavigation(opts)) {
3724 return {
3725 path
3726 };
3727 }
3728 if (opts.formMethod && !isValidMethod(opts.formMethod)) {
3729 return {
3730 path,
3731 error: getInternalRouterError(405, {
3732 method: opts.formMethod
3733 })
3734 };
3735 }
3736 let getInvalidBodyError = () => ({
3737 path,
3738 error: getInternalRouterError(400, {
3739 type: "invalid-body"
3740 })
3741 });
3742 // Create a Submission on non-GET navigations
3743 let rawFormMethod = opts.formMethod || "get";
3744 let formMethod = normalizeFormMethod ? rawFormMethod.toUpperCase() : rawFormMethod.toLowerCase();
3745 let formAction = stripHashFromPath(path);
3746 if (opts.body !== undefined) {
3747 if (opts.formEncType === "text/plain") {
3748 // text only support POST/PUT/PATCH/DELETE submissions
3749 if (!isMutationMethod(formMethod)) {
3750 return getInvalidBodyError();
3751 }
3752 let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
3753 // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
3754 Array.from(opts.body.entries()).reduce((acc, _ref5) => {
3755 let [name, value] = _ref5;
3756 return "" + acc + name + "=" + value + "\n";
3757 }, "") : String(opts.body);
3758 return {
3759 path,
3760 submission: {
3761 formMethod,
3762 formAction,
3763 formEncType: opts.formEncType,
3764 formData: undefined,
3765 json: undefined,
3766 text
3767 }
3768 };
3769 } else if (opts.formEncType === "application/json") {
3770 // json only supports POST/PUT/PATCH/DELETE submissions
3771 if (!isMutationMethod(formMethod)) {
3772 return getInvalidBodyError();
3773 }
3774 try {
3775 let json = typeof opts.body === "string" ? JSON.parse(opts.body) : opts.body;
3776 return {
3777 path,
3778 submission: {
3779 formMethod,
3780 formAction,
3781 formEncType: opts.formEncType,
3782 formData: undefined,
3783 json,
3784 text: undefined
3785 }
3786 };
3787 } catch (e) {
3788 return getInvalidBodyError();
3789 }
3790 }
3791 }
3792 invariant(typeof FormData === "function", "FormData is not available in this environment");
3793 let searchParams;
3794 let formData;
3795 if (opts.formData) {
3796 searchParams = convertFormDataToSearchParams(opts.formData);
3797 formData = opts.formData;
3798 } else if (opts.body instanceof FormData) {
3799 searchParams = convertFormDataToSearchParams(opts.body);
3800 formData = opts.body;
3801 } else if (opts.body instanceof URLSearchParams) {
3802 searchParams = opts.body;
3803 formData = convertSearchParamsToFormData(searchParams);
3804 } else if (opts.body == null) {
3805 searchParams = new URLSearchParams();
3806 formData = new FormData();
3807 } else {
3808 try {
3809 searchParams = new URLSearchParams(opts.body);
3810 formData = convertSearchParamsToFormData(searchParams);
3811 } catch (e) {
3812 return getInvalidBodyError();
3813 }
3814 }
3815 let submission = {
3816 formMethod,
3817 formAction,
3818 formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
3819 formData,
3820 json: undefined,
3821 text: undefined
3822 };
3823 if (isMutationMethod(submission.formMethod)) {
3824 return {
3825 path,
3826 submission
3827 };
3828 }
3829 // Flatten submission onto URLSearchParams for GET submissions
3830 let parsedPath = parsePath(path);
3831 // On GET navigation submissions we can drop the ?index param from the
3832 // resulting location since all loaders will run. But fetcher GET submissions
3833 // only run a single loader so we need to preserve any incoming ?index params
3834 if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
3835 searchParams.append("index", "");
3836 }
3837 parsedPath.search = "?" + searchParams;
3838 return {
3839 path: createPath(parsedPath),
3840 submission
3841 };
3842}
3843// Filter out all routes below any caught error as they aren't going to
3844// render so we don't need to load them
3845function getLoaderMatchesUntilBoundary(matches, boundaryId) {
3846 let boundaryMatches = matches;
3847 if (boundaryId) {
3848 let index = matches.findIndex(m => m.route.id === boundaryId);
3849 if (index >= 0) {
3850 boundaryMatches = matches.slice(0, index);
3851 }
3852 }
3853 return boundaryMatches;
3854}
3855function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
3856 let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
3857 let currentUrl = history.createURL(state.location);
3858 let nextUrl = history.createURL(location);
3859 // Pick navigation matches that are net-new or qualify for revalidation
3860 let boundaryId = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[0] : undefined;
3861 let boundaryMatches = boundaryId ? getLoaderMatchesUntilBoundary(matches, boundaryId) : matches;
3862 // Don't revalidate loaders by default after action 4xx/5xx responses
3863 // when the flag is enabled. They can still opt-into revalidation via
3864 // `shouldRevalidate` via `actionResult`
3865 let actionStatus = pendingActionResult ? pendingActionResult[1].statusCode : undefined;
3866 let shouldSkipRevalidation = skipActionErrorRevalidation && actionStatus && actionStatus >= 400;
3867 let navigationMatches = boundaryMatches.filter((match, index) => {
3868 let {
3869 route
3870 } = match;
3871 if (route.lazy) {
3872 // We haven't loaded this route yet so we don't know if it's got a loader!
3873 return true;
3874 }
3875 if (route.loader == null) {
3876 return false;
3877 }
3878 if (isInitialLoad) {
3879 if (typeof route.loader !== "function" || route.loader.hydrate) {
3880 return true;
3881 }
3882 return state.loaderData[route.id] === undefined && (
3883 // Don't re-run if the loader ran and threw an error
3884 !state.errors || state.errors[route.id] === undefined);
3885 }
3886 // Always call the loader on new route instances and pending defer cancellations
3887 if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
3888 return true;
3889 }
3890 // This is the default implementation for when we revalidate. If the route
3891 // provides it's own implementation, then we give them full control but
3892 // provide this value so they can leverage it if needed after they check
3893 // their own specific use cases
3894 let currentRouteMatch = state.matches[index];
3895 let nextRouteMatch = match;
3896 return shouldRevalidateLoader(match, _extends({
3897 currentUrl,
3898 currentParams: currentRouteMatch.params,
3899 nextUrl,
3900 nextParams: nextRouteMatch.params
3901 }, submission, {
3902 actionResult,
3903 actionStatus,
3904 defaultShouldRevalidate: shouldSkipRevalidation ? false :
3905 // Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
3906 isRevalidationRequired || currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
3907 // Search params affect all loaders
3908 currentUrl.search !== nextUrl.search || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
3909 }));
3910 });
3911 // Pick fetcher.loads that need to be revalidated
3912 let revalidatingFetchers = [];
3913 fetchLoadMatches.forEach((f, key) => {
3914 // Don't revalidate:
3915 // - on initial load (shouldn't be any fetchers then anyway)
3916 // - if fetcher won't be present in the subsequent render
3917 // - no longer matches the URL (v7_fetcherPersist=false)
3918 // - was unmounted but persisted due to v7_fetcherPersist=true
3919 if (isInitialLoad || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
3920 return;
3921 }
3922 let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
3923 // If the fetcher path no longer matches, push it in with null matches so
3924 // we can trigger a 404 in callLoadersAndMaybeResolveData. Note this is
3925 // currently only a use-case for Remix HMR where the route tree can change
3926 // at runtime and remove a route previously loaded via a fetcher
3927 if (!fetcherMatches) {
3928 revalidatingFetchers.push({
3929 key,
3930 routeId: f.routeId,
3931 path: f.path,
3932 matches: null,
3933 match: null,
3934 controller: null
3935 });
3936 return;
3937 }
3938 // Revalidating fetchers are decoupled from the route matches since they
3939 // load from a static href. They revalidate based on explicit revalidation
3940 // (submission, useRevalidator, or X-Remix-Revalidate)
3941 let fetcher = state.fetchers.get(key);
3942 let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
3943 let shouldRevalidate = false;
3944 if (fetchRedirectIds.has(key)) {
3945 // Never trigger a revalidation of an actively redirecting fetcher
3946 shouldRevalidate = false;
3947 } else if (cancelledFetcherLoads.has(key)) {
3948 // Always mark for revalidation if the fetcher was cancelled
3949 cancelledFetcherLoads.delete(key);
3950 shouldRevalidate = true;
3951 } else if (fetcher && fetcher.state !== "idle" && fetcher.data === undefined) {
3952 // If the fetcher hasn't ever completed loading yet, then this isn't a
3953 // revalidation, it would just be a brand new load if an explicit
3954 // revalidation is required
3955 shouldRevalidate = isRevalidationRequired;
3956 } else {
3957 // Otherwise fall back on any user-defined shouldRevalidate, defaulting
3958 // to explicit revalidations only
3959 shouldRevalidate = shouldRevalidateLoader(fetcherMatch, _extends({
3960 currentUrl,
3961 currentParams: state.matches[state.matches.length - 1].params,
3962 nextUrl,
3963 nextParams: matches[matches.length - 1].params
3964 }, submission, {
3965 actionResult,
3966 actionStatus,
3967 defaultShouldRevalidate: shouldSkipRevalidation ? false : isRevalidationRequired
3968 }));
3969 }
3970 if (shouldRevalidate) {
3971 revalidatingFetchers.push({
3972 key,
3973 routeId: f.routeId,
3974 path: f.path,
3975 matches: fetcherMatches,
3976 match: fetcherMatch,
3977 controller: new AbortController()
3978 });
3979 }
3980 });
3981 return [navigationMatches, revalidatingFetchers];
3982}
3983function isNewLoader(currentLoaderData, currentMatch, match) {
3984 let isNew =
3985 // [a] -> [a, b]
3986 !currentMatch ||
3987 // [a, b] -> [a, c]
3988 match.route.id !== currentMatch.route.id;
3989 // Handle the case that we don't have data for a re-used route, potentially
3990 // from a prior error or from a cancelled pending deferred
3991 let isMissingData = currentLoaderData[match.route.id] === undefined;
3992 // Always load if this is a net-new route or we don't yet have data
3993 return isNew || isMissingData;
3994}
3995function isNewRouteInstance(currentMatch, match) {
3996 let currentPath = currentMatch.route.path;
3997 return (
3998 // param change for this match, /users/123 -> /users/456
3999 currentMatch.pathname !== match.pathname ||
4000 // splat param changed, which is not present in match.path
4001 // e.g. /files/images/avatar.jpg -> files/finances.xls
4002 currentPath != null && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
4003 );
4004}
4005function shouldRevalidateLoader(loaderMatch, arg) {
4006 if (loaderMatch.route.shouldRevalidate) {
4007 let routeChoice = loaderMatch.route.shouldRevalidate(arg);
4008 if (typeof routeChoice === "boolean") {
4009 return routeChoice;
4010 }
4011 }
4012 return arg.defaultShouldRevalidate;
4013}
4014/**
4015 * Idempotent utility to execute patchRoutesOnMiss() to lazily load route
4016 * definitions and update the routes/routeManifest
4017 */
4018async function loadLazyRouteChildren(patchRoutesOnMissImpl, path, matches, routes, manifest, mapRouteProperties, pendingRouteChildren, signal) {
4019 let key = [path, ...matches.map(m => m.route.id)].join("-");
4020 try {
4021 let pending = pendingRouteChildren.get(key);
4022 if (!pending) {
4023 pending = patchRoutesOnMissImpl({
4024 path,
4025 matches,
4026 patch: (routeId, children) => {
4027 if (!signal.aborted) {
4028 patchRoutesImpl(routeId, children, routes, manifest, mapRouteProperties);
4029 }
4030 }
4031 });
4032 pendingRouteChildren.set(key, pending);
4033 }
4034 if (pending && isPromise(pending)) {
4035 await pending;
4036 }
4037 } finally {
4038 pendingRouteChildren.delete(key);
4039 }
4040}
4041function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties) {
4042 if (routeId) {
4043 var _route$children;
4044 let route = manifest[routeId];
4045 invariant(route, "No route found to patch children into: routeId = " + routeId);
4046 let dataChildren = convertRoutesToDataRoutes(children, mapRouteProperties, [routeId, "patch", String(((_route$children = route.children) == null ? void 0 : _route$children.length) || "0")], manifest);
4047 if (route.children) {
4048 route.children.push(...dataChildren);
4049 } else {
4050 route.children = dataChildren;
4051 }
4052 } else {
4053 let dataChildren = convertRoutesToDataRoutes(children, mapRouteProperties, ["patch", String(routesToUse.length || "0")], manifest);
4054 routesToUse.push(...dataChildren);
4055 }
4056}
4057/**
4058 * Execute route.lazy() methods to lazily load route modules (loader, action,
4059 * shouldRevalidate) and update the routeManifest in place which shares objects
4060 * with dataRoutes so those get updated as well.
4061 */
4062async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
4063 if (!route.lazy) {
4064 return;
4065 }
4066 let lazyRoute = await route.lazy();
4067 // If the lazy route function was executed and removed by another parallel
4068 // call then we can return - first lazy() to finish wins because the return
4069 // value of lazy is expected to be static
4070 if (!route.lazy) {
4071 return;
4072 }
4073 let routeToUpdate = manifest[route.id];
4074 invariant(routeToUpdate, "No route found in manifest");
4075 // Update the route in place. This should be safe because there's no way
4076 // we could yet be sitting on this route as we can't get there without
4077 // resolving lazy() first.
4078 //
4079 // This is different than the HMR "update" use-case where we may actively be
4080 // on the route being updated. The main concern boils down to "does this
4081 // mutation affect any ongoing navigations or any current state.matches
4082 // values?". If not, it should be safe to update in place.
4083 let routeUpdates = {};
4084 for (let lazyRouteProperty in lazyRoute) {
4085 let staticRouteValue = routeToUpdate[lazyRouteProperty];
4086 let isPropertyStaticallyDefined = staticRouteValue !== undefined &&
4087 // This property isn't static since it should always be updated based
4088 // on the route updates
4089 lazyRouteProperty !== "hasErrorBoundary";
4090 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."));
4091 if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {
4092 routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];
4093 }
4094 }
4095 // Mutate the route with the provided updates. Do this first so we pass
4096 // the updated version to mapRouteProperties
4097 Object.assign(routeToUpdate, routeUpdates);
4098 // Mutate the `hasErrorBoundary` property on the route based on the route
4099 // updates and remove the `lazy` function so we don't resolve the lazy
4100 // route again.
4101 Object.assign(routeToUpdate, _extends({}, mapRouteProperties(routeToUpdate), {
4102 lazy: undefined
4103 }));
4104}
4105// Default implementation of `dataStrategy` which fetches all loaders in parallel
4106function defaultDataStrategy(opts) {
4107 return Promise.all(opts.matches.map(m => m.resolve()));
4108}
4109async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext) {
4110 let routeIdsToLoad = matchesToLoad.reduce((acc, m) => acc.add(m.route.id), new Set());
4111 let loadedMatches = new Set();
4112 // Send all matches here to allow for a middleware-type implementation.
4113 // handler will be a no-op for unneeded routes and we filter those results
4114 // back out below.
4115 let results = await dataStrategyImpl({
4116 matches: matches.map(match => {
4117 let shouldLoad = routeIdsToLoad.has(match.route.id);
4118 // `resolve` encapsulates the route.lazy, executing the
4119 // loader/action, and mapping return values/thrown errors to a
4120 // HandlerResult. Users can pass a callback to take fine-grained control
4121 // over the execution of the loader/action
4122 let resolve = handlerOverride => {
4123 loadedMatches.add(match.route.id);
4124 return shouldLoad ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, requestContext) : Promise.resolve({
4125 type: ResultType.data,
4126 result: undefined
4127 });
4128 };
4129 return _extends({}, match, {
4130 shouldLoad,
4131 resolve
4132 });
4133 }),
4134 request,
4135 params: matches[0].params,
4136 context: requestContext
4137 });
4138 // Throw if any loadRoute implementations not called since they are what
4139 // ensures a route is fully loaded
4140 matches.forEach(m => invariant(loadedMatches.has(m.route.id), "`match.resolve()` was not called for route id \"" + m.route.id + "\". " + "You must call `match.resolve()` on every match passed to " + "`dataStrategy` to ensure all routes are properly loaded."));
4141 // Filter out any middleware-only matches for which we didn't need to run handlers
4142 return results.filter((_, i) => routeIdsToLoad.has(matches[i].route.id));
4143}
4144// Default logic for calling a loader/action is the user has no specified a dataStrategy
4145async function callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, staticContext) {
4146 let result;
4147 let onReject;
4148 let runHandler = handler => {
4149 // Setup a promise we can race against so that abort signals short circuit
4150 let reject;
4151 // This will never resolve so safe to type it as Promise<HandlerResult> to
4152 // satisfy the function return value
4153 let abortPromise = new Promise((_, r) => reject = r);
4154 onReject = () => reject();
4155 request.signal.addEventListener("abort", onReject);
4156 let actualHandler = ctx => {
4157 if (typeof handler !== "function") {
4158 return Promise.reject(new Error("You cannot call the handler for a route which defines a boolean " + ("\"" + type + "\" [routeId: " + match.route.id + "]")));
4159 }
4160 return handler({
4161 request,
4162 params: match.params,
4163 context: staticContext
4164 }, ...(ctx !== undefined ? [ctx] : []));
4165 };
4166 let handlerPromise;
4167 if (handlerOverride) {
4168 handlerPromise = handlerOverride(ctx => actualHandler(ctx));
4169 } else {
4170 handlerPromise = (async () => {
4171 try {
4172 let val = await actualHandler();
4173 return {
4174 type: "data",
4175 result: val
4176 };
4177 } catch (e) {
4178 return {
4179 type: "error",
4180 result: e
4181 };
4182 }
4183 })();
4184 }
4185 return Promise.race([handlerPromise, abortPromise]);
4186 };
4187 try {
4188 let handler = match.route[type];
4189 if (match.route.lazy) {
4190 if (handler) {
4191 // Run statically defined handler in parallel with lazy()
4192 let handlerError;
4193 let [value] = await Promise.all([
4194 // If the handler throws, don't let it immediately bubble out,
4195 // since we need to let the lazy() execution finish so we know if this
4196 // route has a boundary that can handle the error
4197 runHandler(handler).catch(e => {
4198 handlerError = e;
4199 }), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);
4200 if (handlerError !== undefined) {
4201 throw handlerError;
4202 }
4203 result = value;
4204 } else {
4205 // Load lazy route module, then run any returned handler
4206 await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
4207 handler = match.route[type];
4208 if (handler) {
4209 // Handler still runs even if we got interrupted to maintain consistency
4210 // with un-abortable behavior of handler execution on non-lazy or
4211 // previously-lazy-loaded routes
4212 result = await runHandler(handler);
4213 } else if (type === "action") {
4214 let url = new URL(request.url);
4215 let pathname = url.pathname + url.search;
4216 throw getInternalRouterError(405, {
4217 method: request.method,
4218 pathname,
4219 routeId: match.route.id
4220 });
4221 } else {
4222 // lazy() route has no loader to run. Short circuit here so we don't
4223 // hit the invariant below that errors on returning undefined.
4224 return {
4225 type: ResultType.data,
4226 result: undefined
4227 };
4228 }
4229 }
4230 } else if (!handler) {
4231 let url = new URL(request.url);
4232 let pathname = url.pathname + url.search;
4233 throw getInternalRouterError(404, {
4234 pathname
4235 });
4236 } else {
4237 result = await runHandler(handler);
4238 }
4239 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`.");
4240 } catch (e) {
4241 // We should already be catching and converting normal handler executions to
4242 // HandlerResults and returning them, so anything that throws here is an
4243 // unexpected error we still need to wrap
4244 return {
4245 type: ResultType.error,
4246 result: e
4247 };
4248 } finally {
4249 if (onReject) {
4250 request.signal.removeEventListener("abort", onReject);
4251 }
4252 }
4253 return result;
4254}
4255async function convertHandlerResultToDataResult(handlerResult) {
4256 let {
4257 result,
4258 type
4259 } = handlerResult;
4260 if (isResponse(result)) {
4261 let data;
4262 try {
4263 let contentType = result.headers.get("Content-Type");
4264 // Check between word boundaries instead of startsWith() due to the last
4265 // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
4266 if (contentType && /\bapplication\/json\b/.test(contentType)) {
4267 if (result.body == null) {
4268 data = null;
4269 } else {
4270 data = await result.json();
4271 }
4272 } else {
4273 data = await result.text();
4274 }
4275 } catch (e) {
4276 return {
4277 type: ResultType.error,
4278 error: e
4279 };
4280 }
4281 if (type === ResultType.error) {
4282 return {
4283 type: ResultType.error,
4284 error: new ErrorResponseImpl(result.status, result.statusText, data),
4285 statusCode: result.status,
4286 headers: result.headers
4287 };
4288 }
4289 return {
4290 type: ResultType.data,
4291 data,
4292 statusCode: result.status,
4293 headers: result.headers
4294 };
4295 }
4296 if (type === ResultType.error) {
4297 if (isDataWithResponseInit(result)) {
4298 var _result$init2;
4299 if (result.data instanceof Error) {
4300 var _result$init;
4301 return {
4302 type: ResultType.error,
4303 error: result.data,
4304 statusCode: (_result$init = result.init) == null ? void 0 : _result$init.status
4305 };
4306 }
4307 // Convert thrown unstable_data() to ErrorResponse instances
4308 result = new ErrorResponseImpl(((_result$init2 = result.init) == null ? void 0 : _result$init2.status) || 500, undefined, result.data);
4309 }
4310 return {
4311 type: ResultType.error,
4312 error: result,
4313 statusCode: isRouteErrorResponse(result) ? result.status : undefined
4314 };
4315 }
4316 if (isDeferredData(result)) {
4317 var _result$init3, _result$init4;
4318 return {
4319 type: ResultType.deferred,
4320 deferredData: result,
4321 statusCode: (_result$init3 = result.init) == null ? void 0 : _result$init3.status,
4322 headers: ((_result$init4 = result.init) == null ? void 0 : _result$init4.headers) && new Headers(result.init.headers)
4323 };
4324 }
4325 if (isDataWithResponseInit(result)) {
4326 var _result$init5, _result$init6;
4327 return {
4328 type: ResultType.data,
4329 data: result.data,
4330 statusCode: (_result$init5 = result.init) == null ? void 0 : _result$init5.status,
4331 headers: (_result$init6 = result.init) != null && _result$init6.headers ? new Headers(result.init.headers) : undefined
4332 };
4333 }
4334 return {
4335 type: ResultType.data,
4336 data: result
4337 };
4338}
4339// Support relative routing in internal redirects
4340function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, v7_relativeSplatPath) {
4341 let location = response.headers.get("Location");
4342 invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
4343 if (!ABSOLUTE_URL_REGEX.test(location)) {
4344 let trimmedMatches = matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1);
4345 location = normalizeTo(new URL(request.url), trimmedMatches, basename, true, location, v7_relativeSplatPath);
4346 response.headers.set("Location", location);
4347 }
4348 return response;
4349}
4350function normalizeRedirectLocation(location, currentUrl, basename) {
4351 if (ABSOLUTE_URL_REGEX.test(location)) {
4352 // Strip off the protocol+origin for same-origin + same-basename absolute redirects
4353 let normalizedLocation = location;
4354 let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
4355 let isSameBasename = stripBasename(url.pathname, basename) != null;
4356 if (url.origin === currentUrl.origin && isSameBasename) {
4357 return url.pathname + url.search + url.hash;
4358 }
4359 }
4360 return location;
4361}
4362// Utility method for creating the Request instances for loaders/actions during
4363// client-side navigations and fetches. During SSR we will always have a
4364// Request instance from the static handler (query/queryRoute)
4365function createClientSideRequest(history, location, signal, submission) {
4366 let url = history.createURL(stripHashFromPath(location)).toString();
4367 let init = {
4368 signal
4369 };
4370 if (submission && isMutationMethod(submission.formMethod)) {
4371 let {
4372 formMethod,
4373 formEncType
4374 } = submission;
4375 // Didn't think we needed this but it turns out unlike other methods, patch
4376 // won't be properly normalized to uppercase and results in a 405 error.
4377 // See: https://fetch.spec.whatwg.org/#concept-method
4378 init.method = formMethod.toUpperCase();
4379 if (formEncType === "application/json") {
4380 init.headers = new Headers({
4381 "Content-Type": formEncType
4382 });
4383 init.body = JSON.stringify(submission.json);
4384 } else if (formEncType === "text/plain") {
4385 // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
4386 init.body = submission.text;
4387 } else if (formEncType === "application/x-www-form-urlencoded" && submission.formData) {
4388 // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
4389 init.body = convertFormDataToSearchParams(submission.formData);
4390 } else {
4391 // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
4392 init.body = submission.formData;
4393 }
4394 }
4395 return new Request(url, init);
4396}
4397function convertFormDataToSearchParams(formData) {
4398 let searchParams = new URLSearchParams();
4399 for (let [key, value] of formData.entries()) {
4400 // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs
4401 searchParams.append(key, typeof value === "string" ? value : value.name);
4402 }
4403 return searchParams;
4404}
4405function convertSearchParamsToFormData(searchParams) {
4406 let formData = new FormData();
4407 for (let [key, value] of searchParams.entries()) {
4408 formData.append(key, value);
4409 }
4410 return formData;
4411}
4412function processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling) {
4413 // Fill in loaderData/errors from our loaders
4414 let loaderData = {};
4415 let errors = null;
4416 let statusCode;
4417 let foundError = false;
4418 let loaderHeaders = {};
4419 let pendingError = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : undefined;
4420 // Process loader results into state.loaderData/state.errors
4421 results.forEach((result, index) => {
4422 let id = matchesToLoad[index].route.id;
4423 invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
4424 if (isErrorResult(result)) {
4425 let error = result.error;
4426 // If we have a pending action error, we report it at the highest-route
4427 // that throws a loader error, and then clear it out to indicate that
4428 // it was consumed
4429 if (pendingError !== undefined) {
4430 error = pendingError;
4431 pendingError = undefined;
4432 }
4433 errors = errors || {};
4434 if (skipLoaderErrorBubbling) {
4435 errors[id] = error;
4436 } else {
4437 // Look upwards from the matched route for the closest ancestor error
4438 // boundary, defaulting to the root match. Prefer higher error values
4439 // if lower errors bubble to the same boundary
4440 let boundaryMatch = findNearestBoundary(matches, id);
4441 if (errors[boundaryMatch.route.id] == null) {
4442 errors[boundaryMatch.route.id] = error;
4443 }
4444 }
4445 // Clear our any prior loaderData for the throwing route
4446 loaderData[id] = undefined;
4447 // Once we find our first (highest) error, we set the status code and
4448 // prevent deeper status codes from overriding
4449 if (!foundError) {
4450 foundError = true;
4451 statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
4452 }
4453 if (result.headers) {
4454 loaderHeaders[id] = result.headers;
4455 }
4456 } else {
4457 if (isDeferredResult(result)) {
4458 activeDeferreds.set(id, result.deferredData);
4459 loaderData[id] = result.deferredData.data;
4460 // Error status codes always override success status codes, but if all
4461 // loaders are successful we take the deepest status code.
4462 if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
4463 statusCode = result.statusCode;
4464 }
4465 if (result.headers) {
4466 loaderHeaders[id] = result.headers;
4467 }
4468 } else {
4469 loaderData[id] = result.data;
4470 // Error status codes always override success status codes, but if all
4471 // loaders are successful we take the deepest status code.
4472 if (result.statusCode && result.statusCode !== 200 && !foundError) {
4473 statusCode = result.statusCode;
4474 }
4475 if (result.headers) {
4476 loaderHeaders[id] = result.headers;
4477 }
4478 }
4479 }
4480 });
4481 // If we didn't consume the pending action error (i.e., all loaders
4482 // resolved), then consume it here. Also clear out any loaderData for the
4483 // throwing route
4484 if (pendingError !== undefined && pendingActionResult) {
4485 errors = {
4486 [pendingActionResult[0]]: pendingError
4487 };
4488 loaderData[pendingActionResult[0]] = undefined;
4489 }
4490 return {
4491 loaderData,
4492 errors,
4493 statusCode: statusCode || 200,
4494 loaderHeaders
4495 };
4496}
4497function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
4498 let {
4499 loaderData,
4500 errors
4501 } = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, false // This method is only called client side so we always want to bubble
4502 );
4503 // Process results from our revalidating fetchers
4504 for (let index = 0; index < revalidatingFetchers.length; index++) {
4505 let {
4506 key,
4507 match,
4508 controller
4509 } = revalidatingFetchers[index];
4510 invariant(fetcherResults !== undefined && fetcherResults[index] !== undefined, "Did not find corresponding fetcher result");
4511 let result = fetcherResults[index];
4512 // Process fetcher non-redirect errors
4513 if (controller && controller.signal.aborted) {
4514 // Nothing to do for aborted fetchers
4515 continue;
4516 } else if (isErrorResult(result)) {
4517 let boundaryMatch = findNearestBoundary(state.matches, match == null ? void 0 : match.route.id);
4518 if (!(errors && errors[boundaryMatch.route.id])) {
4519 errors = _extends({}, errors, {
4520 [boundaryMatch.route.id]: result.error
4521 });
4522 }
4523 state.fetchers.delete(key);
4524 } else if (isRedirectResult(result)) {
4525 // Should never get here, redirects should get processed above, but we
4526 // keep this to type narrow to a success result in the else
4527 invariant(false, "Unhandled fetcher revalidation redirect");
4528 } else if (isDeferredResult(result)) {
4529 // Should never get here, deferred data should be awaited for fetchers
4530 // in resolveDeferredResults
4531 invariant(false, "Unhandled fetcher deferred data");
4532 } else {
4533 let doneFetcher = getDoneFetcher(result.data);
4534 state.fetchers.set(key, doneFetcher);
4535 }
4536 }
4537 return {
4538 loaderData,
4539 errors
4540 };
4541}
4542function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
4543 let mergedLoaderData = _extends({}, newLoaderData);
4544 for (let match of matches) {
4545 let id = match.route.id;
4546 if (newLoaderData.hasOwnProperty(id)) {
4547 if (newLoaderData[id] !== undefined) {
4548 mergedLoaderData[id] = newLoaderData[id];
4549 }
4550 } else if (loaderData[id] !== undefined && match.route.loader) {
4551 // Preserve existing keys not included in newLoaderData and where a loader
4552 // wasn't removed by HMR
4553 mergedLoaderData[id] = loaderData[id];
4554 }
4555 if (errors && errors.hasOwnProperty(id)) {
4556 // Don't keep any loader data below the boundary
4557 break;
4558 }
4559 }
4560 return mergedLoaderData;
4561}
4562function getActionDataForCommit(pendingActionResult) {
4563 if (!pendingActionResult) {
4564 return {};
4565 }
4566 return isErrorResult(pendingActionResult[1]) ? {
4567 // Clear out prior actionData on errors
4568 actionData: {}
4569 } : {
4570 actionData: {
4571 [pendingActionResult[0]]: pendingActionResult[1].data
4572 }
4573 };
4574}
4575// Find the nearest error boundary, looking upwards from the leaf route (or the
4576// route specified by routeId) for the closest ancestor error boundary,
4577// defaulting to the root match
4578function findNearestBoundary(matches, routeId) {
4579 let eligibleMatches = routeId ? matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1) : [...matches];
4580 return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];
4581}
4582function getShortCircuitMatches(routes) {
4583 // Prefer a root layout route if present, otherwise shim in a route object
4584 let route = routes.length === 1 ? routes[0] : routes.find(r => r.index || !r.path || r.path === "/") || {
4585 id: "__shim-error-route__"
4586 };
4587 return {
4588 matches: [{
4589 params: {},
4590 pathname: "",
4591 pathnameBase: "",
4592 route
4593 }],
4594 route
4595 };
4596}
4597function getInternalRouterError(status, _temp5) {
4598 let {
4599 pathname,
4600 routeId,
4601 method,
4602 type,
4603 message
4604 } = _temp5 === void 0 ? {} : _temp5;
4605 let statusText = "Unknown Server Error";
4606 let errorMessage = "Unknown @remix-run/router error";
4607 if (status === 400) {
4608 statusText = "Bad Request";
4609 if (type === "route-discovery") {
4610 errorMessage = "Unable to match URL \"" + pathname + "\" - the `unstable_patchRoutesOnMiss()` " + ("function threw the following error:\n" + message);
4611 } else if (method && pathname && routeId) {
4612 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.";
4613 } else if (type === "defer-action") {
4614 errorMessage = "defer() is not supported in actions";
4615 } else if (type === "invalid-body") {
4616 errorMessage = "Unable to encode submission body";
4617 }
4618 } else if (status === 403) {
4619 statusText = "Forbidden";
4620 errorMessage = "Route \"" + routeId + "\" does not match URL \"" + pathname + "\"";
4621 } else if (status === 404) {
4622 statusText = "Not Found";
4623 errorMessage = "No route matches URL \"" + pathname + "\"";
4624 } else if (status === 405) {
4625 statusText = "Method Not Allowed";
4626 if (method && pathname && routeId) {
4627 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.";
4628 } else if (method) {
4629 errorMessage = "Invalid request method \"" + method.toUpperCase() + "\"";
4630 }
4631 }
4632 return new ErrorResponseImpl(status || 500, statusText, new Error(errorMessage), true);
4633}
4634// Find any returned redirect errors, starting from the lowest match
4635function findRedirect(results) {
4636 for (let i = results.length - 1; i >= 0; i--) {
4637 let result = results[i];
4638 if (isRedirectResult(result)) {
4639 return {
4640 result,
4641 idx: i
4642 };
4643 }
4644 }
4645}
4646function stripHashFromPath(path) {
4647 let parsedPath = typeof path === "string" ? parsePath(path) : path;
4648 return createPath(_extends({}, parsedPath, {
4649 hash: ""
4650 }));
4651}
4652function isHashChangeOnly(a, b) {
4653 if (a.pathname !== b.pathname || a.search !== b.search) {
4654 return false;
4655 }
4656 if (a.hash === "") {
4657 // /page -> /page#hash
4658 return b.hash !== "";
4659 } else if (a.hash === b.hash) {
4660 // /page#hash -> /page#hash
4661 return true;
4662 } else if (b.hash !== "") {
4663 // /page#hash -> /page#other
4664 return true;
4665 }
4666 // If the hash is removed the browser will re-perform a request to the server
4667 // /page#hash -> /page
4668 return false;
4669}
4670function isPromise(val) {
4671 return typeof val === "object" && val != null && "then" in val;
4672}
4673function isHandlerResult(result) {
4674 return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
4675}
4676function isRedirectHandlerResult(result) {
4677 return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
4678}
4679function isDeferredResult(result) {
4680 return result.type === ResultType.deferred;
4681}
4682function isErrorResult(result) {
4683 return result.type === ResultType.error;
4684}
4685function isRedirectResult(result) {
4686 return (result && result.type) === ResultType.redirect;
4687}
4688function isDataWithResponseInit(value) {
4689 return typeof value === "object" && value != null && "type" in value && "data" in value && "init" in value && value.type === "DataWithResponseInit";
4690}
4691function isDeferredData(value) {
4692 let deferred = value;
4693 return deferred && typeof deferred === "object" && typeof deferred.data === "object" && typeof deferred.subscribe === "function" && typeof deferred.cancel === "function" && typeof deferred.resolveData === "function";
4694}
4695function isResponse(value) {
4696 return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
4697}
4698function isRedirectResponse(result) {
4699 if (!isResponse(result)) {
4700 return false;
4701 }
4702 let status = result.status;
4703 let location = result.headers.get("Location");
4704 return status >= 300 && status <= 399 && location != null;
4705}
4706function isValidMethod(method) {
4707 return validRequestMethods.has(method.toLowerCase());
4708}
4709function isMutationMethod(method) {
4710 return validMutationMethods.has(method.toLowerCase());
4711}
4712async function resolveDeferredResults(currentMatches, matchesToLoad, results, signals, isFetcher, currentLoaderData) {
4713 for (let index = 0; index < results.length; index++) {
4714 let result = results[index];
4715 let match = matchesToLoad[index];
4716 // If we don't have a match, then we can have a deferred result to do
4717 // anything with. This is for revalidating fetchers where the route was
4718 // removed during HMR
4719 if (!match) {
4720 continue;
4721 }
4722 let currentMatch = currentMatches.find(m => m.route.id === match.route.id);
4723 let isRevalidatingLoader = currentMatch != null && !isNewRouteInstance(currentMatch, match) && (currentLoaderData && currentLoaderData[match.route.id]) !== undefined;
4724 if (isDeferredResult(result) && (isFetcher || isRevalidatingLoader)) {
4725 // Note: we do not have to touch activeDeferreds here since we race them
4726 // against the signal in resolveDeferredData and they'll get aborted
4727 // there if needed
4728 let signal = signals[index];
4729 invariant(signal, "Expected an AbortSignal for revalidating fetcher deferred result");
4730 await resolveDeferredData(result, signal, isFetcher).then(result => {
4731 if (result) {
4732 results[index] = result || results[index];
4733 }
4734 });
4735 }
4736 }
4737}
4738async function resolveDeferredData(result, signal, unwrap) {
4739 if (unwrap === void 0) {
4740 unwrap = false;
4741 }
4742 let aborted = await result.deferredData.resolveData(signal);
4743 if (aborted) {
4744 return;
4745 }
4746 if (unwrap) {
4747 try {
4748 return {
4749 type: ResultType.data,
4750 data: result.deferredData.unwrappedData
4751 };
4752 } catch (e) {
4753 // Handle any TrackedPromise._error values encountered while unwrapping
4754 return {
4755 type: ResultType.error,
4756 error: e
4757 };
4758 }
4759 }
4760 return {
4761 type: ResultType.data,
4762 data: result.deferredData.data
4763 };
4764}
4765function hasNakedIndexQuery(search) {
4766 return new URLSearchParams(search).getAll("index").some(v => v === "");
4767}
4768function getTargetMatch(matches, location) {
4769 let search = typeof location === "string" ? parsePath(location).search : location.search;
4770 if (matches[matches.length - 1].route.index && hasNakedIndexQuery(search || "")) {
4771 // Return the leaf index route when index is present
4772 return matches[matches.length - 1];
4773 }
4774 // Otherwise grab the deepest "path contributing" match (ignoring index and
4775 // pathless layout routes)
4776 let pathMatches = getPathContributingMatches(matches);
4777 return pathMatches[pathMatches.length - 1];
4778}
4779function getSubmissionFromNavigation(navigation) {
4780 let {
4781 formMethod,
4782 formAction,
4783 formEncType,
4784 text,
4785 formData,
4786 json
4787 } = navigation;
4788 if (!formMethod || !formAction || !formEncType) {
4789 return;
4790 }
4791 if (text != null) {
4792 return {
4793 formMethod,
4794 formAction,
4795 formEncType,
4796 formData: undefined,
4797 json: undefined,
4798 text
4799 };
4800 } else if (formData != null) {
4801 return {
4802 formMethod,
4803 formAction,
4804 formEncType,
4805 formData,
4806 json: undefined,
4807 text: undefined
4808 };
4809 } else if (json !== undefined) {
4810 return {
4811 formMethod,
4812 formAction,
4813 formEncType,
4814 formData: undefined,
4815 json,
4816 text: undefined
4817 };
4818 }
4819}
4820function getLoadingNavigation(location, submission) {
4821 if (submission) {
4822 let navigation = {
4823 state: "loading",
4824 location,
4825 formMethod: submission.formMethod,
4826 formAction: submission.formAction,
4827 formEncType: submission.formEncType,
4828 formData: submission.formData,
4829 json: submission.json,
4830 text: submission.text
4831 };
4832 return navigation;
4833 } else {
4834 let navigation = {
4835 state: "loading",
4836 location,
4837 formMethod: undefined,
4838 formAction: undefined,
4839 formEncType: undefined,
4840 formData: undefined,
4841 json: undefined,
4842 text: undefined
4843 };
4844 return navigation;
4845 }
4846}
4847function getSubmittingNavigation(location, submission) {
4848 let navigation = {
4849 state: "submitting",
4850 location,
4851 formMethod: submission.formMethod,
4852 formAction: submission.formAction,
4853 formEncType: submission.formEncType,
4854 formData: submission.formData,
4855 json: submission.json,
4856 text: submission.text
4857 };
4858 return navigation;
4859}
4860function getLoadingFetcher(submission, data) {
4861 if (submission) {
4862 let fetcher = {
4863 state: "loading",
4864 formMethod: submission.formMethod,
4865 formAction: submission.formAction,
4866 formEncType: submission.formEncType,
4867 formData: submission.formData,
4868 json: submission.json,
4869 text: submission.text,
4870 data
4871 };
4872 return fetcher;
4873 } else {
4874 let fetcher = {
4875 state: "loading",
4876 formMethod: undefined,
4877 formAction: undefined,
4878 formEncType: undefined,
4879 formData: undefined,
4880 json: undefined,
4881 text: undefined,
4882 data
4883 };
4884 return fetcher;
4885 }
4886}
4887function getSubmittingFetcher(submission, existingFetcher) {
4888 let fetcher = {
4889 state: "submitting",
4890 formMethod: submission.formMethod,
4891 formAction: submission.formAction,
4892 formEncType: submission.formEncType,
4893 formData: submission.formData,
4894 json: submission.json,
4895 text: submission.text,
4896 data: existingFetcher ? existingFetcher.data : undefined
4897 };
4898 return fetcher;
4899}
4900function getDoneFetcher(data) {
4901 let fetcher = {
4902 state: "idle",
4903 formMethod: undefined,
4904 formAction: undefined,
4905 formEncType: undefined,
4906 formData: undefined,
4907 json: undefined,
4908 text: undefined,
4909 data
4910 };
4911 return fetcher;
4912}
4913function restoreAppliedTransitions(_window, transitions) {
4914 try {
4915 let sessionPositions = _window.sessionStorage.getItem(TRANSITIONS_STORAGE_KEY);
4916 if (sessionPositions) {
4917 let json = JSON.parse(sessionPositions);
4918 for (let [k, v] of Object.entries(json || {})) {
4919 if (v && Array.isArray(v)) {
4920 transitions.set(k, new Set(v || []));
4921 }
4922 }
4923 }
4924 } catch (e) {
4925 // no-op, use default empty object
4926 }
4927}
4928function persistAppliedTransitions(_window, transitions) {
4929 if (transitions.size > 0) {
4930 let json = {};
4931 for (let [k, v] of transitions) {
4932 json[k] = [...v];
4933 }
4934 try {
4935 _window.sessionStorage.setItem(TRANSITIONS_STORAGE_KEY, JSON.stringify(json));
4936 } catch (error) {
4937 warning(false, "Failed to save applied view transitions in sessionStorage (" + error + ").");
4938 }
4939 }
4940}
4941//#endregion
4942
4943export { 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, defer, generatePath, getStaticContextFromError, getToPathname, isDataWithResponseInit, isDeferredData, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, redirectDocument, replace, resolvePath, resolveTo, stripBasename, data as unstable_data };
4944//# sourceMappingURL=router.js.map
Note: See TracBrowser for help on using the repository browser.