UNPKG

react-router

Version:
1,523 lines (1,519 loc) • 335 kB
/** * react-router v7.4.1 * * 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 */ var __typeError = (msg) => { throw TypeError(msg); }; var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); // lib/router/history.ts var Action = /* @__PURE__ */ ((Action2) => { Action2["Pop"] = "POP"; Action2["Push"] = "PUSH"; Action2["Replace"] = "REPLACE"; return Action2; })(Action || {}); var PopStateEventType = "popstate"; function createMemoryHistory(options = {}) { let { initialEntries = ["/"], initialIndex, v5Compat = false } = options; let entries; entries = initialEntries.map( (entry, index2) => createMemoryLocation( entry, typeof entry === "string" ? null : entry.state, index2 === 0 ? "default" : void 0 ) ); let index = clampIndex( initialIndex == null ? entries.length - 1 : initialIndex ); let action = "POP" /* 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 = null, key) { 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 createHref2(to) { return typeof to === "string" ? to : createPath(to); } let history = { get index() { return index; }, get action() { return action; }, get location() { return getCurrentLocation(); }, createHref: createHref2, createURL(to) { return new URL(createHref2(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 = "PUSH" /* 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 = "REPLACE" /* Replace */; let nextLocation = createMemoryLocation(to, state); entries[index] = nextLocation; if (v5Compat && listener) { listener({ action, location: nextLocation, delta: 0 }); } }, go(delta) { action = "POP" /* 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; } function createBrowserHistory(options = {}) { function createBrowserLocation(window2, globalHistory) { let { pathname, search, hash } = window2.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(window2, to) { return typeof to === "string" ? to : createPath(to); } return getUrlBasedHistory( createBrowserLocation, createBrowserHref, null, options ); } function createHashHistory(options = {}) { function createHashLocation(window2, globalHistory) { let { pathname = "/", search = "", hash = "" } = parsePath(window2.location.hash.substring(1)); if (!pathname.startsWith("/") && !pathname.startsWith(".")) { pathname = "/" + pathname; } 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(window2, to) { let base = window2.document.querySelector("base"); let href2 = ""; if (base && base.getAttribute("href")) { let url = window2.location.href; let hashIndex = url.indexOf("#"); href2 = hashIndex === -1 ? url : url.slice(0, hashIndex); } return href2 + "#" + (typeof to === "string" ? to : 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) { if (typeof console !== "undefined") console.warn(message); try { throw new Error(message); } catch (e) { } } } function createKey() { return Math.random().toString(36).substring(2, 10); } function getHistoryState(location, index) { return { usr: location.state, key: location.key, idx: index }; } function createLocation(current, to, state = null, key) { let location = { 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; } function createPath({ pathname = "/", search = "", hash = "" }) { if (search && search !== "?") pathname += search.charAt(0) === "?" ? search : "?" + search; if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : "#" + hash; return pathname; } function parsePath(path) { let parsedPath = {}; if (path) { let hashIndex = path.indexOf("#"); if (hashIndex >= 0) { parsedPath.hash = path.substring(hashIndex); path = path.substring(0, hashIndex); } let searchIndex = path.indexOf("?"); if (searchIndex >= 0) { parsedPath.search = path.substring(searchIndex); path = path.substring(0, searchIndex); } if (path) { parsedPath.pathname = path; } } return parsedPath; } function getUrlBasedHistory(getLocation, createHref2, validateLocation, options = {}) { let { window: window2 = document.defaultView, v5Compat = false } = options; let globalHistory = window2.history; let action = "POP" /* Pop */; let listener = null; let index = getIndex(); if (index == null) { index = 0; globalHistory.replaceState({ ...globalHistory.state, idx: index }, ""); } function getIndex() { let state = globalHistory.state || { idx: null }; return state.idx; } function handlePop() { action = "POP" /* 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 = "PUSH" /* 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 { globalHistory.pushState(historyState, "", url); } catch (error) { if (error instanceof DOMException && error.name === "DataCloneError") { throw error; } window2.location.assign(url); } if (v5Compat && listener) { listener({ action, location: history.location, delta: 1 }); } } function replace2(to, state) { action = "REPLACE" /* 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) { let base = window2.location.origin !== "null" ? window2.location.origin : window2.location.href; let href2 = typeof to === "string" ? to : createPath(to); href2 = href2.replace(/ $/, "%20"); invariant( base, `No window.location.(origin|href) available to create URL for href: ${href2}` ); return new URL(href2, base); } let history = { get action() { return action; }, get location() { return getLocation(window2, globalHistory); }, listen(fn) { if (listener) { throw new Error("A history only accepts one active listener"); } window2.addEventListener(PopStateEventType, handlePop); listener = fn; return () => { window2.removeEventListener(PopStateEventType, handlePop); listener = null; }; }, createHref(to) { return createHref2(window2, to); }, createURL, encodeLocation(to) { let url = createURL(to); return { pathname: url.pathname, search: url.search, hash: url.hash }; }, push, replace: replace2, go(n) { return globalHistory.go(n); } }; return history; } // lib/router/utils.ts function unstable_createContext(defaultValue) { return { defaultValue }; } var _map; var unstable_RouterContextProvider = class { constructor(init) { __privateAdd(this, _map, /* @__PURE__ */ new Map()); if (init) { for (let [context, value] of init) { this.set(context, value); } } } get(context) { if (__privateGet(this, _map).has(context)) { return __privateGet(this, _map).get(context); } if (context.defaultValue !== void 0) { return context.defaultValue; } throw new Error("No value found for context"); } set(context, value) { __privateGet(this, _map).set(context, value); } }; _map = new WeakMap(); var unsupportedLazyRouteFunctionKeys = /* @__PURE__ */ new Set([ "lazy", "caseSensitive", "path", "id", "index", "unstable_middleware", "unstable_lazyMiddleware", "children" ]); function isIndexRoute(route) { return route.index === true; } function convertRoutesToDataRoutes(routes, mapRouteProperties2, parentPath = [], manifest = {}) { return routes.map((route, index) => { let treePath = [...parentPath, String(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 = { ...route, ...mapRouteProperties2(route), id }; manifest[id] = indexRoute; return indexRoute; } else { let pathOrLayoutRoute = { ...route, ...mapRouteProperties2(route), id, children: void 0 }; manifest[id] = pathOrLayoutRoute; if (route.children) { pathOrLayoutRoute.children = convertRoutesToDataRoutes( route.children, mapRouteProperties2, treePath, manifest ); } return pathOrLayoutRoute; } }); } function matchRoutes(routes, locationArg, basename = "/") { return matchRoutesImpl(routes, locationArg, basename, false); } function matchRoutesImpl(routes, locationArg, basename, allowPartial) { let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg; let pathname = 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) { let decoded = decodePath(pathname); matches = matchRouteBranch( branches[i], decoded, allowPartial ); } return matches; } function convertRouteMatchToUiMatch(match, loaderData) { let { route, pathname, params } = match; return { id: route.id, pathname, params, data: loaderData[route.id], handle: route.handle }; } function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") { let flattenRoute = (route, index, relativePath) => { let meta = { relativePath: relativePath === void 0 ? 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 = joinPaths([parentPath, meta.relativePath]); let routesMeta = parentsMeta.concat(meta); 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); } if (route.path == null && !route.index) { return; } branches.push({ path, score: computeScore(path, route.index), routesMeta }); }; routes.forEach((route, index) => { if (route.path === "" || !route.path?.includes("?")) { flattenRoute(route, index); } else { for (let exploded of explodeOptionalSegments(route.path)) { flattenRoute(route, index, exploded); } } }); return branches; } function explodeOptionalSegments(path) { let segments = path.split("/"); if (segments.length === 0) return []; let [first, ...rest] = segments; let isOptional = first.endsWith("?"); let required = first.replace(/\?$/, ""); if (rest.length === 0) { return isOptional ? [required, ""] : [required]; } let restExploded = explodeOptionalSegments(rest.join("/")); let result = []; result.push( ...restExploded.map( (subpath) => subpath === "" ? required : [required, subpath].join("/") ) ); if (isOptional) { result.push(...restExploded); } return result.map( (exploded) => path.startsWith("/") && exploded === "" ? "/" : exploded ); } function rankRouteBranches(branches) { branches.sort( (a, b) => a.score !== b.score ? b.score - a.score : compareIndexes( a.routesMeta.map((meta) => meta.childrenIndex), b.routesMeta.map((meta) => meta.childrenIndex) ) ); } var paramRe = /^:[\w-]+$/; var dynamicSegmentValue = 3; var indexRouteValue = 2; var emptySegmentValue = 1; var staticSegmentValue = 10; var splatPenalty = -2; var 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, allowPartial = false) { 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 = matchPath( { path: meta.relativePath, caseSensitive: meta.caseSensitive, end }, remainingPathname ); let route = meta.route; if (!match && end && allowPartial && !routesMeta[routesMeta.length - 1].route.index) { match = matchPath( { path: meta.relativePath, caseSensitive: meta.caseSensitive, end: false }, remainingPathname ); } if (!match) { return null; } Object.assign(matchedParams, match.params); matches.push({ // TODO: Can this as be avoided? params: matchedParams, pathname: joinPaths([matchedPathname, match.pathname]), pathnameBase: normalizePathname( joinPaths([matchedPathname, match.pathnameBase]) ), route }); if (match.pathnameBase !== "/") { matchedPathname = joinPaths([matchedPathname, match.pathnameBase]); } } return matches; } function generatePath(originalPath, 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(/\*$/, "/*"); } 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; if (isLastSegment && segment === "*") { const star = "*"; 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); } return segment.replace(/\?$/g, ""); }).filter((segment) => !!segment); return prefix + segments.join("/"); } function matchPath(pattern, pathname) { if (typeof pattern === "string") { pattern = { path: pattern, caseSensitive: false, end: true }; } let [matcher, compiledParams] = 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 = compiledParams.reduce( (memo2, { paramName, isOptional }, index) => { if (paramName === "*") { let splatValue = captureGroups[index] || ""; pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1"); } const value = captureGroups[index]; if (isOptional && !value) { memo2[paramName] = void 0; } else { memo2[paramName] = (value || "").replace(/%2F/g, "/"); } return memo2; }, {} ); return { params, pathname: matchedPathname, pathnameBase, pattern }; } function compilePath(path, caseSensitive = false, 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 params = []; let regexpSource = "^" + path.replace(/\/*\*?$/, "").replace(/^\/*/, "/").replace(/[\\.*+^${}|()[\]]/g, "\\$&").replace( /\/:([\w-]+)(\?)?/g, (_, paramName, isOptional) => { params.push({ paramName, isOptional: isOptional != null }); return isOptional ? "/?([^\\/]+)?" : "/([^\\/]+)"; } ); if (path.endsWith("*")) { params.push({ paramName: "*" }); regexpSource += path === "*" || path === "/*" ? "(.*)$" : "(?:\\/(.+)|\\/*)$"; } else if (end) { regexpSource += "\\/*$"; } else if (path !== "" && path !== "/") { regexpSource += "(?:(?=\\/|$))"; } else { } let matcher = new RegExp(regexpSource, caseSensitive ? void 0 : "i"); return [matcher, params]; } function decodePath(value) { try { return value.split("/").map((v) => decodeURIComponent(v).replace(/\//g, "%2F")).join("/"); } catch (error) { warning( false, `The URL path "${value}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${error}).` ); return value; } } function stripBasename(pathname, basename) { if (basename === "/") return pathname; if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) { return null; } let startIndex = basename.endsWith("/") ? basename.length - 1 : basename.length; let nextChar = pathname.charAt(startIndex); if (nextChar && nextChar !== "/") { return null; } return pathname.slice(startIndex) || "/"; } function resolvePath(to, 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 === "..") { 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 <Link to="..."> and the router will parse it for you.`; } function getPathContributingMatches(matches) { return matches.filter( (match, index) => index === 0 || match.route.path && match.route.path.length > 0 ); } function getResolveToMatches(matches) { let pathMatches = getPathContributingMatches(matches); return pathMatches.map( (match, idx) => idx === pathMatches.length - 1 ? match.pathname : match.pathnameBase ); } function resolveTo(toArg, routePathnames, locationPathname, isPathRelative = false) { let to; if (typeof toArg === "string") { to = parsePath(toArg); } else { to = { ...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; if (toPathname == null) { from = locationPathname; } else { let routePathnameIndex = routePathnames.length - 1; if (!isPathRelative && toPathname.startsWith("..")) { let toSegments = toPathname.split("/"); while (toSegments[0] === "..") { toSegments.shift(); routePathnameIndex -= 1; } to.pathname = toSegments.join("/"); } from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/"; } let path = resolvePath(to, from); let hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/"); let hasCurrentTrailingSlash = (isEmptyPath || toPathname === ".") && locationPathname.endsWith("/"); if (!path.pathname.endsWith("/") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) { path.pathname += "/"; } return path; } var joinPaths = (paths) => paths.join("/").replace(/\/\/+/g, "/"); var normalizePathname = (pathname) => pathname.replace(/\/+$/, "").replace(/^\/*/, "/"); var normalizeSearch = (search) => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search; var normalizeHash = (hash) => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash; var DataWithResponseInit = class { constructor(data2, init) { this.type = "DataWithResponseInit"; this.data = data2; this.init = init || null; } }; function data(data2, init) { return new DataWithResponseInit( data2, typeof init === "number" ? { status: init } : init ); } var redirect = (url, 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, { ...responseInit, headers }); }; var redirectDocument = (url, init) => { let response = redirect(url, init); response.headers.set("X-Remix-Reload-Document", "true"); return response; }; var replace = (url, init) => { let response = redirect(url, init); response.headers.set("X-Remix-Replace", "true"); return response; }; var ErrorResponseImpl = class { constructor(status, statusText, data2, internal = false) { this.status = status; this.statusText = statusText || ""; this.internal = internal; if (data2 instanceof Error) { this.data = data2.toString(); this.error = data2; } else { this.data = data2; } } }; function isRouteErrorResponse(error) { return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error; } // lib/router/router.ts var validMutationMethodsArr = [ "POST", "PUT", "PATCH", "DELETE" ]; var validMutationMethods = new Set( validMutationMethodsArr ); var validRequestMethodsArr = [ "GET", ...validMutationMethodsArr ]; var validRequestMethods = new Set(validRequestMethodsArr); var redirectStatusCodes = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]); var redirectPreserveMethodStatusCodes = /* @__PURE__ */ new Set([307, 308]); var IDLE_NAVIGATION = { state: "idle", location: void 0, formMethod: void 0, formAction: void 0, formEncType: void 0, formData: void 0, json: void 0, text: void 0 }; var IDLE_FETCHER = { state: "idle", data: void 0, formMethod: void 0, formAction: void 0, formEncType: void 0, formData: void 0, json: void 0, text: void 0 }; var IDLE_BLOCKER = { state: "unblocked", proceed: void 0, reset: void 0, location: void 0 }; var ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i; var defaultMapRouteProperties = (route) => ({ hasErrorBoundary: Boolean(route.hasErrorBoundary) }); var TRANSITIONS_STORAGE_KEY = "remix-router-transitions"; var ResetLoaderDataSymbol = Symbol("ResetLoaderData"); function createRouter(init) { const routerWindow = init.window ? init.window : typeof window !== "undefined" ? window : void 0; const isBrowser2 = typeof routerWindow !== "undefined" && typeof routerWindow.document !== "undefined" && typeof routerWindow.document.createElement !== "undefined"; invariant( init.routes.length > 0, "You must provide a non-empty routes array to createRouter" ); let mapRouteProperties2 = init.mapRouteProperties || defaultMapRouteProperties; let manifest = {}; let dataRoutes = convertRoutesToDataRoutes( init.routes, mapRouteProperties2, void 0, manifest ); let inFlightDataRoutes; let basename = init.basename || "/"; let dataStrategyImpl = init.dataStrategy || defaultDataStrategyWithMiddleware; let future = { unstable_middleware: false, ...init.future }; let unlistenHistory = null; let subscribers = /* @__PURE__ */ new Set(); let savedScrollPositions2 = null; let getScrollRestorationKey2 = null; let getScrollPosition = null; let initialScrollRestored = init.hydrationData != null; let initialMatches = matchRoutes(dataRoutes, init.history.location, basename); let initialMatchesIsFOW = false; let initialErrors = null; if (initialMatches == null && !init.patchRoutesOnNavigation) { let error = getInternalRouterError(404, { pathname: init.history.location.pathname }); let { matches, route } = getShortCircuitMatches(dataRoutes); initialMatches = matches; initialErrors = { [route.id]: error }; } if (initialMatches && !init.hydrationData) { let fogOfWar = checkFogOfWar( initialMatches, dataRoutes, init.history.location.pathname ); if (fogOfWar.active) { initialMatches = null; } } let initialized; if (!initialMatches) { initialized = false; initialMatches = []; let fogOfWar = checkFogOfWar( null, dataRoutes, init.history.location.pathname ); if (fogOfWar.active && fogOfWar.matches) { initialMatchesIsFOW = true; initialMatches = fogOfWar.matches; } } else if (initialMatches.some((m) => m.route.lazy)) { initialized = false; } else if (!initialMatches.some((m) => m.route.loader)) { initialized = true; } else { let loaderData = init.hydrationData ? init.hydrationData.loaderData : null; let errors = init.hydrationData ? init.hydrationData.errors : null; if (errors) { let idx = initialMatches.findIndex( (m) => errors[m.route.id] !== void 0 ); initialized = initialMatches.slice(0, idx + 1).every((m) => !shouldLoadRouteOnHydration(m.route, loaderData, errors)); } else { initialized = initialMatches.every( (m) => !shouldLoadRouteOnHydration(m.route, loaderData, errors) ); } } 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: /* @__PURE__ */ new Map(), blockers: /* @__PURE__ */ new Map() }; let pendingAction = "POP" /* Pop */; let pendingPreventScrollReset = false; let pendingNavigationController; let pendingViewTransitionEnabled = false; let appliedViewTransitions = /* @__PURE__ */ new Map(); let removePageHideEventListener = null; let isUninterruptedRevalidation = false; let isRevalidationRequired = false; let cancelledFetcherLoads = /* @__PURE__ */ new Set(); let fetchControllers = /* @__PURE__ */ new Map(); let incrementingLoadId = 0; let pendingNavigationLoadId = -1; let fetchReloadIds = /* @__PURE__ */ new Map(); let fetchRedirectIds = /* @__PURE__ */ new Set(); let fetchLoadMatches = /* @__PURE__ */ new Map(); let activeFetchers = /* @__PURE__ */ new Map(); let fetchersQueuedForDeletion = /* @__PURE__ */ new Set(); let blockerFunctions = /* @__PURE__ */ new Map(); let unblockBlockerHistoryUpdate = void 0; let pendingRevalidationDfd = null; function initialize() { unlistenHistory = init.history.listen( ({ action: historyAction, location, delta }) => { if (unblockBlockerHistoryUpdate) { unblockBlockerHistoryUpdate(); unblockBlockerHistoryUpdate = void 0; 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) { let nextHistoryUpdatePromise = new Promise((resolve) => { unblockBlockerHistoryUpdate = resolve; }); init.history.go(delta * -1); updateBlocker(blockerKey, { state: "blocked", location, proceed() { updateBlocker(blockerKey, { state: "proceeding", proceed: void 0, reset: void 0, location }); nextHistoryUpdatePromise.then(() => init.history.go(delta)); }, reset() { let blockers = new Map(state.blockers); blockers.set(blockerKey, IDLE_BLOCKER); updateState({ blockers }); } }); return; } return startNavigation(historyAction, location); } ); if (isBrowser2) { restoreAppliedTransitions(routerWindow, appliedViewTransitions); let _saveAppliedTransitions = () => persistAppliedTransitions(routerWindow, appliedViewTransitions); routerWindow.addEventListener("pagehide", _saveAppliedTransitions); removePageHideEventListener = () => routerWindow.removeEventListener("pagehide", _saveAppliedTransitions); } if (!state.initialized) { startNavigation("POP" /* Pop */, state.location, { initialHydration: true }); } return router; } function dispose() { if (unlistenHistory) { unlistenHistory(); } if (removePageHideEventListener) { removePageHideEventListener(); } subscribers.clear(); pendingNavigationController && pendingNavigationController.abort(); state.fetchers.forEach((_, key) => deleteFetcher(key)); state.blockers.forEach((_, key) => deleteBlocker(key)); } function subscribe(fn) { subscribers.add(fn); return () => subscribers.delete(fn); } function updateState(newState, opts = {}) { state = { ...state, ...newState }; let unmountedFetchers = []; let mountedFetchers = []; state.fetchers.forEach((fetcher, key) => { if (fetcher.state === "idle") { if (fetchersQueuedForDeletion.has(key)) { unmountedFetchers.push(key); } else { mountedFetchers.push(key); } } }); fetchersQueuedForDeletion.forEach((key) => { if (!state.fetchers.has(key) && !fetchControllers.has(key)) { unmountedFetchers.push(key); } }); [...subscribers].forEach( (subscriber) => subscriber(state, { deletedFetchers: unmountedFetchers, viewTransitionOpts: opts.viewTransitionOpts, flushSync: opts.flushSync === true }) ); unmountedFetchers.forEach((key) => deleteFetcher(key)); mountedFetchers.forEach((key) => state.fetchers.delete(key)); } function completeNavigation(location, newState, { flushSync } = {}) { let isActionReload = state.actionData != null && state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && state.navigation.state === "loading" && location.state?._isRedirect !== true; let actionData; if (newState.actionData) { if (Object.keys(newState.actionData).length > 0) { actionData = newState.actionData; } else { actionData = null; } } else if (isActionReload) { actionData = state.actionData; } else { actionData = null; } let loaderData = newState.loaderData ? mergeLoaderData( state.loaderData, newState.loaderData, newState.matches || [], newState.errors ) : state.loaderData; let blockers = state.blockers; if (blockers.size > 0) { blockers = new Map(blockers); blockers.forEach((_, k) => blockers.set(k, IDLE_BLOCKER)); } let preventScrollReset = pendingPreventScrollReset === true || state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && location.state?._isRedirect !== true; if (inFlightDataRoutes) { dataRoutes = inFlightDataRoutes; inFlightDataRoutes = void 0; } if (isUninterruptedRevalidation) { } else if (pendingAction === "POP" /* Pop */) { } else if (pendingAction === "PUSH" /* Push */) { init.history.push(location, location.state); } else if (pendingAction === "REPLACE" /* Replace */) { init.history.replace(location, location.state); } let viewTransitionOpts; if (pendingAction === "POP" /* Pop */) { let priorPaths = appliedViewTransitions.get(state.location.pathname); if (priorPaths && priorPaths.has(location.pathname)) { viewTransitionOpts = { currentLocation: state.location, nextLocation: location }; } else if (appliedViewTransitions.has(location.pathname)) { viewTransitionOpts = { currentLocation: location, nextLocation: state.location }; } } else if (pendingViewTransitionEnabled) { let toPaths = appliedViewTransitions.get(state.location.pathname); if (toPaths) { toPaths.add(location.pathname); } else { toPaths = /* @__PURE__ */ new Set([location.pathname]); appliedViewTransitions.set(state.location.pathname, toPaths); } viewTransitionOpts = { currentLocation: state.location, nextLocation: location }; } updateState( { ...newState, // matches, errors, fetchers go through as-is actionData, loaderData, historyAction: pendingAction, location, initialized: true, navigation: IDLE_NAVIGATION, revalidation: "idle", restoreScrollPosition: getSavedScrollPosition( location, newState.matches || state.matches ), preventScrollReset, blockers }, { viewTransitionOpts, flushSync: flushSync === true } ); pendingAction = "POP" /* Pop */; pendingPreventScrollReset = false; pendingViewTransitionEnabled = false; isUninterruptedRevalidation = false; isRevalidationRequired = false; pendingRevalidationDfd?.resolve(); pendingRevalidationDfd = null; } async function navigate(to, opts) { if (typeof to === "number") { init.history.go(to); return; } let normalizedPath = normalizeTo( state.location, state.matches, basename, to, opts?.fromRouteId, opts?.relative ); let { path, submission, error } = normalizeNavigateOptions( false, normalizedPath, opts ); let currentLocation = state.location; let nextLocation = createLocation(state.location, path, opts && opts.state); nextLocation = { ...nextLocation, ...init.history.encodeLocation(nextLocation) }; let userReplace = opts && opts.replace != null ? opts.replace : void 0; let historyAction = "PUSH" /* Push */; if (userReplace === true) { historyAction = "REPLACE" /* Replace */; } else if (userReplace === false) { } else if (submission != null && isMutationMethod(submission.formMethod) && submission.formAction === state.location.pathname + state.location.search) { historyAction = "REPLACE" /* Replace */; } let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : void 0; let flushSync = (opts && opts.flushSync) === true; let blockerKey = shouldBlockNavigation({ currentLocation, nextLocation, historyAction }); if (blockerKey) { updateBlocker(blockerKey, { state: "blocked", location: nextLocation, proceed() { updateBlocker(blockerKey, { state: "proceeding", proceed: void 0, reset: void 0, location: nextLocation }); navigate(to, opts); }, reset() { let blockers = new Map(state.blockers); blockers.set(blockerKey, IDLE_BLOCKER); updateState({ blockers }); } }); 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, enableViewTransition: opts && opts.viewTransition, flushSync }); } function revalidate() { if (!pendingRevalidationDfd) { pendingRevalidationDfd = createDeferred(); } interruptActiveLoads(); updateState({ revalidation: "loading" }); let promise = pendingRevalidationDfd.promise; if (state.navigation.state === "submitting") { return promise; } if (state.navigation.state === "idle") { startNavigation(state.historyAction, state.location, { startUninterruptedRevalidation: true }); return promise; } startNavigation( pendingAction || state.historyAction, state.navigation.location, { overrideNavigation: state.navigation, // Proxy through any rending view transition enableViewTransition: pendingViewTransitionEnabled === true } ); return promise; } async function startNavigation(historyAction, location, opts) { pendingNavigationController && pendingNavigationController.abort(); pendingNavigationController = null; pendingAction = historyAction; isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true; saveScrollPosition(state.location, state.matches); pendingPreventScrollReset = (opts && opts.preventScrollReset) === true; pendingViewTransitionEnabled = (opts && opts.enableViewTransition) === true; let routesToUse = inFlightDataRoutes || dataRoutes; let loadingNavigation = opts && opts.overrideNavigation; let matches = opts?.initialHydration && state.matches && state.matches.length > 0 && !initialMatchesIsFOW ? ( // `matchRoutes()` has already been called if we're in here via `router.initialize()` state.matches ) : matchRoutes(routesToUse, location, basename); let flushSync = (opts && opts.flushSync) === true; if (matches && state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) { completeNavigation(location, { matches }, { flushSync }); return; } let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname); if (fogOfWar.active && fogOfWar.matches) { matches = fogOfWar.matches; } if (!matches) { let { error, notFoundMatches, route } = handleNavigational404( location.pathname ); completeNavigation( location, { matches: notFoundMatches, loaderData: {}, errors: { [route.id]: error } }, { flushSync } ); return; } pendingNavigationController = new AbortController(); let request = createClientSideRequest( init.history, location, pendingNavigationController.signal, opts && opts.submission ); let scopedContext = new unstable_RouterContextProvider( init.unstable_getContext ? await init.unstable_getContext() : void 0 ); let pendingActionResult; if (opts && opts.pendingError) { pendingActionResult = [ findNearestBoundary(matches).route.id, { type: "error" /* error */, error: opts.pendingError } ]; } else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) { let actionResult = await handleAction( request, location, opts.submission, matches, scopedContext, fogOfWar.active, { replace: opts.replace, flushSync } ); if (actionResult.shortCircuited) { return; } if (actionResult.pendingActionResult) { let [routeId, result] = actionResult.pendingActionResult; if (isErrorResult(result) && isRouteErrorResponse(result.error) && result.error.status === 404) { pendingNavigationController = null; completeNavigation(location, { matches: actionResult.matches, loaderData: {}, errors: { [routeId]: result.error } }); return; } } matches = actionResult.matches || matches; pendingActionResult = actionResult.pendingActionResult; loadingNavigation = getLoadingNavigation(location, opts.submission); flushSync = false; fogOfWar.active = false; request = createClientSideRequest( init.history, request.url, request.signal ); } let { shortCircuited, matches: updatedMatches, loaderData, errors } = await handleLoaders( request, location, matches, scopedContext, fogOfWar.active, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionResult ); if (shortCircuited) { return; } pendingNavigationController = null; completeNavigation(location, { matches: updatedMatches || matches, ...getActionDataForCommit(pendingActionResult), loaderData, errors }); } async function handleAction(request, location, submission, matches, scopedContext, isFogOfWar, opts = {}) { interruptActiveLoads(); let navigation = getSubmittingNavigation(location, submission); updateState({ navigation }, { flushSync: opts.flushSync === true }); if (isFogOfWar) { let discoverResult = await discoverRoutes( matches, location.pathname, request.signal ); if (discoverResult.type === "aborted") { return { shortCircuited: true }; } else if (discoverResult.type === "error") { let boundaryId = findNearestBoundary(discoverResult.partialMatches).route.id; return { matches: discoverResult.partialMatches, pendingActionResult: [ boundaryId, { type: "error" /* error */, error: discoverResult.error } ] }; } else if (!discoverResult.matches) { let { notFoundMatches, error, route } = handleNavigational404( location.pathname ); return { matches: notFoundMatches, pendingActionResult: [ route.id, { type: "error" /* error */, error } ] }; } else { matches = discoverResult.matches; } } let result; let actionMatch = getTargetMatch(matches, location); if (!actionMatch.route.action && !actionMatch.route.lazy) { result = { type: "error" /* error */, error: getInternalRouterError(405, { method: request.method, pathname: location.pathname, routeId: actionMatch.route.id }) }; } else { let results = await callDataStrategy( "action",