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

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

Pred finalna verzija

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