diff --git a/app.js b/app.js
index ccc9efa..2bc3480 100644
--- a/app.js
+++ b/app.js
@@ -1,8141 +1,160 @@
+/*
+ * ATTENTION: An "eval-source-map" devtool has been used.
+ * This devtool is neither made for production nor for readable output files.
+ * It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
+ * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
+ * or disable the default devtool with "devtool: false".
+ * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
+ */
/******/ (() => { // webpackBootstrap
-/******/ "use strict";
/******/ var __webpack_modules__ = ({
-/***/ 401:
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-
-// EXPORTS
-__webpack_require__.d(__webpack_exports__, {
- Z: () => (/* binding */ app)
-});
-
-// EXTERNAL MODULE: ./node_modules/react/index.js
-var react = __webpack_require__(294);
-var react_namespaceObject = /*#__PURE__*/__webpack_require__.t(react, 2);
-;// CONCATENATED MODULE: ./node_modules/@remix-run/router/dist/router.js
-/**
- * @remix-run/router v1.7.0
- *
- * Copyright (c) Remix Software Inc.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE.md file in the root directory of this source tree.
- *
- * @license MIT
- */
-function _extends() {
- _extends = Object.assign ? Object.assign.bind() : function (target) {
- for (var i = 1; i < arguments.length; i++) {
- var source = arguments[i];
- for (var key in source) {
- if (Object.prototype.hasOwnProperty.call(source, key)) {
- target[key] = source[key];
- }
- }
- }
- return target;
- };
- return _extends.apply(this, arguments);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//#region Types and Constants
-////////////////////////////////////////////////////////////////////////////////
-/**
- * Actions represent the type of change to a location value.
- */
-var Action;
-(function (Action) {
- /**
- * A POP indicates a change to an arbitrary index in the history stack, such
- * as a back or forward navigation. It does not describe the direction of the
- * navigation, only that the current index changed.
- *
- * Note: This is the default action for newly created history objects.
- */
- Action["Pop"] = "POP";
- /**
- * A PUSH indicates a new entry being added to the history stack, such as when
- * a link is clicked and a new page loads. When this happens, all subsequent
- * entries in the stack are lost.
- */
- Action["Push"] = "PUSH";
- /**
- * A REPLACE indicates the entry at the current index in the history stack
- * being replaced by a new one.
- */
- Action["Replace"] = "REPLACE";
-})(Action || (Action = {}));
-const PopStateEventType = "popstate";
-/**
- * Memory history stores the current location in memory. It is designed for use
- * in stateful non-browser environments like tests and React Native.
- */
-function router_createMemoryHistory(options) {
- if (options === void 0) {
- options = {};
- }
- let {
- initialEntries = ["/"],
- initialIndex,
- v5Compat = false
- } = options;
- let entries; // Declare so we can access from createMemoryLocation
- entries = initialEntries.map((entry, index) => createMemoryLocation(entry, typeof entry === "string" ? null : entry.state, index === 0 ? "default" : undefined));
- let index = clampIndex(initialIndex == null ? entries.length - 1 : initialIndex);
- let action = Action.Pop;
- let listener = null;
- function clampIndex(n) {
- return Math.min(Math.max(n, 0), entries.length - 1);
- }
- function getCurrentLocation() {
- return entries[index];
- }
- function createMemoryLocation(to, state, key) {
- if (state === void 0) {
- state = null;
- }
- let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key);
- warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
- return location;
- }
- function createHref(to) {
- return typeof to === "string" ? to : router_createPath(to);
- }
- let history = {
- get index() {
- return index;
- },
- get action() {
- return action;
- },
- get location() {
- return getCurrentLocation();
- },
- createHref,
- createURL(to) {
- return new URL(createHref(to), "http://localhost");
- },
- encodeLocation(to) {
- let path = typeof to === "string" ? parsePath(to) : to;
- return {
- pathname: path.pathname || "",
- search: path.search || "",
- hash: path.hash || ""
- };
- },
- push(to, state) {
- action = Action.Push;
- let nextLocation = createMemoryLocation(to, state);
- index += 1;
- entries.splice(index, entries.length, nextLocation);
- if (v5Compat && listener) {
- listener({
- action,
- location: nextLocation,
- delta: 1
- });
- }
- },
- replace(to, state) {
- action = Action.Replace;
- let nextLocation = createMemoryLocation(to, state);
- entries[index] = nextLocation;
- if (v5Compat && listener) {
- listener({
- action,
- location: nextLocation,
- delta: 0
- });
- }
- },
- go(delta) {
- action = Action.Pop;
- let nextIndex = clampIndex(index + delta);
- let nextLocation = entries[nextIndex];
- index = nextIndex;
- if (listener) {
- listener({
- action,
- location: nextLocation,
- delta
- });
- }
- },
- listen(fn) {
- listener = fn;
- return () => {
- listener = null;
- };
- }
- };
- return history;
-}
-/**
- * Browser history stores the location in regular URLs. This is the standard for
- * most web apps, but it requires some configuration on the server to ensure you
- * serve the same app at multiple URLs.
- *
- * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createbrowserhistory
- */
-function router_createBrowserHistory(options) {
- if (options === void 0) {
- options = {};
- }
- function createBrowserLocation(window, globalHistory) {
- let {
- pathname,
- search,
- hash
- } = window.location;
- return createLocation("", {
- pathname,
- search,
- hash
- },
- // state defaults to `null` because `window.history.state` does
- globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
- }
- function createBrowserHref(window, to) {
- return typeof to === "string" ? to : router_createPath(to);
- }
- return getUrlBasedHistory(createBrowserLocation, createBrowserHref, null, options);
-}
-/**
- * Hash history stores the location in window.location.hash. This makes it ideal
- * for situations where you don't want to send the location to the server for
- * some reason, either because you do cannot configure it or the URL space is
- * reserved for something else.
- *
- * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createhashhistory
- */
-function router_createHashHistory(options) {
- if (options === void 0) {
- options = {};
- }
- function createHashLocation(window, globalHistory) {
- let {
- pathname = "/",
- search = "",
- hash = ""
- } = parsePath(window.location.hash.substr(1));
- return createLocation("", {
- pathname,
- search,
- hash
- },
- // state defaults to `null` because `window.history.state` does
- globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
- }
- function createHashHref(window, to) {
- let base = window.document.querySelector("base");
- let href = "";
- if (base && base.getAttribute("href")) {
- let url = window.location.href;
- let hashIndex = url.indexOf("#");
- href = hashIndex === -1 ? url : url.slice(0, hashIndex);
- }
- return href + "#" + (typeof to === "string" ? to : router_createPath(to));
- }
- function validateHashLocation(location, to) {
- warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
- }
- return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
-}
-function invariant(value, message) {
- if (value === false || value === null || typeof value === "undefined") {
- throw new Error(message);
- }
-}
-function warning(cond, message) {
- if (!cond) {
- // eslint-disable-next-line no-console
- if (typeof console !== "undefined") console.warn(message);
- try {
- // Welcome to debugging history!
- //
- // This error is thrown as a convenience so you can more easily
- // find the source for a warning that appears in the console by
- // enabling "pause on exceptions" in your JavaScript debugger.
- throw new Error(message);
- // eslint-disable-next-line no-empty
- } catch (e) {}
- }
-}
-function createKey() {
- return Math.random().toString(36).substr(2, 8);
-}
-/**
- * For browser-based histories, we combine the state and key into an object
- */
-function getHistoryState(location, index) {
- return {
- usr: location.state,
- key: location.key,
- idx: index
- };
-}
-/**
- * Creates a Location object with a unique key from the given Path
- */
-function createLocation(current, to, state, key) {
- if (state === void 0) {
- state = null;
- }
- let location = _extends({
- pathname: typeof current === "string" ? current : current.pathname,
- search: "",
- hash: ""
- }, typeof to === "string" ? parsePath(to) : to, {
- state,
- // TODO: This could be cleaned up. push/replace should probably just take
- // full Locations now and avoid the need to run through this flow at all
- // But that's a pretty big refactor to the current test suite so going to
- // keep as is for the time being and just let any incoming keys take precedence
- key: to && to.key || key || createKey()
- });
- return location;
-}
-/**
- * Creates a string URL path from the given pathname, search, and hash components.
- */
-function router_createPath(_ref) {
- let {
- pathname = "/",
- search = "",
- hash = ""
- } = _ref;
- if (search && search !== "?") pathname += search.charAt(0) === "?" ? search : "?" + search;
- if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : "#" + hash;
- return pathname;
-}
-/**
- * Parses a string URL path into its separate pathname, search, and hash components.
- */
-function parsePath(path) {
- let parsedPath = {};
- if (path) {
- let hashIndex = path.indexOf("#");
- if (hashIndex >= 0) {
- parsedPath.hash = path.substr(hashIndex);
- path = path.substr(0, hashIndex);
- }
- let searchIndex = path.indexOf("?");
- if (searchIndex >= 0) {
- parsedPath.search = path.substr(searchIndex);
- path = path.substr(0, searchIndex);
- }
- if (path) {
- parsedPath.pathname = path;
- }
- }
- return parsedPath;
-}
-function getUrlBasedHistory(getLocation, createHref, validateLocation, options) {
- if (options === void 0) {
- options = {};
- }
- let {
- window = document.defaultView,
- v5Compat = false
- } = options;
- let globalHistory = window.history;
- let action = Action.Pop;
- let listener = null;
- let index = getIndex();
- // Index should only be null when we initialize. If not, it's because the
- // user called history.pushState or history.replaceState directly, in which
- // case we should log a warning as it will result in bugs.
- if (index == null) {
- index = 0;
- globalHistory.replaceState(_extends({}, globalHistory.state, {
- idx: index
- }), "");
- }
- function getIndex() {
- let state = globalHistory.state || {
- idx: null
- };
- return state.idx;
- }
- function handlePop() {
- action = Action.Pop;
- let nextIndex = getIndex();
- let delta = nextIndex == null ? null : nextIndex - index;
- index = nextIndex;
- if (listener) {
- listener({
- action,
- location: history.location,
- delta
- });
- }
- }
- function push(to, state) {
- action = Action.Push;
- let location = createLocation(history.location, to, state);
- if (validateLocation) validateLocation(location, to);
- index = getIndex() + 1;
- let historyState = getHistoryState(location, index);
- let url = history.createHref(location);
- // try...catch because iOS limits us to 100 pushState calls :/
- try {
- globalHistory.pushState(historyState, "", url);
- } catch (error) {
- // If the exception is because `state` can't be serialized, let that throw
- // outwards just like a replace call would so the dev knows the cause
- // https://html.spec.whatwg.org/multipage/nav-history-apis.html#shared-history-push/replace-state-steps
- // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
- if (error instanceof DOMException && error.name === "DataCloneError") {
- throw error;
- }
- // They are going to lose state here, but there is no real
- // way to warn them about it since the page will refresh...
- window.location.assign(url);
- }
- if (v5Compat && listener) {
- listener({
- action,
- location: history.location,
- delta: 1
- });
- }
- }
- function replace(to, state) {
- action = Action.Replace;
- let location = createLocation(history.location, to, state);
- if (validateLocation) validateLocation(location, to);
- index = getIndex();
- let historyState = getHistoryState(location, index);
- let url = history.createHref(location);
- globalHistory.replaceState(historyState, "", url);
- if (v5Compat && listener) {
- listener({
- action,
- location: history.location,
- delta: 0
- });
- }
- }
- function createURL(to) {
- // window.location.origin is "null" (the literal string value) in Firefox
- // under certain conditions, notably when serving from a local HTML file
- // See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
- let base = window.location.origin !== "null" ? window.location.origin : window.location.href;
- let href = typeof to === "string" ? to : router_createPath(to);
- invariant(base, "No window.location.(origin|href) available to create URL for href: " + href);
- return new URL(href, base);
- }
- let history = {
- get action() {
- return action;
- },
- get location() {
- return getLocation(window, globalHistory);
- },
- listen(fn) {
- if (listener) {
- throw new Error("A history only accepts one active listener");
- }
- window.addEventListener(PopStateEventType, handlePop);
- listener = fn;
- return () => {
- window.removeEventListener(PopStateEventType, handlePop);
- listener = null;
- };
- },
- createHref(to) {
- return createHref(window, to);
- },
- createURL,
- encodeLocation(to) {
- // Encode a Location the same way window.location would
- let url = createURL(to);
- return {
- pathname: url.pathname,
- search: url.search,
- hash: url.hash
- };
- },
- push,
- replace,
- go(n) {
- return globalHistory.go(n);
- }
- };
- return history;
-}
-//#endregion
-
-var ResultType;
-(function (ResultType) {
- ResultType["data"] = "data";
- ResultType["deferred"] = "deferred";
- ResultType["redirect"] = "redirect";
- ResultType["error"] = "error";
-})(ResultType || (ResultType = {}));
-const immutableRouteKeys = new Set(["lazy", "caseSensitive", "path", "id", "index", "children"]);
-function isIndexRoute(route) {
- return route.index === true;
-}
-// Walk the route tree generating unique IDs where necessary so we are working
-// solely with AgnosticDataRouteObject's within the Router
-function convertRoutesToDataRoutes(routes, mapRouteProperties, parentPath, manifest) {
- if (parentPath === void 0) {
- parentPath = [];
- }
- if (manifest === void 0) {
- manifest = {};
- }
- return routes.map((route, index) => {
- let treePath = [...parentPath, index];
- let id = typeof route.id === "string" ? route.id : treePath.join("-");
- invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
- invariant(!manifest[id], "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
- if (isIndexRoute(route)) {
- let indexRoute = _extends({}, route, mapRouteProperties(route), {
- id
- });
- manifest[id] = indexRoute;
- return indexRoute;
- } else {
- let pathOrLayoutRoute = _extends({}, route, mapRouteProperties(route), {
- id,
- children: undefined
- });
- manifest[id] = pathOrLayoutRoute;
- if (route.children) {
- pathOrLayoutRoute.children = convertRoutesToDataRoutes(route.children, mapRouteProperties, treePath, manifest);
- }
- return pathOrLayoutRoute;
- }
- });
-}
-/**
- * Matches the given routes to a location and returns the match data.
- *
- * @see https://reactrouter.com/utils/match-routes
- */
-function matchRoutes(routes, locationArg, basename) {
- if (basename === void 0) {
- basename = "/";
- }
- let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
- let pathname = router_stripBasename(location.pathname || "/", basename);
- if (pathname == null) {
- return null;
- }
- let branches = flattenRoutes(routes);
- rankRouteBranches(branches);
- let matches = null;
- for (let i = 0; matches == null && i < branches.length; ++i) {
- matches = matchRouteBranch(branches[i],
- // Incoming pathnames are generally encoded from either window.location
- // or from router.navigate, but we want to match against the unencoded
- // paths in the route definitions. Memory router locations won't be
- // encoded here but there also shouldn't be anything to decode so this
- // should be a safe operation. This avoids needing matchRoutes to be
- // history-aware.
- safelyDecodeURI(pathname));
- }
- return matches;
-}
-function flattenRoutes(routes, branches, parentsMeta, parentPath) {
- if (branches === void 0) {
- branches = [];
- }
- if (parentsMeta === void 0) {
- parentsMeta = [];
- }
- if (parentPath === void 0) {
- parentPath = "";
- }
- let flattenRoute = (route, index, relativePath) => {
- let meta = {
- relativePath: relativePath === undefined ? route.path || "" : relativePath,
- caseSensitive: route.caseSensitive === true,
- childrenIndex: index,
- route
- };
- if (meta.relativePath.startsWith("/")) {
- 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.");
- meta.relativePath = meta.relativePath.slice(parentPath.length);
- }
- let path = router_joinPaths([parentPath, meta.relativePath]);
- let routesMeta = parentsMeta.concat(meta);
- // Add the children before adding this route to the array so we traverse the
- // route tree depth-first and child routes appear before their parents in
- // the "flattened" version.
- if (route.children && route.children.length > 0) {
- invariant(
- // Our types know better, but runtime JS may not!
- // @ts-expect-error
- route.index !== true, "Index routes must not have child routes. Please remove " + ("all child routes from route path \"" + path + "\"."));
- flattenRoutes(route.children, branches, routesMeta, path);
- }
- // Routes without a path shouldn't ever match by themselves unless they are
- // index routes, so don't add them to the list of possible branches.
- if (route.path == null && !route.index) {
- return;
- }
- branches.push({
- path,
- score: computeScore(path, route.index),
- routesMeta
- });
- };
- routes.forEach((route, index) => {
- var _route$path;
- // coarse-grain check for optional params
- if (route.path === "" || !((_route$path = route.path) != null && _route$path.includes("?"))) {
- flattenRoute(route, index);
- } else {
- for (let exploded of explodeOptionalSegments(route.path)) {
- flattenRoute(route, index, exploded);
- }
- }
- });
- return branches;
-}
-/**
- * Computes all combinations of optional path segments for a given path,
- * excluding combinations that are ambiguous and of lower priority.
- *
- * For example, `/one/:two?/three/:four?/:five?` explodes to:
- * - `/one/three`
- * - `/one/:two/three`
- * - `/one/three/:four`
- * - `/one/three/:five`
- * - `/one/:two/three/:four`
- * - `/one/:two/three/:five`
- * - `/one/three/:four/:five`
- * - `/one/:two/three/:four/:five`
- */
-function explodeOptionalSegments(path) {
- let segments = path.split("/");
- if (segments.length === 0) return [];
- let [first, ...rest] = segments;
- // Optional path segments are denoted by a trailing `?`
- let isOptional = first.endsWith("?");
- // Compute the corresponding required segment: `foo?` -> `foo`
- let required = first.replace(/\?$/, "");
- if (rest.length === 0) {
- // Intepret empty string as omitting an optional segment
- // `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
- return isOptional ? [required, ""] : [required];
- }
- let restExploded = explodeOptionalSegments(rest.join("/"));
- let result = [];
- // All child paths with the prefix. Do this for all children before the
- // optional version for all children so we get consistent ordering where the
- // parent optional aspect is preferred as required. Otherwise, we can get
- // child sections interspersed where deeper optional segments are higher than
- // parent optional segments, where for example, /:two would explodes _earlier_
- // then /:one. By always including the parent as required _for all children_
- // first, we avoid this issue
- result.push(...restExploded.map(subpath => subpath === "" ? required : [required, subpath].join("/")));
- // Then if this is an optional value, add all child versions without
- if (isOptional) {
- result.push(...restExploded);
- }
- // for absolute paths, ensure `/` instead of empty segment
- return result.map(exploded => path.startsWith("/") && exploded === "" ? "/" : exploded);
-}
-function rankRouteBranches(branches) {
- branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
- : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
-}
-const paramRe = /^:\w+$/;
-const dynamicSegmentValue = 3;
-const indexRouteValue = 2;
-const emptySegmentValue = 1;
-const staticSegmentValue = 10;
-const splatPenalty = -2;
-const isSplat = s => s === "*";
-function computeScore(path, index) {
- let segments = path.split("/");
- let initialScore = segments.length;
- if (segments.some(isSplat)) {
- initialScore += splatPenalty;
- }
- if (index) {
- initialScore += indexRouteValue;
- }
- return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
-}
-function compareIndexes(a, b) {
- let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
- return siblings ?
- // If two routes are siblings, we should try to match the earlier sibling
- // first. This allows people to have fine-grained control over the matching
- // behavior by simply putting routes with identical paths in the order they
- // want them tried.
- a[a.length - 1] - b[b.length - 1] :
- // Otherwise, it doesn't really make sense to rank non-siblings by index,
- // so they sort equally.
- 0;
-}
-function matchRouteBranch(branch, pathname) {
- let {
- routesMeta
- } = branch;
- let matchedParams = {};
- let matchedPathname = "/";
- let matches = [];
- for (let i = 0; i < routesMeta.length; ++i) {
- let meta = routesMeta[i];
- let end = i === routesMeta.length - 1;
- let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
- let match = router_matchPath({
- path: meta.relativePath,
- caseSensitive: meta.caseSensitive,
- end
- }, remainingPathname);
- if (!match) return null;
- Object.assign(matchedParams, match.params);
- let route = meta.route;
- matches.push({
- // TODO: Can this as be avoided?
- params: matchedParams,
- pathname: router_joinPaths([matchedPathname, match.pathname]),
- pathnameBase: normalizePathname(router_joinPaths([matchedPathname, match.pathnameBase])),
- route
- });
- if (match.pathnameBase !== "/") {
- matchedPathname = router_joinPaths([matchedPathname, match.pathnameBase]);
- }
- }
- return matches;
-}
-/**
- * Returns a path with params interpolated.
- *
- * @see https://reactrouter.com/utils/generate-path
- */
-function generatePath(originalPath, params) {
- if (params === void 0) {
- params = {};
- }
- let path = originalPath;
- if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
- 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(/\*$/, "/*") + "\"."));
- path = path.replace(/\*$/, "/*");
- }
- // ensure `/` is added at the beginning if the path is absolute
- const prefix = path.startsWith("/") ? "/" : "";
- const stringify = p => p == null ? "" : typeof p === "string" ? p : String(p);
- const segments = path.split(/\/+/).map((segment, index, array) => {
- const isLastSegment = index === array.length - 1;
- // only apply the splat if it's the last segment
- if (isLastSegment && segment === "*") {
- const star = "*";
- // Apply the splat
- return stringify(params[star]);
- }
- const keyMatch = segment.match(/^:(\w+)(\??)$/);
- if (keyMatch) {
- const [, key, optional] = keyMatch;
- let param = params[key];
- invariant(optional === "?" || param != null, "Missing \":" + key + "\" param");
- return stringify(param);
- }
- // Remove any optional markers from optional static segments
- return segment.replace(/\?$/g, "");
- })
- // Remove empty segments
- .filter(segment => !!segment);
- return prefix + segments.join("/");
-}
-/**
- * Performs pattern matching on a URL pathname and returns information about
- * the match.
- *
- * @see https://reactrouter.com/utils/match-path
- */
-function router_matchPath(pattern, pathname) {
- if (typeof pattern === "string") {
- pattern = {
- path: pattern,
- caseSensitive: false,
- end: true
- };
- }
- let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
- let match = pathname.match(matcher);
- if (!match) return null;
- let matchedPathname = match[0];
- let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
- let captureGroups = match.slice(1);
- let params = paramNames.reduce((memo, paramName, index) => {
- // We need to compute the pathnameBase here using the raw splat value
- // instead of using params["*"] later because it will be decoded then
- if (paramName === "*") {
- let splatValue = captureGroups[index] || "";
- pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
- }
- memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || "", paramName);
- return memo;
- }, {});
- return {
- params,
- pathname: matchedPathname,
- pathnameBase,
- pattern
- };
-}
-function compilePath(path, caseSensitive, end) {
- if (caseSensitive === void 0) {
- caseSensitive = false;
- }
- if (end === void 0) {
- end = true;
- }
- 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(/\*$/, "/*") + "\"."));
- let paramNames = [];
- let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
- .replace(/^\/*/, "/") // Make sure it has a leading /
- .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
- .replace(/\/:(\w+)/g, (_, paramName) => {
- paramNames.push(paramName);
- return "/([^\\/]+)";
- });
- if (path.endsWith("*")) {
- paramNames.push("*");
- regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
- : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
- } else if (end) {
- // When matching to the end, ignore trailing slashes
- regexpSource += "\\/*$";
- } else if (path !== "" && path !== "/") {
- // If our path is non-empty and contains anything beyond an initial slash,
- // then we have _some_ form of path in our regex so we should expect to
- // match only if we find the end of this path segment. Look for an optional
- // non-captured trailing slash (to match a portion of the URL) or the end
- // of the path (if we've matched to the end). We used to do this with a
- // word boundary but that gives false positives on routes like
- // /user-preferences since `-` counts as a word boundary.
- regexpSource += "(?:(?=\\/|$))";
- } else ;
- let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
- return [matcher, paramNames];
-}
-function safelyDecodeURI(value) {
- try {
- return decodeURI(value);
- } catch (error) {
- 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 + ")."));
- return value;
- }
-}
-function safelyDecodeURIComponent(value, paramName) {
- try {
- return decodeURIComponent(value);
- } catch (error) {
- warning(false, "The value for the URL param \"" + paramName + "\" will not be decoded because" + (" the string \"" + value + "\" is a malformed URL segment. This is probably") + (" due to a bad percent encoding (" + error + ")."));
- return value;
- }
-}
-/**
- * @private
- */
-function router_stripBasename(pathname, basename) {
- if (basename === "/") return pathname;
- if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
- return null;
- }
- // We want to leave trailing slash behavior in the user's control, so if they
- // specify a basename with a trailing slash, we should support it
- let startIndex = basename.endsWith("/") ? basename.length - 1 : basename.length;
- let nextChar = pathname.charAt(startIndex);
- if (nextChar && nextChar !== "/") {
- // pathname does not start with basename/
- return null;
- }
- return pathname.slice(startIndex) || "/";
-}
-/**
- * Returns a resolved path object relative to the given pathname.
- *
- * @see https://reactrouter.com/utils/resolve-path
- */
-function resolvePath(to, fromPathname) {
- if (fromPathname === void 0) {
- fromPathname = "/";
- }
- let {
- pathname: toPathname,
- search = "",
- hash = ""
- } = typeof to === "string" ? parsePath(to) : to;
- let pathname = toPathname ? toPathname.startsWith("/") ? toPathname : resolvePathname(toPathname, fromPathname) : fromPathname;
- return {
- pathname,
- search: normalizeSearch(search),
- hash: normalizeHash(hash)
- };
-}
-function resolvePathname(relativePath, fromPathname) {
- let segments = fromPathname.replace(/\/+$/, "").split("/");
- let relativeSegments = relativePath.split("/");
- relativeSegments.forEach(segment => {
- if (segment === "..") {
- // Keep the root "" segment so the pathname starts at /
- if (segments.length > 1) segments.pop();
- } else if (segment !== ".") {
- segments.push(segment);
- }
- });
- return segments.length > 1 ? segments.join("/") : "/";
-}
-function getInvalidPathError(char, field, dest, path) {
- 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 and the router will parse it for you.";
-}
-/**
- * @private
- *
- * When processing relative navigation we want to ignore ancestor routes that
- * do not contribute to the path, such that index/pathless layout routes don't
- * interfere.
- *
- * For example, when moving a route element into an index route and/or a
- * pathless layout route, relative link behavior contained within should stay
- * the same. Both of the following examples should link back to the root:
- *
- *
- *
- *
- *
- *
- *
- * }> // <-- Does not contribute
- * // <-- Does not contribute
- *
- *
- */
-function getPathContributingMatches(matches) {
- return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
-}
-/**
- * @private
- */
-function router_resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
- if (isPathRelative === void 0) {
- isPathRelative = false;
- }
- let to;
- if (typeof toArg === "string") {
- to = parsePath(toArg);
- } else {
- to = _extends({}, toArg);
- invariant(!to.pathname || !to.pathname.includes("?"), getInvalidPathError("?", "pathname", "search", to));
- invariant(!to.pathname || !to.pathname.includes("#"), getInvalidPathError("#", "pathname", "hash", to));
- invariant(!to.search || !to.search.includes("#"), getInvalidPathError("#", "search", "hash", to));
- }
- let isEmptyPath = toArg === "" || to.pathname === "";
- let toPathname = isEmptyPath ? "/" : to.pathname;
- let from;
- // Routing is relative to the current pathname if explicitly requested.
- //
- // If a pathname is explicitly provided in `to`, it should be relative to the
- // route context. This is explained in `Note on `` values` in our
- // migration guide from v5 as a means of disambiguation between `to` values
- // that begin with `/` and those that do not. However, this is problematic for
- // `to` values that do not provide a pathname. `to` can simply be a search or
- // hash string, in which case we should assume that the navigation is relative
- // to the current location's pathname and *not* the route pathname.
- if (isPathRelative || toPathname == null) {
- from = locationPathname;
- } else {
- let routePathnameIndex = routePathnames.length - 1;
- if (toPathname.startsWith("..")) {
- let toSegments = toPathname.split("/");
- // Each leading .. segment means "go up one route" instead of "go up one
- // URL segment". This is a key difference from how works and a
- // major reason we call this a "to" value instead of a "href".
- while (toSegments[0] === "..") {
- toSegments.shift();
- routePathnameIndex -= 1;
- }
- to.pathname = toSegments.join("/");
- }
- // If there are more ".." segments than parent routes, resolve relative to
- // the root / URL.
- from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
- }
- let path = resolvePath(to, from);
- // Ensure the pathname has a trailing slash if the original "to" had one
- let hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/");
- // Or if this was a link to the current path which has a trailing slash
- let hasCurrentTrailingSlash = (isEmptyPath || toPathname === ".") && locationPathname.endsWith("/");
- if (!path.pathname.endsWith("/") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) {
- path.pathname += "/";
- }
- return path;
-}
-/**
- * @private
- */
-function getToPathname(to) {
- // Empty strings should be treated the same as / paths
- return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
-}
-/**
- * @private
- */
-const router_joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
-/**
- * @private
- */
-const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
-/**
- * @private
- */
-const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
-/**
- * @private
- */
-const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
-/**
- * This is a shortcut for creating `application/json` responses. Converts `data`
- * to JSON and sets the `Content-Type` header.
- */
-const json = function json(data, init) {
- if (init === void 0) {
- init = {};
- }
- let responseInit = typeof init === "number" ? {
- status: init
- } : init;
- let headers = new Headers(responseInit.headers);
- if (!headers.has("Content-Type")) {
- headers.set("Content-Type", "application/json; charset=utf-8");
- }
- return new Response(JSON.stringify(data), _extends({}, responseInit, {
- headers
- }));
-};
-class AbortedDeferredError extends Error {}
-class DeferredData {
- constructor(data, responseInit) {
- this.pendingKeysSet = new Set();
- this.subscribers = new Set();
- this.deferredKeys = [];
- invariant(data && typeof data === "object" && !Array.isArray(data), "defer() only accepts plain objects");
- // Set up an AbortController + Promise we can race against to exit early
- // cancellation
- let reject;
- this.abortPromise = new Promise((_, r) => reject = r);
- this.controller = new AbortController();
- let onAbort = () => reject(new AbortedDeferredError("Deferred data aborted"));
- this.unlistenAbortSignal = () => this.controller.signal.removeEventListener("abort", onAbort);
- this.controller.signal.addEventListener("abort", onAbort);
- this.data = Object.entries(data).reduce((acc, _ref) => {
- let [key, value] = _ref;
- return Object.assign(acc, {
- [key]: this.trackPromise(key, value)
- });
- }, {});
- if (this.done) {
- // All incoming values were resolved
- this.unlistenAbortSignal();
- }
- this.init = responseInit;
- }
- trackPromise(key, value) {
- if (!(value instanceof Promise)) {
- return value;
- }
- this.deferredKeys.push(key);
- this.pendingKeysSet.add(key);
- // We store a little wrapper promise that will be extended with
- // _data/_error props upon resolve/reject
- let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, null, data), error => this.onSettle(promise, key, error));
- // Register rejection listeners to avoid uncaught promise rejections on
- // errors or aborted deferred values
- promise.catch(() => {});
- Object.defineProperty(promise, "_tracked", {
- get: () => true
- });
- return promise;
- }
- onSettle(promise, key, error, data) {
- if (this.controller.signal.aborted && error instanceof AbortedDeferredError) {
- this.unlistenAbortSignal();
- Object.defineProperty(promise, "_error", {
- get: () => error
- });
- return Promise.reject(error);
- }
- this.pendingKeysSet.delete(key);
- if (this.done) {
- // Nothing left to abort!
- this.unlistenAbortSignal();
- }
- if (error) {
- Object.defineProperty(promise, "_error", {
- get: () => error
- });
- this.emit(false, key);
- return Promise.reject(error);
- }
- Object.defineProperty(promise, "_data", {
- get: () => data
- });
- this.emit(false, key);
- return data;
- }
- emit(aborted, settledKey) {
- this.subscribers.forEach(subscriber => subscriber(aborted, settledKey));
- }
- subscribe(fn) {
- this.subscribers.add(fn);
- return () => this.subscribers.delete(fn);
- }
- cancel() {
- this.controller.abort();
- this.pendingKeysSet.forEach((v, k) => this.pendingKeysSet.delete(k));
- this.emit(true);
- }
- async resolveData(signal) {
- let aborted = false;
- if (!this.done) {
- let onAbort = () => this.cancel();
- signal.addEventListener("abort", onAbort);
- aborted = await new Promise(resolve => {
- this.subscribe(aborted => {
- signal.removeEventListener("abort", onAbort);
- if (aborted || this.done) {
- resolve(aborted);
- }
- });
- });
- }
- return aborted;
- }
- get done() {
- return this.pendingKeysSet.size === 0;
- }
- get unwrappedData() {
- invariant(this.data !== null && this.done, "Can only unwrap data on initialized and settled deferreds");
- return Object.entries(this.data).reduce((acc, _ref2) => {
- let [key, value] = _ref2;
- return Object.assign(acc, {
- [key]: unwrapTrackedPromise(value)
- });
- }, {});
- }
- get pendingKeys() {
- return Array.from(this.pendingKeysSet);
- }
-}
-function isTrackedPromise(value) {
- return value instanceof Promise && value._tracked === true;
-}
-function unwrapTrackedPromise(value) {
- if (!isTrackedPromise(value)) {
- return value;
- }
- if (value._error) {
- throw value._error;
- }
- return value._data;
-}
-const defer = function defer(data, init) {
- if (init === void 0) {
- init = {};
- }
- let responseInit = typeof init === "number" ? {
- status: init
- } : init;
- return new DeferredData(data, responseInit);
-};
-/**
- * A redirect response. Sets the status code and the `Location` header.
- * Defaults to "302 Found".
- */
-const redirect = function redirect(url, init) {
- if (init === void 0) {
- init = 302;
- }
- let responseInit = init;
- if (typeof responseInit === "number") {
- responseInit = {
- status: responseInit
- };
- } else if (typeof responseInit.status === "undefined") {
- responseInit.status = 302;
- }
- let headers = new Headers(responseInit.headers);
- headers.set("Location", url);
- return new Response(null, _extends({}, responseInit, {
- headers
- }));
-};
-/**
- * @private
- * Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
- */
-class ErrorResponse {
- constructor(status, statusText, data, internal) {
- if (internal === void 0) {
- internal = false;
- }
- this.status = status;
- this.statusText = statusText || "";
- this.internal = internal;
- if (data instanceof Error) {
- this.data = data.toString();
- this.error = data;
- } else {
- this.data = data;
- }
- }
-}
-/**
- * Check if the given error is an ErrorResponse generated from a 4xx/5xx
- * Response thrown from an action/loader
- */
-function isRouteErrorResponse(error) {
- return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
-}
-
-const validMutationMethodsArr = ["post", "put", "patch", "delete"];
-const validMutationMethods = new Set(validMutationMethodsArr);
-const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
-const validRequestMethods = new Set(validRequestMethodsArr);
-const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
-const redirectPreserveMethodStatusCodes = new Set([307, 308]);
-const IDLE_NAVIGATION = {
- state: "idle",
- location: undefined,
- formMethod: undefined,
- formAction: undefined,
- formEncType: undefined,
- formData: undefined,
- json: undefined,
- text: undefined
-};
-const IDLE_FETCHER = {
- state: "idle",
- data: undefined,
- formMethod: undefined,
- formAction: undefined,
- formEncType: undefined,
- formData: undefined,
- json: undefined,
- text: undefined
-};
-const router_IDLE_BLOCKER = {
- state: "unblocked",
- proceed: undefined,
- reset: undefined,
- location: undefined
-};
-const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
-const defaultMapRouteProperties = route => ({
- hasErrorBoundary: Boolean(route.hasErrorBoundary)
-});
-//#endregion
-////////////////////////////////////////////////////////////////////////////////
-//#region createRouter
-////////////////////////////////////////////////////////////////////////////////
-/**
- * Create a router and listen to history POP navigations
- */
-function router_createRouter(init) {
- const routerWindow = init.window ? init.window : typeof window !== "undefined" ? window : undefined;
- const isBrowser = typeof routerWindow !== "undefined" && typeof routerWindow.document !== "undefined" && typeof routerWindow.document.createElement !== "undefined";
- const isServer = !isBrowser;
- invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
- let mapRouteProperties;
- if (init.mapRouteProperties) {
- mapRouteProperties = init.mapRouteProperties;
- } else if (init.detectErrorBoundary) {
- // If they are still using the deprecated version, wrap it with the new API
- let detectErrorBoundary = init.detectErrorBoundary;
- mapRouteProperties = route => ({
- hasErrorBoundary: detectErrorBoundary(route)
- });
- } else {
- mapRouteProperties = defaultMapRouteProperties;
- }
- // Routes keyed by ID
- let manifest = {};
- // Routes in tree format for matching
- let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
- let inFlightDataRoutes;
- let basename = init.basename || "/";
- // Config driven behavior flags
- let future = _extends({
- v7_normalizeFormMethod: false,
- v7_prependBasename: false
- }, init.future);
- // Cleanup function for history
- let unlistenHistory = null;
- // Externally-provided functions to call on all state changes
- let subscribers = new Set();
- // Externally-provided object to hold scroll restoration locations during routing
- let savedScrollPositions = null;
- // Externally-provided function to get scroll restoration keys
- let getScrollRestorationKey = null;
- // Externally-provided function to get current scroll position
- let getScrollPosition = null;
- // One-time flag to control the initial hydration scroll restoration. Because
- // we don't get the saved positions from until _after_
- // the initial render, we need to manually trigger a separate updateState to
- // send along the restoreScrollPosition
- // Set to true if we have `hydrationData` since we assume we were SSR'd and that
- // SSR did the initial scroll restoration.
- let initialScrollRestored = init.hydrationData != null;
- let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
- let initialErrors = null;
- if (initialMatches == null) {
- // If we do not match a user-provided-route, fall back to the root
- // to allow the error boundary to take over
- let error = getInternalRouterError(404, {
- pathname: init.history.location.pathname
- });
- let {
- matches,
- route
- } = getShortCircuitMatches(dataRoutes);
- initialMatches = matches;
- initialErrors = {
- [route.id]: error
- };
- }
- let initialized =
- // All initialMatches need to be loaded before we're ready. If we have lazy
- // functions around still then we'll need to run them in initialize()
- !initialMatches.some(m => m.route.lazy) && (
- // And we have to either have no loaders or have been provided hydrationData
- !initialMatches.some(m => m.route.loader) || init.hydrationData != null);
- let router;
- let state = {
- historyAction: init.history.action,
- location: init.history.location,
- matches: initialMatches,
- initialized,
- navigation: IDLE_NAVIGATION,
- // Don't restore on initial updateState() if we were SSR'd
- restoreScrollPosition: init.hydrationData != null ? false : null,
- preventScrollReset: false,
- revalidation: "idle",
- loaderData: init.hydrationData && init.hydrationData.loaderData || {},
- actionData: init.hydrationData && init.hydrationData.actionData || null,
- errors: init.hydrationData && init.hydrationData.errors || initialErrors,
- fetchers: new Map(),
- blockers: new Map()
- };
- // -- Stateful internal variables to manage navigations --
- // Current navigation in progress (to be committed in completeNavigation)
- let pendingAction = Action.Pop;
- // Should the current navigation prevent the scroll reset if scroll cannot
- // be restored?
- let pendingPreventScrollReset = false;
- // AbortController for the active navigation
- let pendingNavigationController;
- // We use this to avoid touching history in completeNavigation if a
- // revalidation is entirely uninterrupted
- let isUninterruptedRevalidation = false;
- // Use this internal flag to force revalidation of all loaders:
- // - submissions (completed or interrupted)
- // - useRevalidator()
- // - X-Remix-Revalidate (from redirect)
- let isRevalidationRequired = false;
- // Use this internal array to capture routes that require revalidation due
- // to a cancelled deferred on action submission
- let cancelledDeferredRoutes = [];
- // Use this internal array to capture fetcher loads that were cancelled by an
- // action navigation and require revalidation
- let cancelledFetcherLoads = [];
- // AbortControllers for any in-flight fetchers
- let fetchControllers = new Map();
- // Track loads based on the order in which they started
- let incrementingLoadId = 0;
- // Track the outstanding pending navigation data load to be compared against
- // the globally incrementing load when a fetcher load lands after a completed
- // navigation
- let pendingNavigationLoadId = -1;
- // Fetchers that triggered data reloads as a result of their actions
- let fetchReloadIds = new Map();
- // Fetchers that triggered redirect navigations
- let fetchRedirectIds = new Set();
- // Most recent href/match for fetcher.load calls for fetchers
- let fetchLoadMatches = new Map();
- // Store DeferredData instances for active route matches. When a
- // route loader returns defer() we stick one in here. Then, when a nested
- // promise resolves we update loaderData. If a new navigation starts we
- // cancel active deferreds for eliminated routes.
- let activeDeferreds = new Map();
- // Store blocker functions in a separate Map outside of router state since
- // we don't need to update UI state if they change
- let blockerFunctions = new Map();
- // Flag to ignore the next history update, so we can revert the URL change on
- // a POP navigation that was blocked by the user without touching router state
- let ignoreNextHistoryUpdate = false;
- // Initialize the router, all side effects should be kicked off from here.
- // Implemented as a Fluent API for ease of:
- // let router = createRouter(init).initialize();
- function initialize() {
- // If history informs us of a POP navigation, start the navigation but do not update
- // state. We'll update our own state once the navigation completes
- unlistenHistory = init.history.listen(_ref => {
- let {
- action: historyAction,
- location,
- delta
- } = _ref;
- // Ignore this event if it was just us resetting the URL from a
- // blocked POP navigation
- if (ignoreNextHistoryUpdate) {
- ignoreNextHistoryUpdate = false;
- return;
- }
- 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.");
- let blockerKey = shouldBlockNavigation({
- currentLocation: state.location,
- nextLocation: location,
- historyAction
- });
- if (blockerKey && delta != null) {
- // Restore the URL to match the current UI, but don't update router state
- ignoreNextHistoryUpdate = true;
- init.history.go(delta * -1);
- // Put the blocker into a blocked state
- updateBlocker(blockerKey, {
- state: "blocked",
- location,
- proceed() {
- updateBlocker(blockerKey, {
- state: "proceeding",
- proceed: undefined,
- reset: undefined,
- location
- });
- // Re-do the same POP navigation we just blocked
- init.history.go(delta);
- },
- reset() {
- let blockers = new Map(state.blockers);
- blockers.set(blockerKey, router_IDLE_BLOCKER);
- updateState({
- blockers
- });
- }
- });
- return;
- }
- return startNavigation(historyAction, location);
- });
- // Kick off initial data load if needed. Use Pop to avoid modifying history
- // Note we don't do any handling of lazy here. For SPA's it'll get handled
- // in the normal navigation flow. For SSR it's expected that lazy modules are
- // resolved prior to router creation since we can't go into a fallbackElement
- // UI for SSR'd apps
- if (!state.initialized) {
- startNavigation(Action.Pop, state.location);
- }
- return router;
- }
- // Clean up a router and it's side effects
- function dispose() {
- if (unlistenHistory) {
- unlistenHistory();
- }
- subscribers.clear();
- pendingNavigationController && pendingNavigationController.abort();
- state.fetchers.forEach((_, key) => deleteFetcher(key));
- state.blockers.forEach((_, key) => deleteBlocker(key));
- }
- // Subscribe to state updates for the router
- function subscribe(fn) {
- subscribers.add(fn);
- return () => subscribers.delete(fn);
- }
- // Update our state and notify the calling context of the change
- function updateState(newState) {
- state = _extends({}, state, newState);
- subscribers.forEach(subscriber => subscriber(state));
- }
- // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
- // and setting state.[historyAction/location/matches] to the new route.
- // - Location is a required param
- // - Navigation will always be set to IDLE_NAVIGATION
- // - Can pass any other state in newState
- function completeNavigation(location, newState) {
- var _location$state, _location$state2;
- // Deduce if we're in a loading/actionReload state:
- // - We have committed actionData in the store
- // - The current navigation was a mutation submission
- // - We're past the submitting state and into the loading state
- // - The location being loaded is not the result of a redirect
- 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;
- let actionData;
- if (newState.actionData) {
- if (Object.keys(newState.actionData).length > 0) {
- actionData = newState.actionData;
- } else {
- // Empty actionData -> clear prior actionData due to an action error
- actionData = null;
- }
- } else if (isActionReload) {
- // Keep the current data if we're wrapping up the action reload
- actionData = state.actionData;
- } else {
- // Clear actionData on any other completed navigations
- actionData = null;
- }
- // Always preserve any existing loaderData from re-used routes
- let loaderData = newState.loaderData ? mergeLoaderData(state.loaderData, newState.loaderData, newState.matches || [], newState.errors) : state.loaderData;
- // On a successful navigation we can assume we got through all blockers
- // so we can start fresh
- let blockers = new Map();
- blockerFunctions.clear();
- // Always respect the user flag. Otherwise don't reset on mutation
- // submission navigations unless they redirect
- let preventScrollReset = pendingPreventScrollReset === true || state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && ((_location$state2 = location.state) == null ? void 0 : _location$state2._isRedirect) !== true;
- if (inFlightDataRoutes) {
- dataRoutes = inFlightDataRoutes;
- inFlightDataRoutes = undefined;
- }
- if (isUninterruptedRevalidation) ; else if (pendingAction === Action.Pop) ; else if (pendingAction === Action.Push) {
- init.history.push(location, location.state);
- } else if (pendingAction === Action.Replace) {
- init.history.replace(location, location.state);
- }
- updateState(_extends({}, newState, {
- actionData,
- loaderData,
- historyAction: pendingAction,
- location,
- initialized: true,
- navigation: IDLE_NAVIGATION,
- revalidation: "idle",
- restoreScrollPosition: getSavedScrollPosition(location, newState.matches || state.matches),
- preventScrollReset,
- blockers
- }));
- // Reset stateful navigation vars
- pendingAction = Action.Pop;
- pendingPreventScrollReset = false;
- isUninterruptedRevalidation = false;
- isRevalidationRequired = false;
- cancelledDeferredRoutes = [];
- cancelledFetcherLoads = [];
- }
- // Trigger a navigation event, which can either be a numerical POP or a PUSH
- // replace with an optional submission
- async function navigate(to, opts) {
- if (typeof to === "number") {
- init.history.go(to);
- return;
- }
- let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, to, opts == null ? void 0 : opts.fromRouteId, opts == null ? void 0 : opts.relative);
- let {
- path,
- submission,
- error
- } = normalizeNavigateOptions(future.v7_normalizeFormMethod, false, normalizedPath, opts);
- let currentLocation = state.location;
- let nextLocation = createLocation(state.location, path, opts && opts.state);
- // When using navigate as a PUSH/REPLACE we aren't reading an already-encoded
- // URL from window.location, so we need to encode it here so the behavior
- // remains the same as POP and non-data-router usages. new URL() does all
- // the same encoding we'd get from a history.pushState/window.location read
- // without having to touch history
- nextLocation = _extends({}, nextLocation, init.history.encodeLocation(nextLocation));
- let userReplace = opts && opts.replace != null ? opts.replace : undefined;
- let historyAction = Action.Push;
- if (userReplace === true) {
- historyAction = Action.Replace;
- } else if (userReplace === false) ; else if (submission != null && isMutationMethod(submission.formMethod) && submission.formAction === state.location.pathname + state.location.search) {
- // By default on submissions to the current location we REPLACE so that
- // users don't have to double-click the back button to get to the prior
- // location. If the user redirects to a different location from the
- // action/loader this will be ignored and the redirect will be a PUSH
- historyAction = Action.Replace;
- }
- let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
- let blockerKey = shouldBlockNavigation({
- currentLocation,
- nextLocation,
- historyAction
- });
- if (blockerKey) {
- // Put the blocker into a blocked state
- updateBlocker(blockerKey, {
- state: "blocked",
- location: nextLocation,
- proceed() {
- updateBlocker(blockerKey, {
- state: "proceeding",
- proceed: undefined,
- reset: undefined,
- location: nextLocation
- });
- // Send the same navigation through
- navigate(to, opts);
- },
- reset() {
- let blockers = new Map(state.blockers);
- blockers.set(blockerKey, router_IDLE_BLOCKER);
- updateState({
- blockers
- });
- }
- });
- return;
- }
- return await startNavigation(historyAction, nextLocation, {
- submission,
- // Send through the formData serialization error if we have one so we can
- // render at the right error boundary after we match routes
- pendingError: error,
- preventScrollReset,
- replace: opts && opts.replace
- });
- }
- // Revalidate all current loaders. If a navigation is in progress or if this
- // is interrupted by a navigation, allow this to "succeed" by calling all
- // loaders during the next loader round
- function revalidate() {
- interruptActiveLoads();
- updateState({
- revalidation: "loading"
- });
- // If we're currently submitting an action, we don't need to start a new
- // navigation, we'll just let the follow up loader execution call all loaders
- if (state.navigation.state === "submitting") {
- return;
- }
- // If we're currently in an idle state, start a new navigation for the current
- // action/location and mark it as uninterrupted, which will skip the history
- // update in completeNavigation
- if (state.navigation.state === "idle") {
- startNavigation(state.historyAction, state.location, {
- startUninterruptedRevalidation: true
- });
- return;
- }
- // Otherwise, if we're currently in a loading state, just start a new
- // navigation to the navigation.location but do not trigger an uninterrupted
- // revalidation so that history correctly updates once the navigation completes
- startNavigation(pendingAction || state.historyAction, state.navigation.location, {
- overrideNavigation: state.navigation
- });
- }
- // Start a navigation to the given action/location. Can optionally provide a
- // overrideNavigation which will override the normalLoad in the case of a redirect
- // navigation
- async function startNavigation(historyAction, location, opts) {
- // Abort any in-progress navigations and start a new one. Unset any ongoing
- // uninterrupted revalidations unless told otherwise, since we want this
- // new navigation to update history normally
- pendingNavigationController && pendingNavigationController.abort();
- pendingNavigationController = null;
- pendingAction = historyAction;
- isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true;
- // Save the current scroll position every time we start a new navigation,
- // and track whether we should reset scroll on completion
- saveScrollPosition(state.location, state.matches);
- pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
- let routesToUse = inFlightDataRoutes || dataRoutes;
- let loadingNavigation = opts && opts.overrideNavigation;
- let matches = matchRoutes(routesToUse, location, basename);
- // Short circuit with a 404 on the root error boundary if we match nothing
- if (!matches) {
- let error = getInternalRouterError(404, {
- pathname: location.pathname
- });
- let {
- matches: notFoundMatches,
- route
- } = getShortCircuitMatches(routesToUse);
- // Cancel all pending deferred on 404s since we don't keep any routes
- cancelActiveDeferreds();
- completeNavigation(location, {
- matches: notFoundMatches,
- loaderData: {},
- errors: {
- [route.id]: error
- }
- });
- return;
- }
- // Short circuit if it's only a hash change and not a revalidation or
- // mutation submission.
- //
- // Ignore on initial page loads because since the initial load will always
- // be "same hash". For example, on /page#hash and submit a