10349 lines
327 KiB
JavaScript
10349 lines
327 KiB
JavaScript
/**
|
|
* react-router v7.9.3
|
|
*
|
|
* 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 href = "";
|
|
if (base && base.getAttribute("href")) {
|
|
let url = window2.location.href;
|
|
let hashIndex = url.indexOf("#");
|
|
href = hashIndex === -1 ? url : url.slice(0, hashIndex);
|
|
}
|
|
return href + "#" + (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) {
|
|
return createBrowserURLImpl(to);
|
|
}
|
|
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;
|
|
}
|
|
function createBrowserURLImpl(to, isAbsolute = false) {
|
|
let base = "http://localhost";
|
|
if (typeof window !== "undefined") {
|
|
base = window.location.origin !== "null" ? window.location.origin : window.location.href;
|
|
}
|
|
invariant(base, "No window.location.(origin|href) available to create URL");
|
|
let href = typeof to === "string" ? to : createPath(to);
|
|
href = href.replace(/ $/, "%20");
|
|
if (!isAbsolute && href.startsWith("//")) {
|
|
href = base + href;
|
|
}
|
|
return new URL(href, base);
|
|
}
|
|
|
|
// lib/router/utils.ts
|
|
function createContext(defaultValue) {
|
|
return { defaultValue };
|
|
}
|
|
var _map;
|
|
var RouterContextProvider = class {
|
|
/**
|
|
* Create a new `RouterContextProvider` instance
|
|
* @param init An optional initial context map to populate the provider with
|
|
*/
|
|
constructor(init) {
|
|
__privateAdd(this, _map, /* @__PURE__ */ new Map());
|
|
if (init) {
|
|
for (let [context, value] of init) {
|
|
this.set(context, value);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Access a value from the context. If no value has been set for the context,
|
|
* it will return the context's `defaultValue` if provided, or throw an error
|
|
* if no `defaultValue` was set.
|
|
* @param context The context to get the value for
|
|
* @returns The value for the context, or the context's `defaultValue` if no
|
|
* value was set
|
|
*/
|
|
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 a value for the context. If the context already has a value set, this
|
|
* will overwrite it.
|
|
*
|
|
* @param context The context to set the value for
|
|
* @param value The value to set for the context
|
|
* @returns {void}
|
|
*/
|
|
set(context, value) {
|
|
__privateGet(this, _map).set(context, value);
|
|
}
|
|
};
|
|
_map = new WeakMap();
|
|
var unsupportedLazyRouteObjectKeys = /* @__PURE__ */ new Set([
|
|
"lazy",
|
|
"caseSensitive",
|
|
"path",
|
|
"id",
|
|
"index",
|
|
"children"
|
|
]);
|
|
function isUnsupportedLazyRouteObjectKey(key) {
|
|
return unsupportedLazyRouteObjectKeys.has(
|
|
key
|
|
);
|
|
}
|
|
var unsupportedLazyRouteFunctionKeys = /* @__PURE__ */ new Set([
|
|
"lazy",
|
|
"caseSensitive",
|
|
"path",
|
|
"id",
|
|
"index",
|
|
"middleware",
|
|
"children"
|
|
]);
|
|
function isUnsupportedLazyRouteFunctionKey(key) {
|
|
return unsupportedLazyRouteFunctionKeys.has(
|
|
key
|
|
);
|
|
}
|
|
function isIndexRoute(route) {
|
|
return route.index === true;
|
|
}
|
|
function convertRoutesToDataRoutes(routes, mapRouteProperties2, parentPath = [], manifest = {}, allowInPlaceMutations = false) {
|
|
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(
|
|
allowInPlaceMutations || !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,
|
|
allowInPlaceMutations
|
|
);
|
|
}
|
|
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],
|
|
loaderData: loaderData[route.id],
|
|
handle: route.handle
|
|
};
|
|
}
|
|
function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "", _hasParentOptionalSegments = false) {
|
|
let flattenRoute = (route, index, hasParentOptionalSegments = _hasParentOptionalSegments, relativePath) => {
|
|
let meta = {
|
|
relativePath: relativePath === void 0 ? route.path || "" : relativePath,
|
|
caseSensitive: route.caseSensitive === true,
|
|
childrenIndex: index,
|
|
route
|
|
};
|
|
if (meta.relativePath.startsWith("/")) {
|
|
if (!meta.relativePath.startsWith(parentPath) && hasParentOptionalSegments) {
|
|
return;
|
|
}
|
|
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,
|
|
hasParentOptionalSegments
|
|
);
|
|
}
|
|
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, true, 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 stringify2 = (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 stringify2(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 encodeURIComponent(stringify2(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 ? "/?([^\\/]+)?" : "/([^\\/]+)";
|
|
}
|
|
).replace(/\/([\w-]+)\?(\/|$)/g, "(/$1)?$2");
|
|
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 prependBasename({
|
|
basename,
|
|
pathname
|
|
}) {
|
|
return pathname === "/" ? basename : joinPaths([basename, pathname]);
|
|
}
|
|
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 isAbsoluteUrl = (url) => ABSOLUTE_URL_REGEX.test(url);
|
|
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 hydrationRouteProperties2 = init.hydrationRouteProperties || [];
|
|
let mapRouteProperties2 = init.mapRouteProperties || defaultMapRouteProperties;
|
|
let manifest = {};
|
|
let dataRoutes = convertRoutesToDataRoutes(
|
|
init.routes,
|
|
mapRouteProperties2,
|
|
void 0,
|
|
manifest
|
|
);
|
|
let inFlightDataRoutes;
|
|
let basename = init.basename || "/";
|
|
if (!basename.startsWith("/")) {
|
|
basename = `/${basename}`;
|
|
}
|
|
let dataStrategyImpl = init.dataStrategy || defaultDataStrategyWithMiddleware;
|
|
let future = {
|
|
...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;
|
|
let initialized;
|
|
if (initialMatches == null && !init.patchRoutesOnNavigation) {
|
|
let error = getInternalRouterError(404, {
|
|
pathname: init.history.location.pathname
|
|
});
|
|
let { matches, route } = getShortCircuitMatches(dataRoutes);
|
|
initialized = true;
|
|
initialMatches = matches;
|
|
initialErrors = { [route.id]: error };
|
|
} else {
|
|
if (initialMatches && !init.hydrationData) {
|
|
let fogOfWar = checkFogOfWar(
|
|
initialMatches,
|
|
dataRoutes,
|
|
init.history.location.pathname
|
|
);
|
|
if (fogOfWar.active) {
|
|
initialMatches = null;
|
|
}
|
|
}
|
|
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) => routeHasLoaderOrMiddleware(m.route))) {
|
|
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 = {}) {
|
|
if (newState.matches) {
|
|
newState.matches = newState.matches.map((m) => {
|
|
let route = manifest[m.route.id];
|
|
let matchRoute = m.route;
|
|
if (matchRoute.element !== route.element || matchRoute.errorElement !== route.errorElement || matchRoute.hydrateFallbackElement !== route.hydrateFallbackElement) {
|
|
return {
|
|
...m,
|
|
route
|
|
};
|
|
}
|
|
return m;
|
|
});
|
|
}
|
|
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 restoreScrollPosition = isUninterruptedRevalidation ? false : getSavedScrollPosition(location, newState.matches || state.matches);
|
|
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,
|
|
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 = init.getContext ? await init.getContext() : new RouterContextProvider();
|
|
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,
|
|
opts && opts.initialHydration === true,
|
|
{ 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, initialHydration, 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") {
|
|
if (discoverResult.partialMatches.length === 0) {
|
|
let { matches: matches2, route } = getShortCircuitMatches(dataRoutes);
|
|
return {
|
|
matches: matches2,
|
|
pendingActionResult: [
|
|
route.id,
|
|
{
|
|
type: "error" /* error */,
|
|
error: discoverResult.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 dsMatches = getTargetedDataStrategyMatches(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
request,
|
|
matches,
|
|
actionMatch,
|
|
initialHydration ? [] : hydrationRouteProperties2,
|
|
scopedContext
|
|
);
|
|
let results = await callDataStrategy(
|
|
request,
|
|
dsMatches,
|
|
scopedContext,
|
|
null
|
|
);
|
|
result = results[actionMatch.route.id];
|
|
if (!result) {
|
|
for (let match of matches) {
|
|
if (results[match.route.id]) {
|
|
result = results[match.route.id];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (request.signal.aborted) {
|
|
return { shortCircuited: true };
|
|
}
|
|
}
|
|
if (isRedirectResult(result)) {
|
|
let replace2;
|
|
if (opts && opts.replace != null) {
|
|
replace2 = opts.replace;
|
|
} else {
|
|
let location2 = normalizeRedirectLocation(
|
|
result.response.headers.get("Location"),
|
|
new URL(request.url),
|
|
basename
|
|
);
|
|
replace2 = location2 === state.location.pathname + state.location.search;
|
|
}
|
|
await startRedirectNavigation(request, result, true, {
|
|
submission,
|
|
replace: replace2
|
|
});
|
|
return { shortCircuited: true };
|
|
}
|
|
if (isErrorResult(result)) {
|
|
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
if ((opts && opts.replace) !== true) {
|
|
pendingAction = "PUSH" /* Push */;
|
|
}
|
|
return {
|
|
matches,
|
|
pendingActionResult: [
|
|
boundaryMatch.route.id,
|
|
result,
|
|
actionMatch.route.id
|
|
]
|
|
};
|
|
}
|
|
return {
|
|
matches,
|
|
pendingActionResult: [actionMatch.route.id, result]
|
|
};
|
|
}
|
|
async function handleLoaders(request, location, matches, scopedContext, isFogOfWar, overrideNavigation, submission, fetcherSubmission, replace2, initialHydration, flushSync, pendingActionResult) {
|
|
let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
|
|
let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);
|
|
let shouldUpdateNavigationState = !isUninterruptedRevalidation && !initialHydration;
|
|
if (isFogOfWar) {
|
|
if (shouldUpdateNavigationState) {
|
|
let actionData = getUpdatedActionData(pendingActionResult);
|
|
updateState(
|
|
{
|
|
navigation: loadingNavigation,
|
|
...actionData !== void 0 ? { actionData } : {}
|
|
},
|
|
{
|
|
flushSync
|
|
}
|
|
);
|
|
}
|
|
let discoverResult = await discoverRoutes(
|
|
matches,
|
|
location.pathname,
|
|
request.signal
|
|
);
|
|
if (discoverResult.type === "aborted") {
|
|
return { shortCircuited: true };
|
|
} else if (discoverResult.type === "error") {
|
|
if (discoverResult.partialMatches.length === 0) {
|
|
let { matches: matches2, route } = getShortCircuitMatches(dataRoutes);
|
|
return {
|
|
matches: matches2,
|
|
loaderData: {},
|
|
errors: {
|
|
[route.id]: discoverResult.error
|
|
}
|
|
};
|
|
}
|
|
let boundaryId = findNearestBoundary(discoverResult.partialMatches).route.id;
|
|
return {
|
|
matches: discoverResult.partialMatches,
|
|
loaderData: {},
|
|
errors: {
|
|
[boundaryId]: discoverResult.error
|
|
}
|
|
};
|
|
} else if (!discoverResult.matches) {
|
|
let { error, notFoundMatches, route } = handleNavigational404(
|
|
location.pathname
|
|
);
|
|
return {
|
|
matches: notFoundMatches,
|
|
loaderData: {},
|
|
errors: {
|
|
[route.id]: error
|
|
}
|
|
};
|
|
} else {
|
|
matches = discoverResult.matches;
|
|
}
|
|
}
|
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
let { dsMatches, revalidatingFetchers } = getMatchesToLoad(
|
|
request,
|
|
scopedContext,
|
|
mapRouteProperties2,
|
|
manifest,
|
|
init.history,
|
|
state,
|
|
matches,
|
|
activeSubmission,
|
|
location,
|
|
initialHydration ? [] : hydrationRouteProperties2,
|
|
initialHydration === true,
|
|
isRevalidationRequired,
|
|
cancelledFetcherLoads,
|
|
fetchersQueuedForDeletion,
|
|
fetchLoadMatches,
|
|
fetchRedirectIds,
|
|
routesToUse,
|
|
basename,
|
|
init.patchRoutesOnNavigation != null,
|
|
pendingActionResult
|
|
);
|
|
pendingNavigationLoadId = ++incrementingLoadId;
|
|
if (!init.dataStrategy && !dsMatches.some((m) => m.shouldLoad) && !dsMatches.some(
|
|
(m) => m.route.middleware && m.route.middleware.length > 0
|
|
) && revalidatingFetchers.length === 0) {
|
|
let updatedFetchers2 = markFetchRedirectsDone();
|
|
completeNavigation(
|
|
location,
|
|
{
|
|
matches,
|
|
loaderData: {},
|
|
// Commit pending error if we're short circuiting
|
|
errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? { [pendingActionResult[0]]: pendingActionResult[1].error } : null,
|
|
...getActionDataForCommit(pendingActionResult),
|
|
...updatedFetchers2 ? { fetchers: new Map(state.fetchers) } : {}
|
|
},
|
|
{ flushSync }
|
|
);
|
|
return { shortCircuited: true };
|
|
}
|
|
if (shouldUpdateNavigationState) {
|
|
let updates = {};
|
|
if (!isFogOfWar) {
|
|
updates.navigation = loadingNavigation;
|
|
let actionData = getUpdatedActionData(pendingActionResult);
|
|
if (actionData !== void 0) {
|
|
updates.actionData = actionData;
|
|
}
|
|
}
|
|
if (revalidatingFetchers.length > 0) {
|
|
updates.fetchers = getUpdatedRevalidatingFetchers(revalidatingFetchers);
|
|
}
|
|
updateState(updates, { flushSync });
|
|
}
|
|
revalidatingFetchers.forEach((rf) => {
|
|
abortFetcher(rf.key);
|
|
if (rf.controller) {
|
|
fetchControllers.set(rf.key, rf.controller);
|
|
}
|
|
});
|
|
let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach((f) => abortFetcher(f.key));
|
|
if (pendingNavigationController) {
|
|
pendingNavigationController.signal.addEventListener(
|
|
"abort",
|
|
abortPendingFetchRevalidations
|
|
);
|
|
}
|
|
let { loaderResults, fetcherResults } = await callLoadersAndMaybeResolveData(
|
|
dsMatches,
|
|
revalidatingFetchers,
|
|
request,
|
|
scopedContext
|
|
);
|
|
if (request.signal.aborted) {
|
|
return { shortCircuited: true };
|
|
}
|
|
if (pendingNavigationController) {
|
|
pendingNavigationController.signal.removeEventListener(
|
|
"abort",
|
|
abortPendingFetchRevalidations
|
|
);
|
|
}
|
|
revalidatingFetchers.forEach((rf) => fetchControllers.delete(rf.key));
|
|
let redirect2 = findRedirect(loaderResults);
|
|
if (redirect2) {
|
|
await startRedirectNavigation(request, redirect2.result, true, {
|
|
replace: replace2
|
|
});
|
|
return { shortCircuited: true };
|
|
}
|
|
redirect2 = findRedirect(fetcherResults);
|
|
if (redirect2) {
|
|
fetchRedirectIds.add(redirect2.key);
|
|
await startRedirectNavigation(request, redirect2.result, true, {
|
|
replace: replace2
|
|
});
|
|
return { shortCircuited: true };
|
|
}
|
|
let { loaderData, errors } = processLoaderData(
|
|
state,
|
|
matches,
|
|
loaderResults,
|
|
pendingActionResult,
|
|
revalidatingFetchers,
|
|
fetcherResults
|
|
);
|
|
if (initialHydration && state.errors) {
|
|
errors = { ...state.errors, ...errors };
|
|
}
|
|
let updatedFetchers = markFetchRedirectsDone();
|
|
let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
|
|
let shouldUpdateFetchers = updatedFetchers || didAbortFetchLoads || revalidatingFetchers.length > 0;
|
|
return {
|
|
matches,
|
|
loaderData,
|
|
errors,
|
|
...shouldUpdateFetchers ? { fetchers: new Map(state.fetchers) } : {}
|
|
};
|
|
}
|
|
function getUpdatedActionData(pendingActionResult) {
|
|
if (pendingActionResult && !isErrorResult(pendingActionResult[1])) {
|
|
return {
|
|
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
};
|
|
} else if (state.actionData) {
|
|
if (Object.keys(state.actionData).length === 0) {
|
|
return null;
|
|
} else {
|
|
return state.actionData;
|
|
}
|
|
}
|
|
}
|
|
function getUpdatedRevalidatingFetchers(revalidatingFetchers) {
|
|
revalidatingFetchers.forEach((rf) => {
|
|
let fetcher = state.fetchers.get(rf.key);
|
|
let revalidatingFetcher = getLoadingFetcher(
|
|
void 0,
|
|
fetcher ? fetcher.data : void 0
|
|
);
|
|
state.fetchers.set(rf.key, revalidatingFetcher);
|
|
});
|
|
return new Map(state.fetchers);
|
|
}
|
|
async function fetch2(key, routeId, href, opts) {
|
|
abortFetcher(key);
|
|
let flushSync = (opts && opts.flushSync) === true;
|
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
let normalizedPath = normalizeTo(
|
|
state.location,
|
|
state.matches,
|
|
basename,
|
|
href,
|
|
routeId,
|
|
opts?.relative
|
|
);
|
|
let matches = matchRoutes(routesToUse, normalizedPath, basename);
|
|
let fogOfWar = checkFogOfWar(matches, routesToUse, normalizedPath);
|
|
if (fogOfWar.active && fogOfWar.matches) {
|
|
matches = fogOfWar.matches;
|
|
}
|
|
if (!matches) {
|
|
setFetcherError(
|
|
key,
|
|
routeId,
|
|
getInternalRouterError(404, { pathname: normalizedPath }),
|
|
{ flushSync }
|
|
);
|
|
return;
|
|
}
|
|
let { path, submission, error } = normalizeNavigateOptions(
|
|
true,
|
|
normalizedPath,
|
|
opts
|
|
);
|
|
if (error) {
|
|
setFetcherError(key, routeId, error, { flushSync });
|
|
return;
|
|
}
|
|
let scopedContext = init.getContext ? await init.getContext() : new RouterContextProvider();
|
|
let preventScrollReset = (opts && opts.preventScrollReset) === true;
|
|
if (submission && isMutationMethod(submission.formMethod)) {
|
|
await handleFetcherAction(
|
|
key,
|
|
routeId,
|
|
path,
|
|
matches,
|
|
scopedContext,
|
|
fogOfWar.active,
|
|
flushSync,
|
|
preventScrollReset,
|
|
submission
|
|
);
|
|
return;
|
|
}
|
|
fetchLoadMatches.set(key, { routeId, path });
|
|
await handleFetcherLoader(
|
|
key,
|
|
routeId,
|
|
path,
|
|
matches,
|
|
scopedContext,
|
|
fogOfWar.active,
|
|
flushSync,
|
|
preventScrollReset,
|
|
submission
|
|
);
|
|
}
|
|
async function handleFetcherAction(key, routeId, path, requestMatches, scopedContext, isFogOfWar, flushSync, preventScrollReset, submission) {
|
|
interruptActiveLoads();
|
|
fetchLoadMatches.delete(key);
|
|
let existingFetcher = state.fetchers.get(key);
|
|
updateFetcherState(key, getSubmittingFetcher(submission, existingFetcher), {
|
|
flushSync
|
|
});
|
|
let abortController = new AbortController();
|
|
let fetchRequest = createClientSideRequest(
|
|
init.history,
|
|
path,
|
|
abortController.signal,
|
|
submission
|
|
);
|
|
if (isFogOfWar) {
|
|
let discoverResult = await discoverRoutes(
|
|
requestMatches,
|
|
new URL(fetchRequest.url).pathname,
|
|
fetchRequest.signal,
|
|
key
|
|
);
|
|
if (discoverResult.type === "aborted") {
|
|
return;
|
|
} else if (discoverResult.type === "error") {
|
|
setFetcherError(key, routeId, discoverResult.error, { flushSync });
|
|
return;
|
|
} else if (!discoverResult.matches) {
|
|
setFetcherError(
|
|
key,
|
|
routeId,
|
|
getInternalRouterError(404, { pathname: path }),
|
|
{ flushSync }
|
|
);
|
|
return;
|
|
} else {
|
|
requestMatches = discoverResult.matches;
|
|
}
|
|
}
|
|
let match = getTargetMatch(requestMatches, path);
|
|
if (!match.route.action && !match.route.lazy) {
|
|
let error = getInternalRouterError(405, {
|
|
method: submission.formMethod,
|
|
pathname: path,
|
|
routeId
|
|
});
|
|
setFetcherError(key, routeId, error, { flushSync });
|
|
return;
|
|
}
|
|
fetchControllers.set(key, abortController);
|
|
let originatingLoadId = incrementingLoadId;
|
|
let fetchMatches = getTargetedDataStrategyMatches(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
fetchRequest,
|
|
requestMatches,
|
|
match,
|
|
hydrationRouteProperties2,
|
|
scopedContext
|
|
);
|
|
let actionResults = await callDataStrategy(
|
|
fetchRequest,
|
|
fetchMatches,
|
|
scopedContext,
|
|
key
|
|
);
|
|
let actionResult = actionResults[match.route.id];
|
|
if (fetchRequest.signal.aborted) {
|
|
if (fetchControllers.get(key) === abortController) {
|
|
fetchControllers.delete(key);
|
|
}
|
|
return;
|
|
}
|
|
if (fetchersQueuedForDeletion.has(key)) {
|
|
if (isRedirectResult(actionResult) || isErrorResult(actionResult)) {
|
|
updateFetcherState(key, getDoneFetcher(void 0));
|
|
return;
|
|
}
|
|
} else {
|
|
if (isRedirectResult(actionResult)) {
|
|
fetchControllers.delete(key);
|
|
if (pendingNavigationLoadId > originatingLoadId) {
|
|
updateFetcherState(key, getDoneFetcher(void 0));
|
|
return;
|
|
} else {
|
|
fetchRedirectIds.add(key);
|
|
updateFetcherState(key, getLoadingFetcher(submission));
|
|
return startRedirectNavigation(fetchRequest, actionResult, false, {
|
|
fetcherSubmission: submission,
|
|
preventScrollReset
|
|
});
|
|
}
|
|
}
|
|
if (isErrorResult(actionResult)) {
|
|
setFetcherError(key, routeId, actionResult.error);
|
|
return;
|
|
}
|
|
}
|
|
let nextLocation = state.navigation.location || state.location;
|
|
let revalidationRequest = createClientSideRequest(
|
|
init.history,
|
|
nextLocation,
|
|
abortController.signal
|
|
);
|
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
let matches = state.navigation.state !== "idle" ? matchRoutes(routesToUse, state.navigation.location, basename) : state.matches;
|
|
invariant(matches, "Didn't find any matches after fetcher action");
|
|
let loadId = ++incrementingLoadId;
|
|
fetchReloadIds.set(key, loadId);
|
|
let loadFetcher = getLoadingFetcher(submission, actionResult.data);
|
|
state.fetchers.set(key, loadFetcher);
|
|
let { dsMatches, revalidatingFetchers } = getMatchesToLoad(
|
|
revalidationRequest,
|
|
scopedContext,
|
|
mapRouteProperties2,
|
|
manifest,
|
|
init.history,
|
|
state,
|
|
matches,
|
|
submission,
|
|
nextLocation,
|
|
hydrationRouteProperties2,
|
|
false,
|
|
isRevalidationRequired,
|
|
cancelledFetcherLoads,
|
|
fetchersQueuedForDeletion,
|
|
fetchLoadMatches,
|
|
fetchRedirectIds,
|
|
routesToUse,
|
|
basename,
|
|
init.patchRoutesOnNavigation != null,
|
|
[match.route.id, actionResult]
|
|
);
|
|
revalidatingFetchers.filter((rf) => rf.key !== key).forEach((rf) => {
|
|
let staleKey = rf.key;
|
|
let existingFetcher2 = state.fetchers.get(staleKey);
|
|
let revalidatingFetcher = getLoadingFetcher(
|
|
void 0,
|
|
existingFetcher2 ? existingFetcher2.data : void 0
|
|
);
|
|
state.fetchers.set(staleKey, revalidatingFetcher);
|
|
abortFetcher(staleKey);
|
|
if (rf.controller) {
|
|
fetchControllers.set(staleKey, rf.controller);
|
|
}
|
|
});
|
|
updateState({ fetchers: new Map(state.fetchers) });
|
|
let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach((rf) => abortFetcher(rf.key));
|
|
abortController.signal.addEventListener(
|
|
"abort",
|
|
abortPendingFetchRevalidations
|
|
);
|
|
let { loaderResults, fetcherResults } = await callLoadersAndMaybeResolveData(
|
|
dsMatches,
|
|
revalidatingFetchers,
|
|
revalidationRequest,
|
|
scopedContext
|
|
);
|
|
if (abortController.signal.aborted) {
|
|
return;
|
|
}
|
|
abortController.signal.removeEventListener(
|
|
"abort",
|
|
abortPendingFetchRevalidations
|
|
);
|
|
fetchReloadIds.delete(key);
|
|
fetchControllers.delete(key);
|
|
revalidatingFetchers.forEach((r) => fetchControllers.delete(r.key));
|
|
if (state.fetchers.has(key)) {
|
|
let doneFetcher = getDoneFetcher(actionResult.data);
|
|
state.fetchers.set(key, doneFetcher);
|
|
}
|
|
let redirect2 = findRedirect(loaderResults);
|
|
if (redirect2) {
|
|
return startRedirectNavigation(
|
|
revalidationRequest,
|
|
redirect2.result,
|
|
false,
|
|
{ preventScrollReset }
|
|
);
|
|
}
|
|
redirect2 = findRedirect(fetcherResults);
|
|
if (redirect2) {
|
|
fetchRedirectIds.add(redirect2.key);
|
|
return startRedirectNavigation(
|
|
revalidationRequest,
|
|
redirect2.result,
|
|
false,
|
|
{ preventScrollReset }
|
|
);
|
|
}
|
|
let { loaderData, errors } = processLoaderData(
|
|
state,
|
|
matches,
|
|
loaderResults,
|
|
void 0,
|
|
revalidatingFetchers,
|
|
fetcherResults
|
|
);
|
|
abortStaleFetchLoads(loadId);
|
|
if (state.navigation.state === "loading" && loadId > pendingNavigationLoadId) {
|
|
invariant(pendingAction, "Expected pending action");
|
|
pendingNavigationController && pendingNavigationController.abort();
|
|
completeNavigation(state.navigation.location, {
|
|
matches,
|
|
loaderData,
|
|
errors,
|
|
fetchers: new Map(state.fetchers)
|
|
});
|
|
} else {
|
|
updateState({
|
|
errors,
|
|
loaderData: mergeLoaderData(
|
|
state.loaderData,
|
|
loaderData,
|
|
matches,
|
|
errors
|
|
),
|
|
fetchers: new Map(state.fetchers)
|
|
});
|
|
isRevalidationRequired = false;
|
|
}
|
|
}
|
|
async function handleFetcherLoader(key, routeId, path, matches, scopedContext, isFogOfWar, flushSync, preventScrollReset, submission) {
|
|
let existingFetcher = state.fetchers.get(key);
|
|
updateFetcherState(
|
|
key,
|
|
getLoadingFetcher(
|
|
submission,
|
|
existingFetcher ? existingFetcher.data : void 0
|
|
),
|
|
{ flushSync }
|
|
);
|
|
let abortController = new AbortController();
|
|
let fetchRequest = createClientSideRequest(
|
|
init.history,
|
|
path,
|
|
abortController.signal
|
|
);
|
|
if (isFogOfWar) {
|
|
let discoverResult = await discoverRoutes(
|
|
matches,
|
|
new URL(fetchRequest.url).pathname,
|
|
fetchRequest.signal,
|
|
key
|
|
);
|
|
if (discoverResult.type === "aborted") {
|
|
return;
|
|
} else if (discoverResult.type === "error") {
|
|
setFetcherError(key, routeId, discoverResult.error, { flushSync });
|
|
return;
|
|
} else if (!discoverResult.matches) {
|
|
setFetcherError(
|
|
key,
|
|
routeId,
|
|
getInternalRouterError(404, { pathname: path }),
|
|
{ flushSync }
|
|
);
|
|
return;
|
|
} else {
|
|
matches = discoverResult.matches;
|
|
}
|
|
}
|
|
let match = getTargetMatch(matches, path);
|
|
fetchControllers.set(key, abortController);
|
|
let originatingLoadId = incrementingLoadId;
|
|
let dsMatches = getTargetedDataStrategyMatches(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
fetchRequest,
|
|
matches,
|
|
match,
|
|
hydrationRouteProperties2,
|
|
scopedContext
|
|
);
|
|
let results = await callDataStrategy(
|
|
fetchRequest,
|
|
dsMatches,
|
|
scopedContext,
|
|
key
|
|
);
|
|
let result = results[match.route.id];
|
|
if (fetchControllers.get(key) === abortController) {
|
|
fetchControllers.delete(key);
|
|
}
|
|
if (fetchRequest.signal.aborted) {
|
|
return;
|
|
}
|
|
if (fetchersQueuedForDeletion.has(key)) {
|
|
updateFetcherState(key, getDoneFetcher(void 0));
|
|
return;
|
|
}
|
|
if (isRedirectResult(result)) {
|
|
if (pendingNavigationLoadId > originatingLoadId) {
|
|
updateFetcherState(key, getDoneFetcher(void 0));
|
|
return;
|
|
} else {
|
|
fetchRedirectIds.add(key);
|
|
await startRedirectNavigation(fetchRequest, result, false, {
|
|
preventScrollReset
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
if (isErrorResult(result)) {
|
|
setFetcherError(key, routeId, result.error);
|
|
return;
|
|
}
|
|
updateFetcherState(key, getDoneFetcher(result.data));
|
|
}
|
|
async function startRedirectNavigation(request, redirect2, isNavigation, {
|
|
submission,
|
|
fetcherSubmission,
|
|
preventScrollReset,
|
|
replace: replace2
|
|
} = {}) {
|
|
if (redirect2.response.headers.has("X-Remix-Revalidate")) {
|
|
isRevalidationRequired = true;
|
|
}
|
|
let location = redirect2.response.headers.get("Location");
|
|
invariant(location, "Expected a Location header on the redirect Response");
|
|
location = normalizeRedirectLocation(
|
|
location,
|
|
new URL(request.url),
|
|
basename
|
|
);
|
|
let redirectLocation = createLocation(state.location, location, {
|
|
_isRedirect: true
|
|
});
|
|
if (isBrowser2) {
|
|
let isDocumentReload = false;
|
|
if (redirect2.response.headers.has("X-Remix-Reload-Document")) {
|
|
isDocumentReload = true;
|
|
} else if (isAbsoluteUrl(location)) {
|
|
const url = createBrowserURLImpl(location, true);
|
|
isDocumentReload = // Hard reload if it's an absolute URL to a new origin
|
|
url.origin !== routerWindow.location.origin || // Hard reload if it's an absolute URL that does not match our basename
|
|
stripBasename(url.pathname, basename) == null;
|
|
}
|
|
if (isDocumentReload) {
|
|
if (replace2) {
|
|
routerWindow.location.replace(location);
|
|
} else {
|
|
routerWindow.location.assign(location);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
pendingNavigationController = null;
|
|
let redirectNavigationType = replace2 === true || redirect2.response.headers.has("X-Remix-Replace") ? "REPLACE" /* Replace */ : "PUSH" /* Push */;
|
|
let { formMethod, formAction, formEncType } = state.navigation;
|
|
if (!submission && !fetcherSubmission && formMethod && formAction && formEncType) {
|
|
submission = getSubmissionFromNavigation(state.navigation);
|
|
}
|
|
let activeSubmission = submission || fetcherSubmission;
|
|
if (redirectPreserveMethodStatusCodes.has(redirect2.response.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
|
|
await startNavigation(redirectNavigationType, redirectLocation, {
|
|
submission: {
|
|
...activeSubmission,
|
|
formAction: location
|
|
},
|
|
// Preserve these flags across redirects
|
|
preventScrollReset: preventScrollReset || pendingPreventScrollReset,
|
|
enableViewTransition: isNavigation ? pendingViewTransitionEnabled : void 0
|
|
});
|
|
} else {
|
|
let overrideNavigation = getLoadingNavigation(
|
|
redirectLocation,
|
|
submission
|
|
);
|
|
await startNavigation(redirectNavigationType, redirectLocation, {
|
|
overrideNavigation,
|
|
// Send fetcher submissions through for shouldRevalidate
|
|
fetcherSubmission,
|
|
// Preserve these flags across redirects
|
|
preventScrollReset: preventScrollReset || pendingPreventScrollReset,
|
|
enableViewTransition: isNavigation ? pendingViewTransitionEnabled : void 0
|
|
});
|
|
}
|
|
}
|
|
async function callDataStrategy(request, matches, scopedContext, fetcherKey) {
|
|
let results;
|
|
let dataResults = {};
|
|
try {
|
|
results = await callDataStrategyImpl(
|
|
dataStrategyImpl,
|
|
request,
|
|
matches,
|
|
fetcherKey,
|
|
scopedContext,
|
|
false
|
|
);
|
|
} catch (e) {
|
|
matches.filter((m) => m.shouldLoad).forEach((m) => {
|
|
dataResults[m.route.id] = {
|
|
type: "error" /* error */,
|
|
error: e
|
|
};
|
|
});
|
|
return dataResults;
|
|
}
|
|
if (request.signal.aborted) {
|
|
return dataResults;
|
|
}
|
|
for (let [routeId, result] of Object.entries(results)) {
|
|
if (isRedirectDataStrategyResult(result)) {
|
|
let response = result.result;
|
|
dataResults[routeId] = {
|
|
type: "redirect" /* redirect */,
|
|
response: normalizeRelativeRoutingRedirectResponse(
|
|
response,
|
|
request,
|
|
routeId,
|
|
matches,
|
|
basename
|
|
)
|
|
};
|
|
} else {
|
|
dataResults[routeId] = await convertDataStrategyResultToDataResult(result);
|
|
}
|
|
}
|
|
return dataResults;
|
|
}
|
|
async function callLoadersAndMaybeResolveData(matches, fetchersToLoad, request, scopedContext) {
|
|
let loaderResultsPromise = callDataStrategy(
|
|
request,
|
|
matches,
|
|
scopedContext,
|
|
null
|
|
);
|
|
let fetcherResultsPromise = Promise.all(
|
|
fetchersToLoad.map(async (f) => {
|
|
if (f.matches && f.match && f.request && f.controller) {
|
|
let results = await callDataStrategy(
|
|
f.request,
|
|
f.matches,
|
|
scopedContext,
|
|
f.key
|
|
);
|
|
let result = results[f.match.route.id];
|
|
return { [f.key]: result };
|
|
} else {
|
|
return Promise.resolve({
|
|
[f.key]: {
|
|
type: "error" /* error */,
|
|
error: getInternalRouterError(404, {
|
|
pathname: f.path
|
|
})
|
|
}
|
|
});
|
|
}
|
|
})
|
|
);
|
|
let loaderResults = await loaderResultsPromise;
|
|
let fetcherResults = (await fetcherResultsPromise).reduce(
|
|
(acc, r) => Object.assign(acc, r),
|
|
{}
|
|
);
|
|
return {
|
|
loaderResults,
|
|
fetcherResults
|
|
};
|
|
}
|
|
function interruptActiveLoads() {
|
|
isRevalidationRequired = true;
|
|
fetchLoadMatches.forEach((_, key) => {
|
|
if (fetchControllers.has(key)) {
|
|
cancelledFetcherLoads.add(key);
|
|
}
|
|
abortFetcher(key);
|
|
});
|
|
}
|
|
function updateFetcherState(key, fetcher, opts = {}) {
|
|
state.fetchers.set(key, fetcher);
|
|
updateState(
|
|
{ fetchers: new Map(state.fetchers) },
|
|
{ flushSync: (opts && opts.flushSync) === true }
|
|
);
|
|
}
|
|
function setFetcherError(key, routeId, error, opts = {}) {
|
|
let boundaryMatch = findNearestBoundary(state.matches, routeId);
|
|
deleteFetcher(key);
|
|
updateState(
|
|
{
|
|
errors: {
|
|
[boundaryMatch.route.id]: error
|
|
},
|
|
fetchers: new Map(state.fetchers)
|
|
},
|
|
{ flushSync: (opts && opts.flushSync) === true }
|
|
);
|
|
}
|
|
function getFetcher(key) {
|
|
activeFetchers.set(key, (activeFetchers.get(key) || 0) + 1);
|
|
if (fetchersQueuedForDeletion.has(key)) {
|
|
fetchersQueuedForDeletion.delete(key);
|
|
}
|
|
return state.fetchers.get(key) || IDLE_FETCHER;
|
|
}
|
|
function resetFetcher(key, opts) {
|
|
abortFetcher(key, opts?.reason);
|
|
updateFetcherState(key, getDoneFetcher(null));
|
|
}
|
|
function deleteFetcher(key) {
|
|
let fetcher = state.fetchers.get(key);
|
|
if (fetchControllers.has(key) && !(fetcher && fetcher.state === "loading" && fetchReloadIds.has(key))) {
|
|
abortFetcher(key);
|
|
}
|
|
fetchLoadMatches.delete(key);
|
|
fetchReloadIds.delete(key);
|
|
fetchRedirectIds.delete(key);
|
|
fetchersQueuedForDeletion.delete(key);
|
|
cancelledFetcherLoads.delete(key);
|
|
state.fetchers.delete(key);
|
|
}
|
|
function queueFetcherForDeletion(key) {
|
|
let count = (activeFetchers.get(key) || 0) - 1;
|
|
if (count <= 0) {
|
|
activeFetchers.delete(key);
|
|
fetchersQueuedForDeletion.add(key);
|
|
} else {
|
|
activeFetchers.set(key, count);
|
|
}
|
|
updateState({ fetchers: new Map(state.fetchers) });
|
|
}
|
|
function abortFetcher(key, reason) {
|
|
let controller = fetchControllers.get(key);
|
|
if (controller) {
|
|
controller.abort(reason);
|
|
fetchControllers.delete(key);
|
|
}
|
|
}
|
|
function markFetchersDone(keys) {
|
|
for (let key of keys) {
|
|
let fetcher = getFetcher(key);
|
|
let doneFetcher = getDoneFetcher(fetcher.data);
|
|
state.fetchers.set(key, doneFetcher);
|
|
}
|
|
}
|
|
function markFetchRedirectsDone() {
|
|
let doneKeys = [];
|
|
let updatedFetchers = false;
|
|
for (let key of fetchRedirectIds) {
|
|
let fetcher = state.fetchers.get(key);
|
|
invariant(fetcher, `Expected fetcher: ${key}`);
|
|
if (fetcher.state === "loading") {
|
|
fetchRedirectIds.delete(key);
|
|
doneKeys.push(key);
|
|
updatedFetchers = true;
|
|
}
|
|
}
|
|
markFetchersDone(doneKeys);
|
|
return updatedFetchers;
|
|
}
|
|
function abortStaleFetchLoads(landedId) {
|
|
let yeetedKeys = [];
|
|
for (let [key, id] of fetchReloadIds) {
|
|
if (id < landedId) {
|
|
let fetcher = state.fetchers.get(key);
|
|
invariant(fetcher, `Expected fetcher: ${key}`);
|
|
if (fetcher.state === "loading") {
|
|
abortFetcher(key);
|
|
fetchReloadIds.delete(key);
|
|
yeetedKeys.push(key);
|
|
}
|
|
}
|
|
}
|
|
markFetchersDone(yeetedKeys);
|
|
return yeetedKeys.length > 0;
|
|
}
|
|
function getBlocker(key, fn) {
|
|
let blocker = state.blockers.get(key) || IDLE_BLOCKER;
|
|
if (blockerFunctions.get(key) !== fn) {
|
|
blockerFunctions.set(key, fn);
|
|
}
|
|
return blocker;
|
|
}
|
|
function deleteBlocker(key) {
|
|
state.blockers.delete(key);
|
|
blockerFunctions.delete(key);
|
|
}
|
|
function updateBlocker(key, newBlocker) {
|
|
let blocker = state.blockers.get(key) || IDLE_BLOCKER;
|
|
invariant(
|
|
blocker.state === "unblocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "proceeding" || blocker.state === "blocked" && newBlocker.state === "unblocked" || blocker.state === "proceeding" && newBlocker.state === "unblocked",
|
|
`Invalid blocker state transition: ${blocker.state} -> ${newBlocker.state}`
|
|
);
|
|
let blockers = new Map(state.blockers);
|
|
blockers.set(key, newBlocker);
|
|
updateState({ blockers });
|
|
}
|
|
function shouldBlockNavigation({
|
|
currentLocation,
|
|
nextLocation,
|
|
historyAction
|
|
}) {
|
|
if (blockerFunctions.size === 0) {
|
|
return;
|
|
}
|
|
if (blockerFunctions.size > 1) {
|
|
warning(false, "A router only supports one blocker at a time");
|
|
}
|
|
let entries = Array.from(blockerFunctions.entries());
|
|
let [blockerKey, blockerFunction] = entries[entries.length - 1];
|
|
let blocker = state.blockers.get(blockerKey);
|
|
if (blocker && blocker.state === "proceeding") {
|
|
return;
|
|
}
|
|
if (blockerFunction({ currentLocation, nextLocation, historyAction })) {
|
|
return blockerKey;
|
|
}
|
|
}
|
|
function handleNavigational404(pathname) {
|
|
let error = getInternalRouterError(404, { pathname });
|
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
let { matches, route } = getShortCircuitMatches(routesToUse);
|
|
return { notFoundMatches: matches, route, error };
|
|
}
|
|
function enableScrollRestoration(positions, getPosition, getKey) {
|
|
savedScrollPositions2 = positions;
|
|
getScrollPosition = getPosition;
|
|
getScrollRestorationKey2 = getKey || null;
|
|
if (!initialScrollRestored && state.navigation === IDLE_NAVIGATION) {
|
|
initialScrollRestored = true;
|
|
let y = getSavedScrollPosition(state.location, state.matches);
|
|
if (y != null) {
|
|
updateState({ restoreScrollPosition: y });
|
|
}
|
|
}
|
|
return () => {
|
|
savedScrollPositions2 = null;
|
|
getScrollPosition = null;
|
|
getScrollRestorationKey2 = null;
|
|
};
|
|
}
|
|
function getScrollKey(location, matches) {
|
|
if (getScrollRestorationKey2) {
|
|
let key = getScrollRestorationKey2(
|
|
location,
|
|
matches.map((m) => convertRouteMatchToUiMatch(m, state.loaderData))
|
|
);
|
|
return key || location.key;
|
|
}
|
|
return location.key;
|
|
}
|
|
function saveScrollPosition(location, matches) {
|
|
if (savedScrollPositions2 && getScrollPosition) {
|
|
let key = getScrollKey(location, matches);
|
|
savedScrollPositions2[key] = getScrollPosition();
|
|
}
|
|
}
|
|
function getSavedScrollPosition(location, matches) {
|
|
if (savedScrollPositions2) {
|
|
let key = getScrollKey(location, matches);
|
|
let y = savedScrollPositions2[key];
|
|
if (typeof y === "number") {
|
|
return y;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function checkFogOfWar(matches, routesToUse, pathname) {
|
|
if (init.patchRoutesOnNavigation) {
|
|
if (!matches) {
|
|
let fogMatches = matchRoutesImpl(
|
|
routesToUse,
|
|
pathname,
|
|
basename,
|
|
true
|
|
);
|
|
return { active: true, matches: fogMatches || [] };
|
|
} else {
|
|
if (Object.keys(matches[0].params).length > 0) {
|
|
let partialMatches = matchRoutesImpl(
|
|
routesToUse,
|
|
pathname,
|
|
basename,
|
|
true
|
|
);
|
|
return { active: true, matches: partialMatches };
|
|
}
|
|
}
|
|
}
|
|
return { active: false, matches: null };
|
|
}
|
|
async function discoverRoutes(matches, pathname, signal, fetcherKey) {
|
|
if (!init.patchRoutesOnNavigation) {
|
|
return { type: "success", matches };
|
|
}
|
|
let partialMatches = matches;
|
|
while (true) {
|
|
let isNonHMR = inFlightDataRoutes == null;
|
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
let localManifest = manifest;
|
|
try {
|
|
await init.patchRoutesOnNavigation({
|
|
signal,
|
|
path: pathname,
|
|
matches: partialMatches,
|
|
fetcherKey,
|
|
patch: (routeId, children) => {
|
|
if (signal.aborted) return;
|
|
patchRoutesImpl(
|
|
routeId,
|
|
children,
|
|
routesToUse,
|
|
localManifest,
|
|
mapRouteProperties2,
|
|
false
|
|
);
|
|
}
|
|
});
|
|
} catch (e) {
|
|
return { type: "error", error: e, partialMatches };
|
|
} finally {
|
|
if (isNonHMR && !signal.aborted) {
|
|
dataRoutes = [...dataRoutes];
|
|
}
|
|
}
|
|
if (signal.aborted) {
|
|
return { type: "aborted" };
|
|
}
|
|
let newMatches = matchRoutes(routesToUse, pathname, basename);
|
|
if (newMatches) {
|
|
return { type: "success", matches: newMatches };
|
|
}
|
|
let newPartialMatches = matchRoutesImpl(
|
|
routesToUse,
|
|
pathname,
|
|
basename,
|
|
true
|
|
);
|
|
if (!newPartialMatches || partialMatches.length === newPartialMatches.length && partialMatches.every(
|
|
(m, i) => m.route.id === newPartialMatches[i].route.id
|
|
)) {
|
|
return { type: "success", matches: null };
|
|
}
|
|
partialMatches = newPartialMatches;
|
|
}
|
|
}
|
|
function _internalSetRoutes(newRoutes) {
|
|
manifest = {};
|
|
inFlightDataRoutes = convertRoutesToDataRoutes(
|
|
newRoutes,
|
|
mapRouteProperties2,
|
|
void 0,
|
|
manifest
|
|
);
|
|
}
|
|
function patchRoutes(routeId, children, unstable_allowElementMutations = false) {
|
|
let isNonHMR = inFlightDataRoutes == null;
|
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
patchRoutesImpl(
|
|
routeId,
|
|
children,
|
|
routesToUse,
|
|
manifest,
|
|
mapRouteProperties2,
|
|
unstable_allowElementMutations
|
|
);
|
|
if (isNonHMR) {
|
|
dataRoutes = [...dataRoutes];
|
|
updateState({});
|
|
}
|
|
}
|
|
router = {
|
|
get basename() {
|
|
return basename;
|
|
},
|
|
get future() {
|
|
return future;
|
|
},
|
|
get state() {
|
|
return state;
|
|
},
|
|
get routes() {
|
|
return dataRoutes;
|
|
},
|
|
get window() {
|
|
return routerWindow;
|
|
},
|
|
initialize,
|
|
subscribe,
|
|
enableScrollRestoration,
|
|
navigate,
|
|
fetch: fetch2,
|
|
revalidate,
|
|
// Passthrough to history-aware createHref used by useHref so we get proper
|
|
// hash-aware URLs in DOM paths
|
|
createHref: (to) => init.history.createHref(to),
|
|
encodeLocation: (to) => init.history.encodeLocation(to),
|
|
getFetcher,
|
|
resetFetcher,
|
|
deleteFetcher: queueFetcherForDeletion,
|
|
dispose,
|
|
getBlocker,
|
|
deleteBlocker,
|
|
patchRoutes,
|
|
_internalFetchControllers: fetchControllers,
|
|
// TODO: Remove setRoutes, it's temporary to avoid dealing with
|
|
// updating the tree while validating the update algorithm.
|
|
_internalSetRoutes,
|
|
_internalSetStateDoNotUseOrYouWillBreakYourApp(newState) {
|
|
updateState(newState);
|
|
}
|
|
};
|
|
return router;
|
|
}
|
|
function createStaticHandler(routes, opts) {
|
|
invariant(
|
|
routes.length > 0,
|
|
"You must provide a non-empty routes array to createStaticHandler"
|
|
);
|
|
let manifest = {};
|
|
let basename = (opts ? opts.basename : null) || "/";
|
|
let mapRouteProperties2 = opts?.mapRouteProperties || defaultMapRouteProperties;
|
|
let dataRoutes = convertRoutesToDataRoutes(
|
|
routes,
|
|
mapRouteProperties2,
|
|
void 0,
|
|
manifest
|
|
);
|
|
async function query(request, {
|
|
requestContext,
|
|
filterMatchesToLoad,
|
|
skipLoaderErrorBubbling,
|
|
skipRevalidation,
|
|
dataStrategy,
|
|
generateMiddlewareResponse
|
|
} = {}) {
|
|
let url = new URL(request.url);
|
|
let method = request.method;
|
|
let location = createLocation("", createPath(url), null, "default");
|
|
let matches = matchRoutes(dataRoutes, location, basename);
|
|
requestContext = requestContext != null ? requestContext : new RouterContextProvider();
|
|
if (!isValidMethod(method) && method !== "HEAD") {
|
|
let error = getInternalRouterError(405, { method });
|
|
let { matches: methodNotAllowedMatches, route } = getShortCircuitMatches(dataRoutes);
|
|
let staticContext = {
|
|
basename,
|
|
location,
|
|
matches: methodNotAllowedMatches,
|
|
loaderData: {},
|
|
actionData: null,
|
|
errors: {
|
|
[route.id]: error
|
|
},
|
|
statusCode: error.status,
|
|
loaderHeaders: {},
|
|
actionHeaders: {}
|
|
};
|
|
return generateMiddlewareResponse ? generateMiddlewareResponse(() => Promise.resolve(staticContext)) : staticContext;
|
|
} else if (!matches) {
|
|
let error = getInternalRouterError(404, { pathname: location.pathname });
|
|
let { matches: notFoundMatches, route } = getShortCircuitMatches(dataRoutes);
|
|
let staticContext = {
|
|
basename,
|
|
location,
|
|
matches: notFoundMatches,
|
|
loaderData: {},
|
|
actionData: null,
|
|
errors: {
|
|
[route.id]: error
|
|
},
|
|
statusCode: error.status,
|
|
loaderHeaders: {},
|
|
actionHeaders: {}
|
|
};
|
|
return generateMiddlewareResponse ? generateMiddlewareResponse(() => Promise.resolve(staticContext)) : staticContext;
|
|
}
|
|
if (generateMiddlewareResponse) {
|
|
invariant(
|
|
requestContext instanceof RouterContextProvider,
|
|
"When using middleware in `staticHandler.query()`, any provided `requestContext` must be an instance of `RouterContextProvider`"
|
|
);
|
|
try {
|
|
await loadLazyMiddlewareForMatches(
|
|
matches,
|
|
manifest,
|
|
mapRouteProperties2
|
|
);
|
|
let renderedStaticContext;
|
|
let response = await runServerMiddlewarePipeline(
|
|
{
|
|
request,
|
|
matches,
|
|
params: matches[0].params,
|
|
// If we're calling middleware then it must be enabled so we can cast
|
|
// this to the proper type knowing it's not an `AppLoadContext`
|
|
context: requestContext
|
|
},
|
|
async () => {
|
|
let res = await generateMiddlewareResponse(
|
|
async (revalidationRequest, opts2 = {}) => {
|
|
let result2 = await queryImpl(
|
|
revalidationRequest,
|
|
location,
|
|
matches,
|
|
requestContext,
|
|
dataStrategy || null,
|
|
skipLoaderErrorBubbling === true,
|
|
null,
|
|
"filterMatchesToLoad" in opts2 ? opts2.filterMatchesToLoad ?? null : filterMatchesToLoad ?? null,
|
|
skipRevalidation === true
|
|
);
|
|
if (isResponse(result2)) {
|
|
return result2;
|
|
}
|
|
renderedStaticContext = { location, basename, ...result2 };
|
|
return renderedStaticContext;
|
|
}
|
|
);
|
|
return res;
|
|
},
|
|
async (error, routeId) => {
|
|
if (isRedirectResponse(error)) {
|
|
return error;
|
|
}
|
|
if (isResponse(error)) {
|
|
try {
|
|
error = new ErrorResponseImpl(
|
|
error.status,
|
|
error.statusText,
|
|
await parseResponseBody(error)
|
|
);
|
|
} catch (e) {
|
|
error = e;
|
|
}
|
|
}
|
|
if (isDataWithResponseInit(error)) {
|
|
error = dataWithResponseInitToErrorResponse(error);
|
|
}
|
|
if (renderedStaticContext) {
|
|
if (routeId in renderedStaticContext.loaderData) {
|
|
renderedStaticContext.loaderData[routeId] = void 0;
|
|
}
|
|
let staticContext = getStaticContextFromError(
|
|
dataRoutes,
|
|
renderedStaticContext,
|
|
error,
|
|
skipLoaderErrorBubbling ? routeId : findNearestBoundary(matches, routeId).route.id
|
|
);
|
|
return generateMiddlewareResponse(
|
|
() => Promise.resolve(staticContext)
|
|
);
|
|
} else {
|
|
let boundaryRouteId = skipLoaderErrorBubbling ? routeId : findNearestBoundary(
|
|
matches,
|
|
matches.find(
|
|
(m) => m.route.id === routeId || m.route.loader
|
|
)?.route.id || routeId
|
|
).route.id;
|
|
let staticContext = {
|
|
matches,
|
|
location,
|
|
basename,
|
|
loaderData: {},
|
|
actionData: null,
|
|
errors: {
|
|
[boundaryRouteId]: error
|
|
},
|
|
statusCode: isRouteErrorResponse(error) ? error.status : 500,
|
|
actionHeaders: {},
|
|
loaderHeaders: {}
|
|
};
|
|
return generateMiddlewareResponse(
|
|
() => Promise.resolve(staticContext)
|
|
);
|
|
}
|
|
}
|
|
);
|
|
invariant(isResponse(response), "Expected a response in query()");
|
|
return response;
|
|
} catch (e) {
|
|
if (isResponse(e)) {
|
|
return e;
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
let result = await queryImpl(
|
|
request,
|
|
location,
|
|
matches,
|
|
requestContext,
|
|
dataStrategy || null,
|
|
skipLoaderErrorBubbling === true,
|
|
null,
|
|
filterMatchesToLoad || null,
|
|
skipRevalidation === true
|
|
);
|
|
if (isResponse(result)) {
|
|
return result;
|
|
}
|
|
return { location, basename, ...result };
|
|
}
|
|
async function queryRoute(request, {
|
|
routeId,
|
|
requestContext,
|
|
dataStrategy,
|
|
generateMiddlewareResponse
|
|
} = {}) {
|
|
let url = new URL(request.url);
|
|
let method = request.method;
|
|
let location = createLocation("", createPath(url), null, "default");
|
|
let matches = matchRoutes(dataRoutes, location, basename);
|
|
requestContext = requestContext != null ? requestContext : new RouterContextProvider();
|
|
if (!isValidMethod(method) && method !== "HEAD" && method !== "OPTIONS") {
|
|
throw getInternalRouterError(405, { method });
|
|
} else if (!matches) {
|
|
throw getInternalRouterError(404, { pathname: location.pathname });
|
|
}
|
|
let match = routeId ? matches.find((m) => m.route.id === routeId) : getTargetMatch(matches, location);
|
|
if (routeId && !match) {
|
|
throw getInternalRouterError(403, {
|
|
pathname: location.pathname,
|
|
routeId
|
|
});
|
|
} else if (!match) {
|
|
throw getInternalRouterError(404, { pathname: location.pathname });
|
|
}
|
|
if (generateMiddlewareResponse) {
|
|
invariant(
|
|
requestContext instanceof RouterContextProvider,
|
|
"When using middleware in `staticHandler.queryRoute()`, any provided `requestContext` must be an instance of `RouterContextProvider`"
|
|
);
|
|
await loadLazyMiddlewareForMatches(matches, manifest, mapRouteProperties2);
|
|
let response = await runServerMiddlewarePipeline(
|
|
{
|
|
request,
|
|
matches,
|
|
params: matches[0].params,
|
|
// If we're calling middleware then it must be enabled so we can cast
|
|
// this to the proper type knowing it's not an `AppLoadContext`
|
|
context: requestContext
|
|
},
|
|
async () => {
|
|
let res = await generateMiddlewareResponse(
|
|
async (innerRequest) => {
|
|
let result2 = await queryImpl(
|
|
innerRequest,
|
|
location,
|
|
matches,
|
|
requestContext,
|
|
dataStrategy || null,
|
|
false,
|
|
match,
|
|
null,
|
|
false
|
|
);
|
|
let processed = handleQueryResult(result2);
|
|
return isResponse(processed) ? processed : typeof processed === "string" ? new Response(processed) : Response.json(processed);
|
|
}
|
|
);
|
|
return res;
|
|
},
|
|
(error) => {
|
|
if (isDataWithResponseInit(error)) {
|
|
return Promise.resolve(dataWithResponseInitToResponse(error));
|
|
}
|
|
if (isResponse(error)) {
|
|
return Promise.resolve(error);
|
|
}
|
|
throw error;
|
|
}
|
|
);
|
|
return response;
|
|
}
|
|
let result = await queryImpl(
|
|
request,
|
|
location,
|
|
matches,
|
|
requestContext,
|
|
dataStrategy || null,
|
|
false,
|
|
match,
|
|
null,
|
|
false
|
|
);
|
|
return handleQueryResult(result);
|
|
function handleQueryResult(result2) {
|
|
if (isResponse(result2)) {
|
|
return result2;
|
|
}
|
|
let error = result2.errors ? Object.values(result2.errors)[0] : void 0;
|
|
if (error !== void 0) {
|
|
throw error;
|
|
}
|
|
if (result2.actionData) {
|
|
return Object.values(result2.actionData)[0];
|
|
}
|
|
if (result2.loaderData) {
|
|
return Object.values(result2.loaderData)[0];
|
|
}
|
|
return void 0;
|
|
}
|
|
}
|
|
async function queryImpl(request, location, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch, filterMatchesToLoad, skipRevalidation) {
|
|
invariant(
|
|
request.signal,
|
|
"query()/queryRoute() requests must contain an AbortController signal"
|
|
);
|
|
try {
|
|
if (isMutationMethod(request.method)) {
|
|
let result2 = await submit(
|
|
request,
|
|
matches,
|
|
routeMatch || getTargetMatch(matches, location),
|
|
requestContext,
|
|
dataStrategy,
|
|
skipLoaderErrorBubbling,
|
|
routeMatch != null,
|
|
filterMatchesToLoad,
|
|
skipRevalidation
|
|
);
|
|
return result2;
|
|
}
|
|
let result = await loadRouteData(
|
|
request,
|
|
matches,
|
|
requestContext,
|
|
dataStrategy,
|
|
skipLoaderErrorBubbling,
|
|
routeMatch,
|
|
filterMatchesToLoad
|
|
);
|
|
return isResponse(result) ? result : {
|
|
...result,
|
|
actionData: null,
|
|
actionHeaders: {}
|
|
};
|
|
} catch (e) {
|
|
if (isDataStrategyResult(e) && isResponse(e.result)) {
|
|
if (e.type === "error" /* error */) {
|
|
throw e.result;
|
|
}
|
|
return e.result;
|
|
}
|
|
if (isRedirectResponse(e)) {
|
|
return e;
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
async function submit(request, matches, actionMatch, requestContext, dataStrategy, skipLoaderErrorBubbling, isRouteRequest, filterMatchesToLoad, skipRevalidation) {
|
|
let result;
|
|
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
let error = getInternalRouterError(405, {
|
|
method: request.method,
|
|
pathname: new URL(request.url).pathname,
|
|
routeId: actionMatch.route.id
|
|
});
|
|
if (isRouteRequest) {
|
|
throw error;
|
|
}
|
|
result = {
|
|
type: "error" /* error */,
|
|
error
|
|
};
|
|
} else {
|
|
let dsMatches = getTargetedDataStrategyMatches(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
request,
|
|
matches,
|
|
actionMatch,
|
|
[],
|
|
requestContext
|
|
);
|
|
let results = await callDataStrategy(
|
|
request,
|
|
dsMatches,
|
|
isRouteRequest,
|
|
requestContext,
|
|
dataStrategy
|
|
);
|
|
result = results[actionMatch.route.id];
|
|
if (request.signal.aborted) {
|
|
throwStaticHandlerAbortedError(request, isRouteRequest);
|
|
}
|
|
}
|
|
if (isRedirectResult(result)) {
|
|
throw new Response(null, {
|
|
status: result.response.status,
|
|
headers: {
|
|
Location: result.response.headers.get("Location")
|
|
}
|
|
});
|
|
}
|
|
if (isRouteRequest) {
|
|
if (isErrorResult(result)) {
|
|
throw result.error;
|
|
}
|
|
return {
|
|
matches: [actionMatch],
|
|
loaderData: {},
|
|
actionData: { [actionMatch.route.id]: result.data },
|
|
errors: null,
|
|
// Note: statusCode + headers are unused here since queryRoute will
|
|
// return the raw Response or value
|
|
statusCode: 200,
|
|
loaderHeaders: {},
|
|
actionHeaders: {}
|
|
};
|
|
}
|
|
if (skipRevalidation) {
|
|
if (isErrorResult(result)) {
|
|
let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
|
|
return {
|
|
statusCode: isRouteErrorResponse(result.error) ? result.error.status : result.statusCode != null ? result.statusCode : 500,
|
|
actionData: null,
|
|
actionHeaders: {
|
|
...result.headers ? { [actionMatch.route.id]: result.headers } : {}
|
|
},
|
|
matches,
|
|
loaderData: {},
|
|
errors: {
|
|
[boundaryMatch.route.id]: result.error
|
|
},
|
|
loaderHeaders: {}
|
|
};
|
|
} else {
|
|
return {
|
|
actionData: {
|
|
[actionMatch.route.id]: result.data
|
|
},
|
|
actionHeaders: result.headers ? { [actionMatch.route.id]: result.headers } : {},
|
|
matches,
|
|
loaderData: {},
|
|
errors: null,
|
|
statusCode: result.statusCode || 200,
|
|
loaderHeaders: {}
|
|
};
|
|
}
|
|
}
|
|
let loaderRequest = new Request(request.url, {
|
|
headers: request.headers,
|
|
redirect: request.redirect,
|
|
signal: request.signal
|
|
});
|
|
if (isErrorResult(result)) {
|
|
let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
|
|
let handlerContext2 = await loadRouteData(
|
|
loaderRequest,
|
|
matches,
|
|
requestContext,
|
|
dataStrategy,
|
|
skipLoaderErrorBubbling,
|
|
null,
|
|
filterMatchesToLoad,
|
|
[boundaryMatch.route.id, result]
|
|
);
|
|
return {
|
|
...handlerContext2,
|
|
statusCode: isRouteErrorResponse(result.error) ? result.error.status : result.statusCode != null ? result.statusCode : 500,
|
|
actionData: null,
|
|
actionHeaders: {
|
|
...result.headers ? { [actionMatch.route.id]: result.headers } : {}
|
|
}
|
|
};
|
|
}
|
|
let handlerContext = await loadRouteData(
|
|
loaderRequest,
|
|
matches,
|
|
requestContext,
|
|
dataStrategy,
|
|
skipLoaderErrorBubbling,
|
|
null,
|
|
filterMatchesToLoad
|
|
);
|
|
return {
|
|
...handlerContext,
|
|
actionData: {
|
|
[actionMatch.route.id]: result.data
|
|
},
|
|
// action status codes take precedence over loader status codes
|
|
...result.statusCode ? { statusCode: result.statusCode } : {},
|
|
actionHeaders: result.headers ? { [actionMatch.route.id]: result.headers } : {}
|
|
};
|
|
}
|
|
async function loadRouteData(request, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch, filterMatchesToLoad, pendingActionResult) {
|
|
let isRouteRequest = routeMatch != null;
|
|
if (isRouteRequest && !routeMatch?.route.loader && !routeMatch?.route.lazy) {
|
|
throw getInternalRouterError(400, {
|
|
method: request.method,
|
|
pathname: new URL(request.url).pathname,
|
|
routeId: routeMatch?.route.id
|
|
});
|
|
}
|
|
let dsMatches;
|
|
if (routeMatch) {
|
|
dsMatches = getTargetedDataStrategyMatches(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
request,
|
|
matches,
|
|
routeMatch,
|
|
[],
|
|
requestContext
|
|
);
|
|
} else {
|
|
let maxIdx = pendingActionResult && isErrorResult(pendingActionResult[1]) ? (
|
|
// Up to but not including the boundary
|
|
matches.findIndex((m) => m.route.id === pendingActionResult[0]) - 1
|
|
) : void 0;
|
|
dsMatches = matches.map((match, index) => {
|
|
if (maxIdx != null && index > maxIdx) {
|
|
return getDataStrategyMatch(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
request,
|
|
match,
|
|
[],
|
|
requestContext,
|
|
false
|
|
);
|
|
}
|
|
return getDataStrategyMatch(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
request,
|
|
match,
|
|
[],
|
|
requestContext,
|
|
(match.route.loader || match.route.lazy) != null && (!filterMatchesToLoad || filterMatchesToLoad(match))
|
|
);
|
|
});
|
|
}
|
|
if (!dataStrategy && !dsMatches.some((m) => m.shouldLoad)) {
|
|
return {
|
|
matches,
|
|
loaderData: {},
|
|
errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
|
|
[pendingActionResult[0]]: pendingActionResult[1].error
|
|
} : null,
|
|
statusCode: 200,
|
|
loaderHeaders: {}
|
|
};
|
|
}
|
|
let results = await callDataStrategy(
|
|
request,
|
|
dsMatches,
|
|
isRouteRequest,
|
|
requestContext,
|
|
dataStrategy
|
|
);
|
|
if (request.signal.aborted) {
|
|
throwStaticHandlerAbortedError(request, isRouteRequest);
|
|
}
|
|
let handlerContext = processRouteLoaderData(
|
|
matches,
|
|
results,
|
|
pendingActionResult,
|
|
true,
|
|
skipLoaderErrorBubbling
|
|
);
|
|
return {
|
|
...handlerContext,
|
|
matches
|
|
};
|
|
}
|
|
async function callDataStrategy(request, matches, isRouteRequest, requestContext, dataStrategy) {
|
|
let results = await callDataStrategyImpl(
|
|
dataStrategy || defaultDataStrategy,
|
|
request,
|
|
matches,
|
|
null,
|
|
requestContext,
|
|
true
|
|
);
|
|
let dataResults = {};
|
|
await Promise.all(
|
|
matches.map(async (match) => {
|
|
if (!(match.route.id in results)) {
|
|
return;
|
|
}
|
|
let result = results[match.route.id];
|
|
if (isRedirectDataStrategyResult(result)) {
|
|
let response = result.result;
|
|
throw normalizeRelativeRoutingRedirectResponse(
|
|
response,
|
|
request,
|
|
match.route.id,
|
|
matches,
|
|
basename
|
|
);
|
|
}
|
|
if (isRouteRequest) {
|
|
if (isResponse(result.result)) {
|
|
throw result;
|
|
} else if (isDataWithResponseInit(result.result)) {
|
|
throw dataWithResponseInitToResponse(result.result);
|
|
}
|
|
}
|
|
dataResults[match.route.id] = await convertDataStrategyResultToDataResult(result);
|
|
})
|
|
);
|
|
return dataResults;
|
|
}
|
|
return {
|
|
dataRoutes,
|
|
query,
|
|
queryRoute
|
|
};
|
|
}
|
|
function getStaticContextFromError(routes, handlerContext, error, boundaryId) {
|
|
let errorBoundaryId = boundaryId || handlerContext._deepestRenderedBoundaryId || routes[0].id;
|
|
return {
|
|
...handlerContext,
|
|
statusCode: isRouteErrorResponse(error) ? error.status : 500,
|
|
errors: {
|
|
[errorBoundaryId]: error
|
|
}
|
|
};
|
|
}
|
|
function throwStaticHandlerAbortedError(request, isRouteRequest) {
|
|
if (request.signal.reason !== void 0) {
|
|
throw request.signal.reason;
|
|
}
|
|
let method = isRouteRequest ? "queryRoute" : "query";
|
|
throw new Error(
|
|
`${method}() call aborted without an \`AbortSignal.reason\`: ${request.method} ${request.url}`
|
|
);
|
|
}
|
|
function isSubmissionNavigation(opts) {
|
|
return opts != null && ("formData" in opts && opts.formData != null || "body" in opts && opts.body !== void 0);
|
|
}
|
|
function normalizeTo(location, matches, basename, to, fromRouteId, relative) {
|
|
let contextualMatches;
|
|
let activeRouteMatch;
|
|
if (fromRouteId) {
|
|
contextualMatches = [];
|
|
for (let match of matches) {
|
|
contextualMatches.push(match);
|
|
if (match.route.id === fromRouteId) {
|
|
activeRouteMatch = match;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
contextualMatches = matches;
|
|
activeRouteMatch = matches[matches.length - 1];
|
|
}
|
|
let path = resolveTo(
|
|
to ? to : ".",
|
|
getResolveToMatches(contextualMatches),
|
|
stripBasename(location.pathname, basename) || location.pathname,
|
|
relative === "path"
|
|
);
|
|
if (to == null) {
|
|
path.search = location.search;
|
|
path.hash = location.hash;
|
|
}
|
|
if ((to == null || to === "" || to === ".") && activeRouteMatch) {
|
|
let nakedIndex = hasNakedIndexQuery(path.search);
|
|
if (activeRouteMatch.route.index && !nakedIndex) {
|
|
path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
|
|
} else if (!activeRouteMatch.route.index && nakedIndex) {
|
|
let params = new URLSearchParams(path.search);
|
|
let indexValues = params.getAll("index");
|
|
params.delete("index");
|
|
indexValues.filter((v) => v).forEach((v) => params.append("index", v));
|
|
let qs = params.toString();
|
|
path.search = qs ? `?${qs}` : "";
|
|
}
|
|
}
|
|
if (basename !== "/") {
|
|
path.pathname = prependBasename({ basename, pathname: path.pathname });
|
|
}
|
|
return createPath(path);
|
|
}
|
|
function normalizeNavigateOptions(isFetcher, path, opts) {
|
|
if (!opts || !isSubmissionNavigation(opts)) {
|
|
return { path };
|
|
}
|
|
if (opts.formMethod && !isValidMethod(opts.formMethod)) {
|
|
return {
|
|
path,
|
|
error: getInternalRouterError(405, { method: opts.formMethod })
|
|
};
|
|
}
|
|
let getInvalidBodyError = () => ({
|
|
path,
|
|
error: getInternalRouterError(400, { type: "invalid-body" })
|
|
});
|
|
let rawFormMethod = opts.formMethod || "get";
|
|
let formMethod = rawFormMethod.toUpperCase();
|
|
let formAction = stripHashFromPath(path);
|
|
if (opts.body !== void 0) {
|
|
if (opts.formEncType === "text/plain") {
|
|
if (!isMutationMethod(formMethod)) {
|
|
return getInvalidBodyError();
|
|
}
|
|
let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ? (
|
|
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
|
|
Array.from(opts.body.entries()).reduce(
|
|
(acc, [name, value]) => `${acc}${name}=${value}
|
|
`,
|
|
""
|
|
)
|
|
) : String(opts.body);
|
|
return {
|
|
path,
|
|
submission: {
|
|
formMethod,
|
|
formAction,
|
|
formEncType: opts.formEncType,
|
|
formData: void 0,
|
|
json: void 0,
|
|
text
|
|
}
|
|
};
|
|
} else if (opts.formEncType === "application/json") {
|
|
if (!isMutationMethod(formMethod)) {
|
|
return getInvalidBodyError();
|
|
}
|
|
try {
|
|
let json = typeof opts.body === "string" ? JSON.parse(opts.body) : opts.body;
|
|
return {
|
|
path,
|
|
submission: {
|
|
formMethod,
|
|
formAction,
|
|
formEncType: opts.formEncType,
|
|
formData: void 0,
|
|
json,
|
|
text: void 0
|
|
}
|
|
};
|
|
} catch (e) {
|
|
return getInvalidBodyError();
|
|
}
|
|
}
|
|
}
|
|
invariant(
|
|
typeof FormData === "function",
|
|
"FormData is not available in this environment"
|
|
);
|
|
let searchParams;
|
|
let formData;
|
|
if (opts.formData) {
|
|
searchParams = convertFormDataToSearchParams(opts.formData);
|
|
formData = opts.formData;
|
|
} else if (opts.body instanceof FormData) {
|
|
searchParams = convertFormDataToSearchParams(opts.body);
|
|
formData = opts.body;
|
|
} else if (opts.body instanceof URLSearchParams) {
|
|
searchParams = opts.body;
|
|
formData = convertSearchParamsToFormData(searchParams);
|
|
} else if (opts.body == null) {
|
|
searchParams = new URLSearchParams();
|
|
formData = new FormData();
|
|
} else {
|
|
try {
|
|
searchParams = new URLSearchParams(opts.body);
|
|
formData = convertSearchParamsToFormData(searchParams);
|
|
} catch (e) {
|
|
return getInvalidBodyError();
|
|
}
|
|
}
|
|
let submission = {
|
|
formMethod,
|
|
formAction,
|
|
formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
|
|
formData,
|
|
json: void 0,
|
|
text: void 0
|
|
};
|
|
if (isMutationMethod(submission.formMethod)) {
|
|
return { path, submission };
|
|
}
|
|
let parsedPath = parsePath(path);
|
|
if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
|
|
searchParams.append("index", "");
|
|
}
|
|
parsedPath.search = `?${searchParams}`;
|
|
return { path: createPath(parsedPath), submission };
|
|
}
|
|
function getMatchesToLoad(request, scopedContext, mapRouteProperties2, manifest, history, state, matches, submission, location, lazyRoutePropertiesToSkip, initialHydration, isRevalidationRequired, cancelledFetcherLoads, fetchersQueuedForDeletion, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, hasPatchRoutesOnNavigation, pendingActionResult) {
|
|
let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : void 0;
|
|
let currentUrl = history.createURL(state.location);
|
|
let nextUrl = history.createURL(location);
|
|
let maxIdx;
|
|
if (initialHydration && state.errors) {
|
|
let boundaryId = Object.keys(state.errors)[0];
|
|
maxIdx = matches.findIndex((m) => m.route.id === boundaryId);
|
|
} else if (pendingActionResult && isErrorResult(pendingActionResult[1])) {
|
|
let boundaryId = pendingActionResult[0];
|
|
maxIdx = matches.findIndex((m) => m.route.id === boundaryId) - 1;
|
|
}
|
|
let actionStatus = pendingActionResult ? pendingActionResult[1].statusCode : void 0;
|
|
let shouldSkipRevalidation = actionStatus && actionStatus >= 400;
|
|
let baseShouldRevalidateArgs = {
|
|
currentUrl,
|
|
currentParams: state.matches[0]?.params || {},
|
|
nextUrl,
|
|
nextParams: matches[0].params,
|
|
...submission,
|
|
actionResult,
|
|
actionStatus
|
|
};
|
|
let dsMatches = matches.map((match, index) => {
|
|
let { route } = match;
|
|
let forceShouldLoad = null;
|
|
if (maxIdx != null && index > maxIdx) {
|
|
forceShouldLoad = false;
|
|
} else if (route.lazy) {
|
|
forceShouldLoad = true;
|
|
} else if (!routeHasLoaderOrMiddleware(route)) {
|
|
forceShouldLoad = false;
|
|
} else if (initialHydration) {
|
|
forceShouldLoad = shouldLoadRouteOnHydration(
|
|
route,
|
|
state.loaderData,
|
|
state.errors
|
|
);
|
|
} else if (isNewLoader(state.loaderData, state.matches[index], match)) {
|
|
forceShouldLoad = true;
|
|
}
|
|
if (forceShouldLoad !== null) {
|
|
return getDataStrategyMatch(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
request,
|
|
match,
|
|
lazyRoutePropertiesToSkip,
|
|
scopedContext,
|
|
forceShouldLoad
|
|
);
|
|
}
|
|
let defaultShouldRevalidate = shouldSkipRevalidation ? false : (
|
|
// Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
|
|
isRevalidationRequired || currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search || // Search params affect all loaders
|
|
currentUrl.search !== nextUrl.search || isNewRouteInstance(state.matches[index], match)
|
|
);
|
|
let shouldRevalidateArgs = {
|
|
...baseShouldRevalidateArgs,
|
|
defaultShouldRevalidate
|
|
};
|
|
let shouldLoad = shouldRevalidateLoader(match, shouldRevalidateArgs);
|
|
return getDataStrategyMatch(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
request,
|
|
match,
|
|
lazyRoutePropertiesToSkip,
|
|
scopedContext,
|
|
shouldLoad,
|
|
shouldRevalidateArgs
|
|
);
|
|
});
|
|
let revalidatingFetchers = [];
|
|
fetchLoadMatches.forEach((f, key) => {
|
|
if (initialHydration || !matches.some((m) => m.route.id === f.routeId) || fetchersQueuedForDeletion.has(key)) {
|
|
return;
|
|
}
|
|
let fetcher = state.fetchers.get(key);
|
|
let isMidInitialLoad = fetcher && fetcher.state !== "idle" && fetcher.data === void 0;
|
|
let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
|
|
if (!fetcherMatches) {
|
|
if (hasPatchRoutesOnNavigation && isMidInitialLoad) {
|
|
return;
|
|
}
|
|
revalidatingFetchers.push({
|
|
key,
|
|
routeId: f.routeId,
|
|
path: f.path,
|
|
matches: null,
|
|
match: null,
|
|
request: null,
|
|
controller: null
|
|
});
|
|
return;
|
|
}
|
|
if (fetchRedirectIds.has(key)) {
|
|
return;
|
|
}
|
|
let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
|
|
let fetchController = new AbortController();
|
|
let fetchRequest = createClientSideRequest(
|
|
history,
|
|
f.path,
|
|
fetchController.signal
|
|
);
|
|
let fetcherDsMatches = null;
|
|
if (cancelledFetcherLoads.has(key)) {
|
|
cancelledFetcherLoads.delete(key);
|
|
fetcherDsMatches = getTargetedDataStrategyMatches(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
fetchRequest,
|
|
fetcherMatches,
|
|
fetcherMatch,
|
|
lazyRoutePropertiesToSkip,
|
|
scopedContext
|
|
);
|
|
} else if (isMidInitialLoad) {
|
|
if (isRevalidationRequired) {
|
|
fetcherDsMatches = getTargetedDataStrategyMatches(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
fetchRequest,
|
|
fetcherMatches,
|
|
fetcherMatch,
|
|
lazyRoutePropertiesToSkip,
|
|
scopedContext
|
|
);
|
|
}
|
|
} else {
|
|
let shouldRevalidateArgs = {
|
|
...baseShouldRevalidateArgs,
|
|
defaultShouldRevalidate: shouldSkipRevalidation ? false : isRevalidationRequired
|
|
};
|
|
if (shouldRevalidateLoader(fetcherMatch, shouldRevalidateArgs)) {
|
|
fetcherDsMatches = getTargetedDataStrategyMatches(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
fetchRequest,
|
|
fetcherMatches,
|
|
fetcherMatch,
|
|
lazyRoutePropertiesToSkip,
|
|
scopedContext,
|
|
shouldRevalidateArgs
|
|
);
|
|
}
|
|
}
|
|
if (fetcherDsMatches) {
|
|
revalidatingFetchers.push({
|
|
key,
|
|
routeId: f.routeId,
|
|
path: f.path,
|
|
matches: fetcherDsMatches,
|
|
match: fetcherMatch,
|
|
request: fetchRequest,
|
|
controller: fetchController
|
|
});
|
|
}
|
|
});
|
|
return { dsMatches, revalidatingFetchers };
|
|
}
|
|
function routeHasLoaderOrMiddleware(route) {
|
|
return route.loader != null || route.middleware != null && route.middleware.length > 0;
|
|
}
|
|
function shouldLoadRouteOnHydration(route, loaderData, errors) {
|
|
if (route.lazy) {
|
|
return true;
|
|
}
|
|
if (!routeHasLoaderOrMiddleware(route)) {
|
|
return false;
|
|
}
|
|
let hasData = loaderData != null && route.id in loaderData;
|
|
let hasError = errors != null && errors[route.id] !== void 0;
|
|
if (!hasData && hasError) {
|
|
return false;
|
|
}
|
|
if (typeof route.loader === "function" && route.loader.hydrate === true) {
|
|
return true;
|
|
}
|
|
return !hasData && !hasError;
|
|
}
|
|
function isNewLoader(currentLoaderData, currentMatch, match) {
|
|
let isNew = (
|
|
// [a] -> [a, b]
|
|
!currentMatch || // [a, b] -> [a, c]
|
|
match.route.id !== currentMatch.route.id
|
|
);
|
|
let isMissingData = !currentLoaderData.hasOwnProperty(match.route.id);
|
|
return isNew || isMissingData;
|
|
}
|
|
function isNewRouteInstance(currentMatch, match) {
|
|
let currentPath = currentMatch.route.path;
|
|
return (
|
|
// param change for this match, /users/123 -> /users/456
|
|
currentMatch.pathname !== match.pathname || // splat param changed, which is not present in match.path
|
|
// e.g. /files/images/avatar.jpg -> files/finances.xls
|
|
currentPath != null && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
|
|
);
|
|
}
|
|
function shouldRevalidateLoader(loaderMatch, arg) {
|
|
if (loaderMatch.route.shouldRevalidate) {
|
|
let routeChoice = loaderMatch.route.shouldRevalidate(arg);
|
|
if (typeof routeChoice === "boolean") {
|
|
return routeChoice;
|
|
}
|
|
}
|
|
return arg.defaultShouldRevalidate;
|
|
}
|
|
function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties2, allowElementMutations) {
|
|
let childrenToPatch;
|
|
if (routeId) {
|
|
let route = manifest[routeId];
|
|
invariant(
|
|
route,
|
|
`No route found to patch children into: routeId = ${routeId}`
|
|
);
|
|
if (!route.children) {
|
|
route.children = [];
|
|
}
|
|
childrenToPatch = route.children;
|
|
} else {
|
|
childrenToPatch = routesToUse;
|
|
}
|
|
let uniqueChildren = [];
|
|
let existingChildren = [];
|
|
children.forEach((newRoute) => {
|
|
let existingRoute = childrenToPatch.find(
|
|
(existingRoute2) => isSameRoute(newRoute, existingRoute2)
|
|
);
|
|
if (existingRoute) {
|
|
existingChildren.push({ existingRoute, newRoute });
|
|
} else {
|
|
uniqueChildren.push(newRoute);
|
|
}
|
|
});
|
|
if (uniqueChildren.length > 0) {
|
|
let newRoutes = convertRoutesToDataRoutes(
|
|
uniqueChildren,
|
|
mapRouteProperties2,
|
|
[routeId || "_", "patch", String(childrenToPatch?.length || "0")],
|
|
manifest
|
|
);
|
|
childrenToPatch.push(...newRoutes);
|
|
}
|
|
if (allowElementMutations && existingChildren.length > 0) {
|
|
for (let i = 0; i < existingChildren.length; i++) {
|
|
let { existingRoute, newRoute } = existingChildren[i];
|
|
let existingRouteTyped = existingRoute;
|
|
let [newRouteTyped] = convertRoutesToDataRoutes(
|
|
[newRoute],
|
|
mapRouteProperties2,
|
|
[],
|
|
// Doesn't matter for mutated routes since they already have an id
|
|
{},
|
|
// Don't touch the manifest here since we're updating in place
|
|
true
|
|
);
|
|
Object.assign(existingRouteTyped, {
|
|
element: newRouteTyped.element ? newRouteTyped.element : existingRouteTyped.element,
|
|
errorElement: newRouteTyped.errorElement ? newRouteTyped.errorElement : existingRouteTyped.errorElement,
|
|
hydrateFallbackElement: newRouteTyped.hydrateFallbackElement ? newRouteTyped.hydrateFallbackElement : existingRouteTyped.hydrateFallbackElement
|
|
});
|
|
}
|
|
}
|
|
}
|
|
function isSameRoute(newRoute, existingRoute) {
|
|
if ("id" in newRoute && "id" in existingRoute && newRoute.id === existingRoute.id) {
|
|
return true;
|
|
}
|
|
if (!(newRoute.index === existingRoute.index && newRoute.path === existingRoute.path && newRoute.caseSensitive === existingRoute.caseSensitive)) {
|
|
return false;
|
|
}
|
|
if ((!newRoute.children || newRoute.children.length === 0) && (!existingRoute.children || existingRoute.children.length === 0)) {
|
|
return true;
|
|
}
|
|
return newRoute.children.every(
|
|
(aChild, i) => existingRoute.children?.some((bChild) => isSameRoute(aChild, bChild))
|
|
);
|
|
}
|
|
var lazyRoutePropertyCache = /* @__PURE__ */ new WeakMap();
|
|
var loadLazyRouteProperty = ({
|
|
key,
|
|
route,
|
|
manifest,
|
|
mapRouteProperties: mapRouteProperties2
|
|
}) => {
|
|
let routeToUpdate = manifest[route.id];
|
|
invariant(routeToUpdate, "No route found in manifest");
|
|
if (!routeToUpdate.lazy || typeof routeToUpdate.lazy !== "object") {
|
|
return;
|
|
}
|
|
let lazyFn = routeToUpdate.lazy[key];
|
|
if (!lazyFn) {
|
|
return;
|
|
}
|
|
let cache = lazyRoutePropertyCache.get(routeToUpdate);
|
|
if (!cache) {
|
|
cache = {};
|
|
lazyRoutePropertyCache.set(routeToUpdate, cache);
|
|
}
|
|
let cachedPromise = cache[key];
|
|
if (cachedPromise) {
|
|
return cachedPromise;
|
|
}
|
|
let propertyPromise = (async () => {
|
|
let isUnsupported = isUnsupportedLazyRouteObjectKey(key);
|
|
let staticRouteValue = routeToUpdate[key];
|
|
let isStaticallyDefined = staticRouteValue !== void 0 && key !== "hasErrorBoundary";
|
|
if (isUnsupported) {
|
|
warning(
|
|
!isUnsupported,
|
|
"Route property " + key + " is not a supported lazy route property. This property will be ignored."
|
|
);
|
|
cache[key] = Promise.resolve();
|
|
} else if (isStaticallyDefined) {
|
|
warning(
|
|
false,
|
|
`Route "${routeToUpdate.id}" has a static property "${key}" defined. The lazy property will be ignored.`
|
|
);
|
|
} else {
|
|
let value = await lazyFn();
|
|
if (value != null) {
|
|
Object.assign(routeToUpdate, { [key]: value });
|
|
Object.assign(routeToUpdate, mapRouteProperties2(routeToUpdate));
|
|
}
|
|
}
|
|
if (typeof routeToUpdate.lazy === "object") {
|
|
routeToUpdate.lazy[key] = void 0;
|
|
if (Object.values(routeToUpdate.lazy).every((value) => value === void 0)) {
|
|
routeToUpdate.lazy = void 0;
|
|
}
|
|
}
|
|
})();
|
|
cache[key] = propertyPromise;
|
|
return propertyPromise;
|
|
};
|
|
var lazyRouteFunctionCache = /* @__PURE__ */ new WeakMap();
|
|
function loadLazyRoute(route, type, manifest, mapRouteProperties2, lazyRoutePropertiesToSkip) {
|
|
let routeToUpdate = manifest[route.id];
|
|
invariant(routeToUpdate, "No route found in manifest");
|
|
if (!route.lazy) {
|
|
return {
|
|
lazyRoutePromise: void 0,
|
|
lazyHandlerPromise: void 0
|
|
};
|
|
}
|
|
if (typeof route.lazy === "function") {
|
|
let cachedPromise = lazyRouteFunctionCache.get(routeToUpdate);
|
|
if (cachedPromise) {
|
|
return {
|
|
lazyRoutePromise: cachedPromise,
|
|
lazyHandlerPromise: cachedPromise
|
|
};
|
|
}
|
|
let lazyRoutePromise2 = (async () => {
|
|
invariant(
|
|
typeof route.lazy === "function",
|
|
"No lazy route function found"
|
|
);
|
|
let lazyRoute = await route.lazy();
|
|
let routeUpdates = {};
|
|
for (let lazyRouteProperty in lazyRoute) {
|
|
let lazyValue = lazyRoute[lazyRouteProperty];
|
|
if (lazyValue === void 0) {
|
|
continue;
|
|
}
|
|
let isUnsupported = isUnsupportedLazyRouteFunctionKey(lazyRouteProperty);
|
|
let staticRouteValue = routeToUpdate[lazyRouteProperty];
|
|
let isStaticallyDefined = staticRouteValue !== void 0 && // This property isn't static since it should always be updated based
|
|
// on the route updates
|
|
lazyRouteProperty !== "hasErrorBoundary";
|
|
if (isUnsupported) {
|
|
warning(
|
|
!isUnsupported,
|
|
"Route property " + lazyRouteProperty + " is not a supported property to be returned from a lazy route function. This property will be ignored."
|
|
);
|
|
} else if (isStaticallyDefined) {
|
|
warning(
|
|
!isStaticallyDefined,
|
|
`Route "${routeToUpdate.id}" has a static property "${lazyRouteProperty}" defined but its lazy function is also returning a value for this property. The lazy route property "${lazyRouteProperty}" will be ignored.`
|
|
);
|
|
} else {
|
|
routeUpdates[lazyRouteProperty] = lazyValue;
|
|
}
|
|
}
|
|
Object.assign(routeToUpdate, routeUpdates);
|
|
Object.assign(routeToUpdate, {
|
|
// To keep things framework agnostic, we use the provided `mapRouteProperties`
|
|
// function to set the framework-aware properties (`element`/`hasErrorBoundary`)
|
|
// since the logic will differ between frameworks.
|
|
...mapRouteProperties2(routeToUpdate),
|
|
lazy: void 0
|
|
});
|
|
})();
|
|
lazyRouteFunctionCache.set(routeToUpdate, lazyRoutePromise2);
|
|
lazyRoutePromise2.catch(() => {
|
|
});
|
|
return {
|
|
lazyRoutePromise: lazyRoutePromise2,
|
|
lazyHandlerPromise: lazyRoutePromise2
|
|
};
|
|
}
|
|
let lazyKeys = Object.keys(route.lazy);
|
|
let lazyPropertyPromises = [];
|
|
let lazyHandlerPromise = void 0;
|
|
for (let key of lazyKeys) {
|
|
if (lazyRoutePropertiesToSkip && lazyRoutePropertiesToSkip.includes(key)) {
|
|
continue;
|
|
}
|
|
let promise = loadLazyRouteProperty({
|
|
key,
|
|
route,
|
|
manifest,
|
|
mapRouteProperties: mapRouteProperties2
|
|
});
|
|
if (promise) {
|
|
lazyPropertyPromises.push(promise);
|
|
if (key === type) {
|
|
lazyHandlerPromise = promise;
|
|
}
|
|
}
|
|
}
|
|
let lazyRoutePromise = lazyPropertyPromises.length > 0 ? Promise.all(lazyPropertyPromises).then(() => {
|
|
}) : void 0;
|
|
lazyRoutePromise?.catch(() => {
|
|
});
|
|
lazyHandlerPromise?.catch(() => {
|
|
});
|
|
return {
|
|
lazyRoutePromise,
|
|
lazyHandlerPromise
|
|
};
|
|
}
|
|
function isNonNullable(value) {
|
|
return value !== void 0;
|
|
}
|
|
function loadLazyMiddlewareForMatches(matches, manifest, mapRouteProperties2) {
|
|
let promises = matches.map(({ route }) => {
|
|
if (typeof route.lazy !== "object" || !route.lazy.middleware) {
|
|
return void 0;
|
|
}
|
|
return loadLazyRouteProperty({
|
|
key: "middleware",
|
|
route,
|
|
manifest,
|
|
mapRouteProperties: mapRouteProperties2
|
|
});
|
|
}).filter(isNonNullable);
|
|
return promises.length > 0 ? Promise.all(promises) : void 0;
|
|
}
|
|
async function defaultDataStrategy(args) {
|
|
let matchesToLoad = args.matches.filter((m) => m.shouldLoad);
|
|
let keyedResults = {};
|
|
let results = await Promise.all(matchesToLoad.map((m) => m.resolve()));
|
|
results.forEach((result, i) => {
|
|
keyedResults[matchesToLoad[i].route.id] = result;
|
|
});
|
|
return keyedResults;
|
|
}
|
|
async function defaultDataStrategyWithMiddleware(args) {
|
|
if (!args.matches.some((m) => m.route.middleware)) {
|
|
return defaultDataStrategy(args);
|
|
}
|
|
return runClientMiddlewarePipeline(args, () => defaultDataStrategy(args));
|
|
}
|
|
function runServerMiddlewarePipeline(args, handler, errorHandler) {
|
|
return runMiddlewarePipeline(
|
|
args,
|
|
handler,
|
|
processResult,
|
|
isResponse,
|
|
errorHandler
|
|
);
|
|
function processResult(result) {
|
|
return isDataWithResponseInit(result) ? dataWithResponseInitToResponse(result) : result;
|
|
}
|
|
}
|
|
function runClientMiddlewarePipeline(args, handler) {
|
|
return runMiddlewarePipeline(
|
|
args,
|
|
handler,
|
|
(r) => r,
|
|
// No post-processing needed on the client
|
|
isDataStrategyResults,
|
|
errorHandler
|
|
);
|
|
function errorHandler(error, routeId, nextResult) {
|
|
if (nextResult) {
|
|
return Promise.resolve(
|
|
Object.assign(nextResult.value, {
|
|
[routeId]: { type: "error", result: error }
|
|
})
|
|
);
|
|
} else {
|
|
let { matches } = args;
|
|
let maxBoundaryIdx = Math.min(
|
|
// Throwing route
|
|
Math.max(
|
|
matches.findIndex((m) => m.route.id === routeId),
|
|
0
|
|
),
|
|
// or the shallowest route that needs to load data
|
|
Math.max(
|
|
matches.findIndex((m) => m.unstable_shouldCallHandler()),
|
|
0
|
|
)
|
|
);
|
|
let boundaryRouteId = findNearestBoundary(
|
|
matches,
|
|
matches[maxBoundaryIdx].route.id
|
|
).route.id;
|
|
return Promise.resolve({
|
|
[boundaryRouteId]: { type: "error", result: error }
|
|
});
|
|
}
|
|
}
|
|
}
|
|
async function runMiddlewarePipeline(args, handler, processResult, isResult, errorHandler) {
|
|
let { matches, request, params, context } = args;
|
|
let tuples = matches.flatMap(
|
|
(m) => m.route.middleware ? m.route.middleware.map((fn) => [m.route.id, fn]) : []
|
|
);
|
|
let result = await callRouteMiddleware(
|
|
{ request, params, context },
|
|
tuples,
|
|
handler,
|
|
processResult,
|
|
isResult,
|
|
errorHandler
|
|
);
|
|
return result;
|
|
}
|
|
async function callRouteMiddleware(args, middlewares, handler, processResult, isResult, errorHandler, idx = 0) {
|
|
let { request } = args;
|
|
if (request.signal.aborted) {
|
|
throw request.signal.reason ?? new Error(`Request aborted: ${request.method} ${request.url}`);
|
|
}
|
|
let tuple = middlewares[idx];
|
|
if (!tuple) {
|
|
let result = await handler();
|
|
return result;
|
|
}
|
|
let [routeId, middleware] = tuple;
|
|
let nextResult;
|
|
let next = async () => {
|
|
if (nextResult) {
|
|
throw new Error("You may only call `next()` once per middleware");
|
|
}
|
|
try {
|
|
let result = await callRouteMiddleware(
|
|
args,
|
|
middlewares,
|
|
handler,
|
|
processResult,
|
|
isResult,
|
|
errorHandler,
|
|
idx + 1
|
|
);
|
|
nextResult = { value: result };
|
|
return nextResult.value;
|
|
} catch (error) {
|
|
nextResult = { value: await errorHandler(error, routeId, nextResult) };
|
|
return nextResult.value;
|
|
}
|
|
};
|
|
try {
|
|
let value = await middleware(args, next);
|
|
let result = value != null ? processResult(value) : void 0;
|
|
if (isResult(result)) {
|
|
return result;
|
|
} else if (nextResult) {
|
|
return result ?? nextResult.value;
|
|
} else {
|
|
nextResult = { value: await next() };
|
|
return nextResult.value;
|
|
}
|
|
} catch (error) {
|
|
let response = await errorHandler(error, routeId, nextResult);
|
|
return response;
|
|
}
|
|
}
|
|
function getDataStrategyMatchLazyPromises(mapRouteProperties2, manifest, request, match, lazyRoutePropertiesToSkip) {
|
|
let lazyMiddlewarePromise = loadLazyRouteProperty({
|
|
key: "middleware",
|
|
route: match.route,
|
|
manifest,
|
|
mapRouteProperties: mapRouteProperties2
|
|
});
|
|
let lazyRoutePromises = loadLazyRoute(
|
|
match.route,
|
|
isMutationMethod(request.method) ? "action" : "loader",
|
|
manifest,
|
|
mapRouteProperties2,
|
|
lazyRoutePropertiesToSkip
|
|
);
|
|
return {
|
|
middleware: lazyMiddlewarePromise,
|
|
route: lazyRoutePromises.lazyRoutePromise,
|
|
handler: lazyRoutePromises.lazyHandlerPromise
|
|
};
|
|
}
|
|
function getDataStrategyMatch(mapRouteProperties2, manifest, request, match, lazyRoutePropertiesToSkip, scopedContext, shouldLoad, unstable_shouldRevalidateArgs = null) {
|
|
let isUsingNewApi = false;
|
|
let _lazyPromises = getDataStrategyMatchLazyPromises(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
request,
|
|
match,
|
|
lazyRoutePropertiesToSkip
|
|
);
|
|
return {
|
|
...match,
|
|
_lazyPromises,
|
|
shouldLoad,
|
|
unstable_shouldRevalidateArgs,
|
|
unstable_shouldCallHandler(defaultShouldRevalidate) {
|
|
isUsingNewApi = true;
|
|
if (!unstable_shouldRevalidateArgs) {
|
|
return shouldLoad;
|
|
}
|
|
if (typeof defaultShouldRevalidate === "boolean") {
|
|
return shouldRevalidateLoader(match, {
|
|
...unstable_shouldRevalidateArgs,
|
|
defaultShouldRevalidate
|
|
});
|
|
}
|
|
return shouldRevalidateLoader(match, unstable_shouldRevalidateArgs);
|
|
},
|
|
resolve(handlerOverride) {
|
|
let { lazy, loader, middleware } = match.route;
|
|
let callHandler = isUsingNewApi || shouldLoad || handlerOverride && !isMutationMethod(request.method) && (lazy || loader);
|
|
let isMiddlewareOnlyRoute = middleware && middleware.length > 0 && !loader && !lazy;
|
|
if (callHandler && !isMiddlewareOnlyRoute) {
|
|
return callLoaderOrAction({
|
|
request,
|
|
match,
|
|
lazyHandlerPromise: _lazyPromises?.handler,
|
|
lazyRoutePromise: _lazyPromises?.route,
|
|
handlerOverride,
|
|
scopedContext
|
|
});
|
|
}
|
|
return Promise.resolve({ type: "data" /* data */, result: void 0 });
|
|
}
|
|
};
|
|
}
|
|
function getTargetedDataStrategyMatches(mapRouteProperties2, manifest, request, matches, targetMatch, lazyRoutePropertiesToSkip, scopedContext, shouldRevalidateArgs = null) {
|
|
return matches.map((match) => {
|
|
if (match.route.id !== targetMatch.route.id) {
|
|
return {
|
|
...match,
|
|
shouldLoad: false,
|
|
unstable_shouldRevalidateArgs: shouldRevalidateArgs,
|
|
unstable_shouldCallHandler: () => false,
|
|
_lazyPromises: getDataStrategyMatchLazyPromises(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
request,
|
|
match,
|
|
lazyRoutePropertiesToSkip
|
|
),
|
|
resolve: () => Promise.resolve({ type: "data", result: void 0 })
|
|
};
|
|
}
|
|
return getDataStrategyMatch(
|
|
mapRouteProperties2,
|
|
manifest,
|
|
request,
|
|
match,
|
|
lazyRoutePropertiesToSkip,
|
|
scopedContext,
|
|
true,
|
|
shouldRevalidateArgs
|
|
);
|
|
});
|
|
}
|
|
async function callDataStrategyImpl(dataStrategyImpl, request, matches, fetcherKey, scopedContext, isStaticHandler) {
|
|
if (matches.some((m) => m._lazyPromises?.middleware)) {
|
|
await Promise.all(matches.map((m) => m._lazyPromises?.middleware));
|
|
}
|
|
let dataStrategyArgs = {
|
|
request,
|
|
params: matches[0].params,
|
|
context: scopedContext,
|
|
matches
|
|
};
|
|
let runClientMiddleware = isStaticHandler ? () => {
|
|
throw new Error(
|
|
"You cannot call `runClientMiddleware()` from a static handler `dataStrategy`. Middleware is run outside of `dataStrategy` during SSR in order to bubble up the Response. You can enable middleware via the `respond` API in `query`/`queryRoute`"
|
|
);
|
|
} : (cb) => {
|
|
let typedDataStrategyArgs = dataStrategyArgs;
|
|
return runClientMiddlewarePipeline(typedDataStrategyArgs, () => {
|
|
return cb({
|
|
...typedDataStrategyArgs,
|
|
fetcherKey,
|
|
runClientMiddleware: () => {
|
|
throw new Error(
|
|
"Cannot call `runClientMiddleware()` from within an `runClientMiddleware` handler"
|
|
);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
let results = await dataStrategyImpl({
|
|
...dataStrategyArgs,
|
|
fetcherKey,
|
|
runClientMiddleware
|
|
});
|
|
try {
|
|
await Promise.all(
|
|
matches.flatMap((m) => [
|
|
m._lazyPromises?.handler,
|
|
m._lazyPromises?.route
|
|
])
|
|
);
|
|
} catch (e) {
|
|
}
|
|
return results;
|
|
}
|
|
async function callLoaderOrAction({
|
|
request,
|
|
match,
|
|
lazyHandlerPromise,
|
|
lazyRoutePromise,
|
|
handlerOverride,
|
|
scopedContext
|
|
}) {
|
|
let result;
|
|
let onReject;
|
|
let isAction = isMutationMethod(request.method);
|
|
let type = isAction ? "action" : "loader";
|
|
let runHandler = (handler) => {
|
|
let reject;
|
|
let abortPromise = new Promise((_, r) => reject = r);
|
|
onReject = () => reject();
|
|
request.signal.addEventListener("abort", onReject);
|
|
let actualHandler = (ctx) => {
|
|
if (typeof handler !== "function") {
|
|
return Promise.reject(
|
|
new Error(
|
|
`You cannot call the handler for a route which defines a boolean "${type}" [routeId: ${match.route.id}]`
|
|
)
|
|
);
|
|
}
|
|
return handler(
|
|
{
|
|
request,
|
|
params: match.params,
|
|
context: scopedContext
|
|
},
|
|
...ctx !== void 0 ? [ctx] : []
|
|
);
|
|
};
|
|
let handlerPromise = (async () => {
|
|
try {
|
|
let val = await (handlerOverride ? handlerOverride((ctx) => actualHandler(ctx)) : actualHandler());
|
|
return { type: "data", result: val };
|
|
} catch (e) {
|
|
return { type: "error", result: e };
|
|
}
|
|
})();
|
|
return Promise.race([handlerPromise, abortPromise]);
|
|
};
|
|
try {
|
|
let handler = isAction ? match.route.action : match.route.loader;
|
|
if (lazyHandlerPromise || lazyRoutePromise) {
|
|
if (handler) {
|
|
let handlerError;
|
|
let [value] = await Promise.all([
|
|
// If the handler throws, don't let it immediately bubble out,
|
|
// since we need to let the lazy() execution finish so we know if this
|
|
// route has a boundary that can handle the error
|
|
runHandler(handler).catch((e) => {
|
|
handlerError = e;
|
|
}),
|
|
// Ensure all lazy route promises are resolved before continuing
|
|
lazyHandlerPromise,
|
|
lazyRoutePromise
|
|
]);
|
|
if (handlerError !== void 0) {
|
|
throw handlerError;
|
|
}
|
|
result = value;
|
|
} else {
|
|
await lazyHandlerPromise;
|
|
let handler2 = isAction ? match.route.action : match.route.loader;
|
|
if (handler2) {
|
|
[result] = await Promise.all([runHandler(handler2), lazyRoutePromise]);
|
|
} else if (type === "action") {
|
|
let url = new URL(request.url);
|
|
let pathname = url.pathname + url.search;
|
|
throw getInternalRouterError(405, {
|
|
method: request.method,
|
|
pathname,
|
|
routeId: match.route.id
|
|
});
|
|
} else {
|
|
return { type: "data" /* data */, result: void 0 };
|
|
}
|
|
}
|
|
} else if (!handler) {
|
|
let url = new URL(request.url);
|
|
let pathname = url.pathname + url.search;
|
|
throw getInternalRouterError(404, {
|
|
pathname
|
|
});
|
|
} else {
|
|
result = await runHandler(handler);
|
|
}
|
|
} catch (e) {
|
|
return { type: "error" /* error */, result: e };
|
|
} finally {
|
|
if (onReject) {
|
|
request.signal.removeEventListener("abort", onReject);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
async function parseResponseBody(response) {
|
|
let contentType = response.headers.get("Content-Type");
|
|
if (contentType && /\bapplication\/json\b/.test(contentType)) {
|
|
return response.body == null ? null : response.json();
|
|
}
|
|
return response.text();
|
|
}
|
|
async function convertDataStrategyResultToDataResult(dataStrategyResult) {
|
|
let { result, type } = dataStrategyResult;
|
|
if (isResponse(result)) {
|
|
let data2;
|
|
try {
|
|
data2 = await parseResponseBody(result);
|
|
} catch (e) {
|
|
return { type: "error" /* error */, error: e };
|
|
}
|
|
if (type === "error" /* error */) {
|
|
return {
|
|
type: "error" /* error */,
|
|
error: new ErrorResponseImpl(result.status, result.statusText, data2),
|
|
statusCode: result.status,
|
|
headers: result.headers
|
|
};
|
|
}
|
|
return {
|
|
type: "data" /* data */,
|
|
data: data2,
|
|
statusCode: result.status,
|
|
headers: result.headers
|
|
};
|
|
}
|
|
if (type === "error" /* error */) {
|
|
if (isDataWithResponseInit(result)) {
|
|
if (result.data instanceof Error) {
|
|
return {
|
|
type: "error" /* error */,
|
|
error: result.data,
|
|
statusCode: result.init?.status,
|
|
headers: result.init?.headers ? new Headers(result.init.headers) : void 0
|
|
};
|
|
}
|
|
return {
|
|
type: "error" /* error */,
|
|
error: new ErrorResponseImpl(
|
|
result.init?.status || 500,
|
|
void 0,
|
|
result.data
|
|
),
|
|
statusCode: isRouteErrorResponse(result) ? result.status : void 0,
|
|
headers: result.init?.headers ? new Headers(result.init.headers) : void 0
|
|
};
|
|
}
|
|
return {
|
|
type: "error" /* error */,
|
|
error: result,
|
|
statusCode: isRouteErrorResponse(result) ? result.status : void 0
|
|
};
|
|
}
|
|
if (isDataWithResponseInit(result)) {
|
|
return {
|
|
type: "data" /* data */,
|
|
data: result.data,
|
|
statusCode: result.init?.status,
|
|
headers: result.init?.headers ? new Headers(result.init.headers) : void 0
|
|
};
|
|
}
|
|
return { type: "data" /* data */, data: result };
|
|
}
|
|
function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename) {
|
|
let location = response.headers.get("Location");
|
|
invariant(
|
|
location,
|
|
"Redirects returned/thrown from loaders/actions must have a Location header"
|
|
);
|
|
if (!isAbsoluteUrl(location)) {
|
|
let trimmedMatches = matches.slice(
|
|
0,
|
|
matches.findIndex((m) => m.route.id === routeId) + 1
|
|
);
|
|
location = normalizeTo(
|
|
new URL(request.url),
|
|
trimmedMatches,
|
|
basename,
|
|
location
|
|
);
|
|
response.headers.set("Location", location);
|
|
}
|
|
return response;
|
|
}
|
|
function normalizeRedirectLocation(location, currentUrl, basename) {
|
|
if (isAbsoluteUrl(location)) {
|
|
let normalizedLocation = location;
|
|
let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
|
|
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
return url.pathname + url.search + url.hash;
|
|
}
|
|
}
|
|
return location;
|
|
}
|
|
function createClientSideRequest(history, location, signal, submission) {
|
|
let url = history.createURL(stripHashFromPath(location)).toString();
|
|
let init = { signal };
|
|
if (submission && isMutationMethod(submission.formMethod)) {
|
|
let { formMethod, formEncType } = submission;
|
|
init.method = formMethod.toUpperCase();
|
|
if (formEncType === "application/json") {
|
|
init.headers = new Headers({ "Content-Type": formEncType });
|
|
init.body = JSON.stringify(submission.json);
|
|
} else if (formEncType === "text/plain") {
|
|
init.body = submission.text;
|
|
} else if (formEncType === "application/x-www-form-urlencoded" && submission.formData) {
|
|
init.body = convertFormDataToSearchParams(submission.formData);
|
|
} else {
|
|
init.body = submission.formData;
|
|
}
|
|
}
|
|
return new Request(url, init);
|
|
}
|
|
function convertFormDataToSearchParams(formData) {
|
|
let searchParams = new URLSearchParams();
|
|
for (let [key, value] of formData.entries()) {
|
|
searchParams.append(key, typeof value === "string" ? value : value.name);
|
|
}
|
|
return searchParams;
|
|
}
|
|
function convertSearchParamsToFormData(searchParams) {
|
|
let formData = new FormData();
|
|
for (let [key, value] of searchParams.entries()) {
|
|
formData.append(key, value);
|
|
}
|
|
return formData;
|
|
}
|
|
function processRouteLoaderData(matches, results, pendingActionResult, isStaticHandler = false, skipLoaderErrorBubbling = false) {
|
|
let loaderData = {};
|
|
let errors = null;
|
|
let statusCode;
|
|
let foundError = false;
|
|
let loaderHeaders = {};
|
|
let pendingError = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : void 0;
|
|
matches.forEach((match) => {
|
|
if (!(match.route.id in results)) {
|
|
return;
|
|
}
|
|
let id = match.route.id;
|
|
let result = results[id];
|
|
invariant(
|
|
!isRedirectResult(result),
|
|
"Cannot handle redirect results in processLoaderData"
|
|
);
|
|
if (isErrorResult(result)) {
|
|
let error = result.error;
|
|
if (pendingError !== void 0) {
|
|
error = pendingError;
|
|
pendingError = void 0;
|
|
}
|
|
errors = errors || {};
|
|
if (skipLoaderErrorBubbling) {
|
|
errors[id] = error;
|
|
} else {
|
|
let boundaryMatch = findNearestBoundary(matches, id);
|
|
if (errors[boundaryMatch.route.id] == null) {
|
|
errors[boundaryMatch.route.id] = error;
|
|
}
|
|
}
|
|
if (!isStaticHandler) {
|
|
loaderData[id] = ResetLoaderDataSymbol;
|
|
}
|
|
if (!foundError) {
|
|
foundError = true;
|
|
statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
|
|
}
|
|
if (result.headers) {
|
|
loaderHeaders[id] = result.headers;
|
|
}
|
|
} else {
|
|
loaderData[id] = result.data;
|
|
if (result.statusCode && result.statusCode !== 200 && !foundError) {
|
|
statusCode = result.statusCode;
|
|
}
|
|
if (result.headers) {
|
|
loaderHeaders[id] = result.headers;
|
|
}
|
|
}
|
|
});
|
|
if (pendingError !== void 0 && pendingActionResult) {
|
|
errors = { [pendingActionResult[0]]: pendingError };
|
|
if (pendingActionResult[2]) {
|
|
loaderData[pendingActionResult[2]] = void 0;
|
|
}
|
|
}
|
|
return {
|
|
loaderData,
|
|
errors,
|
|
statusCode: statusCode || 200,
|
|
loaderHeaders
|
|
};
|
|
}
|
|
function processLoaderData(state, matches, results, pendingActionResult, revalidatingFetchers, fetcherResults) {
|
|
let { loaderData, errors } = processRouteLoaderData(
|
|
matches,
|
|
results,
|
|
pendingActionResult
|
|
);
|
|
revalidatingFetchers.filter((f) => !f.matches || f.matches.some((m) => m.shouldLoad)).forEach((rf) => {
|
|
let { key, match, controller } = rf;
|
|
if (controller && controller.signal.aborted) {
|
|
return;
|
|
}
|
|
let result = fetcherResults[key];
|
|
invariant(result, "Did not find corresponding fetcher result");
|
|
if (isErrorResult(result)) {
|
|
let boundaryMatch = findNearestBoundary(state.matches, match?.route.id);
|
|
if (!(errors && errors[boundaryMatch.route.id])) {
|
|
errors = {
|
|
...errors,
|
|
[boundaryMatch.route.id]: result.error
|
|
};
|
|
}
|
|
state.fetchers.delete(key);
|
|
} else if (isRedirectResult(result)) {
|
|
invariant(false, "Unhandled fetcher revalidation redirect");
|
|
} else {
|
|
let doneFetcher = getDoneFetcher(result.data);
|
|
state.fetchers.set(key, doneFetcher);
|
|
}
|
|
});
|
|
return { loaderData, errors };
|
|
}
|
|
function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
|
|
let mergedLoaderData = Object.entries(newLoaderData).filter(([, v]) => v !== ResetLoaderDataSymbol).reduce((merged, [k, v]) => {
|
|
merged[k] = v;
|
|
return merged;
|
|
}, {});
|
|
for (let match of matches) {
|
|
let id = match.route.id;
|
|
if (!newLoaderData.hasOwnProperty(id) && loaderData.hasOwnProperty(id) && match.route.loader) {
|
|
mergedLoaderData[id] = loaderData[id];
|
|
}
|
|
if (errors && errors.hasOwnProperty(id)) {
|
|
break;
|
|
}
|
|
}
|
|
return mergedLoaderData;
|
|
}
|
|
function getActionDataForCommit(pendingActionResult) {
|
|
if (!pendingActionResult) {
|
|
return {};
|
|
}
|
|
return isErrorResult(pendingActionResult[1]) ? {
|
|
// Clear out prior actionData on errors
|
|
actionData: {}
|
|
} : {
|
|
actionData: {
|
|
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
}
|
|
};
|
|
}
|
|
function findNearestBoundary(matches, routeId) {
|
|
let eligibleMatches = routeId ? matches.slice(0, matches.findIndex((m) => m.route.id === routeId) + 1) : [...matches];
|
|
return eligibleMatches.reverse().find((m) => m.route.hasErrorBoundary === true) || matches[0];
|
|
}
|
|
function getShortCircuitMatches(routes) {
|
|
let route = routes.length === 1 ? routes[0] : routes.find((r) => r.index || !r.path || r.path === "/") || {
|
|
id: `__shim-error-route__`
|
|
};
|
|
return {
|
|
matches: [
|
|
{
|
|
params: {},
|
|
pathname: "",
|
|
pathnameBase: "",
|
|
route
|
|
}
|
|
],
|
|
route
|
|
};
|
|
}
|
|
function getInternalRouterError(status, {
|
|
pathname,
|
|
routeId,
|
|
method,
|
|
type,
|
|
message
|
|
} = {}) {
|
|
let statusText = "Unknown Server Error";
|
|
let errorMessage = "Unknown @remix-run/router error";
|
|
if (status === 400) {
|
|
statusText = "Bad Request";
|
|
if (method && pathname && routeId) {
|
|
errorMessage = `You made a ${method} request to "${pathname}" but did not provide a \`loader\` for route "${routeId}", so there is no way to handle the request.`;
|
|
} else if (type === "invalid-body") {
|
|
errorMessage = "Unable to encode submission body";
|
|
}
|
|
} else if (status === 403) {
|
|
statusText = "Forbidden";
|
|
errorMessage = `Route "${routeId}" does not match URL "${pathname}"`;
|
|
} else if (status === 404) {
|
|
statusText = "Not Found";
|
|
errorMessage = `No route matches URL "${pathname}"`;
|
|
} else if (status === 405) {
|
|
statusText = "Method Not Allowed";
|
|
if (method && pathname && routeId) {
|
|
errorMessage = `You made a ${method.toUpperCase()} request to "${pathname}" but did not provide an \`action\` for route "${routeId}", so there is no way to handle the request.`;
|
|
} else if (method) {
|
|
errorMessage = `Invalid request method "${method.toUpperCase()}"`;
|
|
}
|
|
}
|
|
return new ErrorResponseImpl(
|
|
status || 500,
|
|
statusText,
|
|
new Error(errorMessage),
|
|
true
|
|
);
|
|
}
|
|
function findRedirect(results) {
|
|
let entries = Object.entries(results);
|
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
let [key, result] = entries[i];
|
|
if (isRedirectResult(result)) {
|
|
return { key, result };
|
|
}
|
|
}
|
|
}
|
|
function stripHashFromPath(path) {
|
|
let parsedPath = typeof path === "string" ? parsePath(path) : path;
|
|
return createPath({ ...parsedPath, hash: "" });
|
|
}
|
|
function isHashChangeOnly(a, b) {
|
|
if (a.pathname !== b.pathname || a.search !== b.search) {
|
|
return false;
|
|
}
|
|
if (a.hash === "") {
|
|
return b.hash !== "";
|
|
} else if (a.hash === b.hash) {
|
|
return true;
|
|
} else if (b.hash !== "") {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function dataWithResponseInitToResponse(data2) {
|
|
return Response.json(data2.data, data2.init ?? void 0);
|
|
}
|
|
function dataWithResponseInitToErrorResponse(data2) {
|
|
return new ErrorResponseImpl(
|
|
data2.init?.status ?? 500,
|
|
data2.init?.statusText ?? "Internal Server Error",
|
|
data2.data
|
|
);
|
|
}
|
|
function isDataStrategyResults(result) {
|
|
return result != null && typeof result === "object" && Object.entries(result).every(
|
|
([key, value]) => typeof key === "string" && isDataStrategyResult(value)
|
|
);
|
|
}
|
|
function isDataStrategyResult(result) {
|
|
return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === "data" /* data */ || result.type === "error" /* error */);
|
|
}
|
|
function isRedirectDataStrategyResult(result) {
|
|
return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
|
|
}
|
|
function isErrorResult(result) {
|
|
return result.type === "error" /* error */;
|
|
}
|
|
function isRedirectResult(result) {
|
|
return (result && result.type) === "redirect" /* redirect */;
|
|
}
|
|
function isDataWithResponseInit(value) {
|
|
return typeof value === "object" && value != null && "type" in value && "data" in value && "init" in value && value.type === "DataWithResponseInit";
|
|
}
|
|
function isResponse(value) {
|
|
return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
|
|
}
|
|
function isRedirectStatusCode(statusCode) {
|
|
return redirectStatusCodes.has(statusCode);
|
|
}
|
|
function isRedirectResponse(result) {
|
|
return isResponse(result) && isRedirectStatusCode(result.status) && result.headers.has("Location");
|
|
}
|
|
function isValidMethod(method) {
|
|
return validRequestMethods.has(method.toUpperCase());
|
|
}
|
|
function isMutationMethod(method) {
|
|
return validMutationMethods.has(method.toUpperCase());
|
|
}
|
|
function hasNakedIndexQuery(search) {
|
|
return new URLSearchParams(search).getAll("index").some((v) => v === "");
|
|
}
|
|
function getTargetMatch(matches, location) {
|
|
let search = typeof location === "string" ? parsePath(location).search : location.search;
|
|
if (matches[matches.length - 1].route.index && hasNakedIndexQuery(search || "")) {
|
|
return matches[matches.length - 1];
|
|
}
|
|
let pathMatches = getPathContributingMatches(matches);
|
|
return pathMatches[pathMatches.length - 1];
|
|
}
|
|
function getSubmissionFromNavigation(navigation) {
|
|
let { formMethod, formAction, formEncType, text, formData, json } = navigation;
|
|
if (!formMethod || !formAction || !formEncType) {
|
|
return;
|
|
}
|
|
if (text != null) {
|
|
return {
|
|
formMethod,
|
|
formAction,
|
|
formEncType,
|
|
formData: void 0,
|
|
json: void 0,
|
|
text
|
|
};
|
|
} else if (formData != null) {
|
|
return {
|
|
formMethod,
|
|
formAction,
|
|
formEncType,
|
|
formData,
|
|
json: void 0,
|
|
text: void 0
|
|
};
|
|
} else if (json !== void 0) {
|
|
return {
|
|
formMethod,
|
|
formAction,
|
|
formEncType,
|
|
formData: void 0,
|
|
json,
|
|
text: void 0
|
|
};
|
|
}
|
|
}
|
|
function getLoadingNavigation(location, submission) {
|
|
if (submission) {
|
|
let navigation = {
|
|
state: "loading",
|
|
location,
|
|
formMethod: submission.formMethod,
|
|
formAction: submission.formAction,
|
|
formEncType: submission.formEncType,
|
|
formData: submission.formData,
|
|
json: submission.json,
|
|
text: submission.text
|
|
};
|
|
return navigation;
|
|
} else {
|
|
let navigation = {
|
|
state: "loading",
|
|
location,
|
|
formMethod: void 0,
|
|
formAction: void 0,
|
|
formEncType: void 0,
|
|
formData: void 0,
|
|
json: void 0,
|
|
text: void 0
|
|
};
|
|
return navigation;
|
|
}
|
|
}
|
|
function getSubmittingNavigation(location, submission) {
|
|
let navigation = {
|
|
state: "submitting",
|
|
location,
|
|
formMethod: submission.formMethod,
|
|
formAction: submission.formAction,
|
|
formEncType: submission.formEncType,
|
|
formData: submission.formData,
|
|
json: submission.json,
|
|
text: submission.text
|
|
};
|
|
return navigation;
|
|
}
|
|
function getLoadingFetcher(submission, data2) {
|
|
if (submission) {
|
|
let fetcher = {
|
|
state: "loading",
|
|
formMethod: submission.formMethod,
|
|
formAction: submission.formAction,
|
|
formEncType: submission.formEncType,
|
|
formData: submission.formData,
|
|
json: submission.json,
|
|
text: submission.text,
|
|
data: data2
|
|
};
|
|
return fetcher;
|
|
} else {
|
|
let fetcher = {
|
|
state: "loading",
|
|
formMethod: void 0,
|
|
formAction: void 0,
|
|
formEncType: void 0,
|
|
formData: void 0,
|
|
json: void 0,
|
|
text: void 0,
|
|
data: data2
|
|
};
|
|
return fetcher;
|
|
}
|
|
}
|
|
function getSubmittingFetcher(submission, existingFetcher) {
|
|
let fetcher = {
|
|
state: "submitting",
|
|
formMethod: submission.formMethod,
|
|
formAction: submission.formAction,
|
|
formEncType: submission.formEncType,
|
|
formData: submission.formData,
|
|
json: submission.json,
|
|
text: submission.text,
|
|
data: existingFetcher ? existingFetcher.data : void 0
|
|
};
|
|
return fetcher;
|
|
}
|
|
function getDoneFetcher(data2) {
|
|
let fetcher = {
|
|
state: "idle",
|
|
formMethod: void 0,
|
|
formAction: void 0,
|
|
formEncType: void 0,
|
|
formData: void 0,
|
|
json: void 0,
|
|
text: void 0,
|
|
data: data2
|
|
};
|
|
return fetcher;
|
|
}
|
|
function restoreAppliedTransitions(_window, transitions) {
|
|
try {
|
|
let sessionPositions = _window.sessionStorage.getItem(
|
|
TRANSITIONS_STORAGE_KEY
|
|
);
|
|
if (sessionPositions) {
|
|
let json = JSON.parse(sessionPositions);
|
|
for (let [k, v] of Object.entries(json || {})) {
|
|
if (v && Array.isArray(v)) {
|
|
transitions.set(k, new Set(v || []));
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
}
|
|
}
|
|
function persistAppliedTransitions(_window, transitions) {
|
|
if (transitions.size > 0) {
|
|
let json = {};
|
|
for (let [k, v] of transitions) {
|
|
json[k] = [...v];
|
|
}
|
|
try {
|
|
_window.sessionStorage.setItem(
|
|
TRANSITIONS_STORAGE_KEY,
|
|
JSON.stringify(json)
|
|
);
|
|
} catch (error) {
|
|
warning(
|
|
false,
|
|
`Failed to save applied view transitions in sessionStorage (${error}).`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
function createDeferred() {
|
|
let resolve;
|
|
let reject;
|
|
let promise = new Promise((res, rej) => {
|
|
resolve = async (val) => {
|
|
res(val);
|
|
try {
|
|
await promise;
|
|
} catch (e) {
|
|
}
|
|
};
|
|
reject = async (error) => {
|
|
rej(error);
|
|
try {
|
|
await promise;
|
|
} catch (e) {
|
|
}
|
|
};
|
|
});
|
|
return {
|
|
promise,
|
|
//@ts-ignore
|
|
resolve,
|
|
//@ts-ignore
|
|
reject
|
|
};
|
|
}
|
|
|
|
// lib/context.ts
|
|
import * as React from "react";
|
|
var DataRouterContext = React.createContext(null);
|
|
DataRouterContext.displayName = "DataRouter";
|
|
var DataRouterStateContext = React.createContext(null);
|
|
DataRouterStateContext.displayName = "DataRouterState";
|
|
var RSCRouterContext = React.createContext(false);
|
|
function useIsRSCRouterContext() {
|
|
return React.useContext(RSCRouterContext);
|
|
}
|
|
var ViewTransitionContext = React.createContext({
|
|
isTransitioning: false
|
|
});
|
|
ViewTransitionContext.displayName = "ViewTransition";
|
|
var FetchersContext = React.createContext(
|
|
/* @__PURE__ */ new Map()
|
|
);
|
|
FetchersContext.displayName = "Fetchers";
|
|
var AwaitContext = React.createContext(null);
|
|
AwaitContext.displayName = "Await";
|
|
var AwaitContextProvider = (props) => React.createElement(AwaitContext.Provider, props);
|
|
var NavigationContext = React.createContext(
|
|
null
|
|
);
|
|
NavigationContext.displayName = "Navigation";
|
|
var LocationContext = React.createContext(
|
|
null
|
|
);
|
|
LocationContext.displayName = "Location";
|
|
var RouteContext = React.createContext({
|
|
outlet: null,
|
|
matches: [],
|
|
isDataRoute: false
|
|
});
|
|
RouteContext.displayName = "Route";
|
|
var RouteErrorContext = React.createContext(null);
|
|
RouteErrorContext.displayName = "RouteError";
|
|
var ENABLE_DEV_WARNINGS = false;
|
|
|
|
// lib/hooks.tsx
|
|
import * as React2 from "react";
|
|
function useHref(to, { relative } = {}) {
|
|
invariant(
|
|
useInRouterContext(),
|
|
// TODO: This error is probably because they somehow have 2 versions of the
|
|
// router loaded. We can help them understand how to avoid that.
|
|
`useHref() may be used only in the context of a <Router> component.`
|
|
);
|
|
let { basename, navigator } = React2.useContext(NavigationContext);
|
|
let { hash, pathname, search } = useResolvedPath(to, { relative });
|
|
let joinedPathname = pathname;
|
|
if (basename !== "/") {
|
|
joinedPathname = pathname === "/" ? basename : joinPaths([basename, pathname]);
|
|
}
|
|
return navigator.createHref({ pathname: joinedPathname, search, hash });
|
|
}
|
|
function useInRouterContext() {
|
|
return React2.useContext(LocationContext) != null;
|
|
}
|
|
function useLocation() {
|
|
invariant(
|
|
useInRouterContext(),
|
|
// TODO: This error is probably because they somehow have 2 versions of the
|
|
// router loaded. We can help them understand how to avoid that.
|
|
`useLocation() may be used only in the context of a <Router> component.`
|
|
);
|
|
return React2.useContext(LocationContext).location;
|
|
}
|
|
function useNavigationType() {
|
|
return React2.useContext(LocationContext).navigationType;
|
|
}
|
|
function useMatch(pattern) {
|
|
invariant(
|
|
useInRouterContext(),
|
|
// TODO: This error is probably because they somehow have 2 versions of the
|
|
// router loaded. We can help them understand how to avoid that.
|
|
`useMatch() may be used only in the context of a <Router> component.`
|
|
);
|
|
let { pathname } = useLocation();
|
|
return React2.useMemo(
|
|
() => matchPath(pattern, decodePath(pathname)),
|
|
[pathname, pattern]
|
|
);
|
|
}
|
|
var navigateEffectWarning = `You should call navigate() in a React.useEffect(), not when your component is first rendered.`;
|
|
function useIsomorphicLayoutEffect(cb) {
|
|
let isStatic = React2.useContext(NavigationContext).static;
|
|
if (!isStatic) {
|
|
React2.useLayoutEffect(cb);
|
|
}
|
|
}
|
|
function useNavigate() {
|
|
let { isDataRoute } = React2.useContext(RouteContext);
|
|
return isDataRoute ? useNavigateStable() : useNavigateUnstable();
|
|
}
|
|
function useNavigateUnstable() {
|
|
invariant(
|
|
useInRouterContext(),
|
|
// TODO: This error is probably because they somehow have 2 versions of the
|
|
// router loaded. We can help them understand how to avoid that.
|
|
`useNavigate() may be used only in the context of a <Router> component.`
|
|
);
|
|
let dataRouterContext = React2.useContext(DataRouterContext);
|
|
let { basename, navigator } = React2.useContext(NavigationContext);
|
|
let { matches } = React2.useContext(RouteContext);
|
|
let { pathname: locationPathname } = useLocation();
|
|
let routePathnamesJson = JSON.stringify(getResolveToMatches(matches));
|
|
let activeRef = React2.useRef(false);
|
|
useIsomorphicLayoutEffect(() => {
|
|
activeRef.current = true;
|
|
});
|
|
let navigate = React2.useCallback(
|
|
(to, options = {}) => {
|
|
warning(activeRef.current, navigateEffectWarning);
|
|
if (!activeRef.current) return;
|
|
if (typeof to === "number") {
|
|
navigator.go(to);
|
|
return;
|
|
}
|
|
let path = resolveTo(
|
|
to,
|
|
JSON.parse(routePathnamesJson),
|
|
locationPathname,
|
|
options.relative === "path"
|
|
);
|
|
if (dataRouterContext == null && basename !== "/") {
|
|
path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
|
|
}
|
|
(!!options.replace ? navigator.replace : navigator.push)(
|
|
path,
|
|
options.state,
|
|
options
|
|
);
|
|
},
|
|
[
|
|
basename,
|
|
navigator,
|
|
routePathnamesJson,
|
|
locationPathname,
|
|
dataRouterContext
|
|
]
|
|
);
|
|
return navigate;
|
|
}
|
|
var OutletContext = React2.createContext(null);
|
|
function useOutletContext() {
|
|
return React2.useContext(OutletContext);
|
|
}
|
|
function useOutlet(context) {
|
|
let outlet = React2.useContext(RouteContext).outlet;
|
|
return React2.useMemo(
|
|
() => outlet && /* @__PURE__ */ React2.createElement(OutletContext.Provider, { value: context }, outlet),
|
|
[outlet, context]
|
|
);
|
|
}
|
|
function useParams() {
|
|
let { matches } = React2.useContext(RouteContext);
|
|
let routeMatch = matches[matches.length - 1];
|
|
return routeMatch ? routeMatch.params : {};
|
|
}
|
|
function useResolvedPath(to, { relative } = {}) {
|
|
let { matches } = React2.useContext(RouteContext);
|
|
let { pathname: locationPathname } = useLocation();
|
|
let routePathnamesJson = JSON.stringify(getResolveToMatches(matches));
|
|
return React2.useMemo(
|
|
() => resolveTo(
|
|
to,
|
|
JSON.parse(routePathnamesJson),
|
|
locationPathname,
|
|
relative === "path"
|
|
),
|
|
[to, routePathnamesJson, locationPathname, relative]
|
|
);
|
|
}
|
|
function useRoutes(routes, locationArg) {
|
|
return useRoutesImpl(routes, locationArg);
|
|
}
|
|
function useRoutesImpl(routes, locationArg, dataRouterState, unstable_onError, future) {
|
|
invariant(
|
|
useInRouterContext(),
|
|
// TODO: This error is probably because they somehow have 2 versions of the
|
|
// router loaded. We can help them understand how to avoid that.
|
|
`useRoutes() may be used only in the context of a <Router> component.`
|
|
);
|
|
let { navigator } = React2.useContext(NavigationContext);
|
|
let { matches: parentMatches } = React2.useContext(RouteContext);
|
|
let routeMatch = parentMatches[parentMatches.length - 1];
|
|
let parentParams = routeMatch ? routeMatch.params : {};
|
|
let parentPathname = routeMatch ? routeMatch.pathname : "/";
|
|
let parentPathnameBase = routeMatch ? routeMatch.pathnameBase : "/";
|
|
let parentRoute = routeMatch && routeMatch.route;
|
|
if (ENABLE_DEV_WARNINGS) {
|
|
let parentPath = parentRoute && parentRoute.path || "";
|
|
warningOnce(
|
|
parentPathname,
|
|
!parentRoute || parentPath.endsWith("*") || parentPath.endsWith("*?"),
|
|
`You rendered descendant <Routes> (or called \`useRoutes()\`) at "${parentPathname}" (under <Route path="${parentPath}">) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render.
|
|
|
|
Please change the parent <Route path="${parentPath}"> to <Route path="${parentPath === "/" ? "*" : `${parentPath}/*`}">.`
|
|
);
|
|
}
|
|
let locationFromContext = useLocation();
|
|
let location;
|
|
if (locationArg) {
|
|
let parsedLocationArg = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
|
|
invariant(
|
|
parentPathnameBase === "/" || parsedLocationArg.pathname?.startsWith(parentPathnameBase),
|
|
`When overriding the location using \`<Routes location>\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${parentPathnameBase}" but pathname "${parsedLocationArg.pathname}" was given in the \`location\` prop.`
|
|
);
|
|
location = parsedLocationArg;
|
|
} else {
|
|
location = locationFromContext;
|
|
}
|
|
let pathname = location.pathname || "/";
|
|
let remainingPathname = pathname;
|
|
if (parentPathnameBase !== "/") {
|
|
let parentSegments = parentPathnameBase.replace(/^\//, "").split("/");
|
|
let segments = pathname.replace(/^\//, "").split("/");
|
|
remainingPathname = "/" + segments.slice(parentSegments.length).join("/");
|
|
}
|
|
let matches = matchRoutes(routes, { pathname: remainingPathname });
|
|
if (ENABLE_DEV_WARNINGS) {
|
|
warning(
|
|
parentRoute || matches != null,
|
|
`No routes matched location "${location.pathname}${location.search}${location.hash}" `
|
|
);
|
|
warning(
|
|
matches == null || matches[matches.length - 1].route.element !== void 0 || matches[matches.length - 1].route.Component !== void 0 || matches[matches.length - 1].route.lazy !== void 0,
|
|
`Matched leaf route at location "${location.pathname}${location.search}${location.hash}" does not have an element or Component. This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.`
|
|
);
|
|
}
|
|
let renderedMatches = _renderMatches(
|
|
matches && matches.map(
|
|
(match) => Object.assign({}, match, {
|
|
params: Object.assign({}, parentParams, match.params),
|
|
pathname: joinPaths([
|
|
parentPathnameBase,
|
|
// Re-encode pathnames that were decoded inside matchRoutes.
|
|
// Pre-encode `?` and `#` ahead of `encodeLocation` because it uses
|
|
// `new URL()` internally and we need to prevent it from treating
|
|
// them as separators
|
|
navigator.encodeLocation ? navigator.encodeLocation(
|
|
match.pathname.replace(/\?/g, "%3F").replace(/#/g, "%23")
|
|
).pathname : match.pathname
|
|
]),
|
|
pathnameBase: match.pathnameBase === "/" ? parentPathnameBase : joinPaths([
|
|
parentPathnameBase,
|
|
// Re-encode pathnames that were decoded inside matchRoutes
|
|
// Pre-encode `?` and `#` ahead of `encodeLocation` because it uses
|
|
// `new URL()` internally and we need to prevent it from treating
|
|
// them as separators
|
|
navigator.encodeLocation ? navigator.encodeLocation(
|
|
match.pathnameBase.replace(/\?/g, "%3F").replace(/#/g, "%23")
|
|
).pathname : match.pathnameBase
|
|
])
|
|
})
|
|
),
|
|
parentMatches,
|
|
dataRouterState,
|
|
unstable_onError,
|
|
future
|
|
);
|
|
if (locationArg && renderedMatches) {
|
|
return /* @__PURE__ */ React2.createElement(
|
|
LocationContext.Provider,
|
|
{
|
|
value: {
|
|
location: {
|
|
pathname: "/",
|
|
search: "",
|
|
hash: "",
|
|
state: null,
|
|
key: "default",
|
|
...location
|
|
},
|
|
navigationType: "POP" /* Pop */
|
|
}
|
|
},
|
|
renderedMatches
|
|
);
|
|
}
|
|
return renderedMatches;
|
|
}
|
|
function DefaultErrorComponent() {
|
|
let error = useRouteError();
|
|
let message = isRouteErrorResponse(error) ? `${error.status} ${error.statusText}` : error instanceof Error ? error.message : JSON.stringify(error);
|
|
let stack = error instanceof Error ? error.stack : null;
|
|
let lightgrey = "rgba(200,200,200, 0.5)";
|
|
let preStyles = { padding: "0.5rem", backgroundColor: lightgrey };
|
|
let codeStyles = { padding: "2px 4px", backgroundColor: lightgrey };
|
|
let devInfo = null;
|
|
if (ENABLE_DEV_WARNINGS) {
|
|
console.error(
|
|
"Error handled by React Router default ErrorBoundary:",
|
|
error
|
|
);
|
|
devInfo = /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("p", null, "\u{1F4BF} Hey developer \u{1F44B}"), /* @__PURE__ */ React2.createElement("p", null, "You can provide a way better UX than this when your app throws errors by providing your own ", /* @__PURE__ */ React2.createElement("code", { style: codeStyles }, "ErrorBoundary"), " or", " ", /* @__PURE__ */ React2.createElement("code", { style: codeStyles }, "errorElement"), " prop on your route."));
|
|
}
|
|
return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("h2", null, "Unexpected Application Error!"), /* @__PURE__ */ React2.createElement("h3", { style: { fontStyle: "italic" } }, message), stack ? /* @__PURE__ */ React2.createElement("pre", { style: preStyles }, stack) : null, devInfo);
|
|
}
|
|
var defaultErrorElement = /* @__PURE__ */ React2.createElement(DefaultErrorComponent, null);
|
|
var RenderErrorBoundary = class extends React2.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
location: props.location,
|
|
revalidation: props.revalidation,
|
|
error: props.error
|
|
};
|
|
}
|
|
static getDerivedStateFromError(error) {
|
|
return { error };
|
|
}
|
|
static getDerivedStateFromProps(props, state) {
|
|
if (state.location !== props.location || state.revalidation !== "idle" && props.revalidation === "idle") {
|
|
return {
|
|
error: props.error,
|
|
location: props.location,
|
|
revalidation: props.revalidation
|
|
};
|
|
}
|
|
return {
|
|
error: props.error !== void 0 ? props.error : state.error,
|
|
location: state.location,
|
|
revalidation: props.revalidation || state.revalidation
|
|
};
|
|
}
|
|
componentDidCatch(error, errorInfo) {
|
|
if (this.props.unstable_onError) {
|
|
this.props.unstable_onError(error, errorInfo);
|
|
} else {
|
|
console.error(
|
|
"React Router caught the following error during render",
|
|
error
|
|
);
|
|
}
|
|
}
|
|
render() {
|
|
return this.state.error !== void 0 ? /* @__PURE__ */ React2.createElement(RouteContext.Provider, { value: this.props.routeContext }, /* @__PURE__ */ React2.createElement(
|
|
RouteErrorContext.Provider,
|
|
{
|
|
value: this.state.error,
|
|
children: this.props.component
|
|
}
|
|
)) : this.props.children;
|
|
}
|
|
};
|
|
function RenderedRoute({ routeContext, match, children }) {
|
|
let dataRouterContext = React2.useContext(DataRouterContext);
|
|
if (dataRouterContext && dataRouterContext.static && dataRouterContext.staticContext && (match.route.errorElement || match.route.ErrorBoundary)) {
|
|
dataRouterContext.staticContext._deepestRenderedBoundaryId = match.route.id;
|
|
}
|
|
return /* @__PURE__ */ React2.createElement(RouteContext.Provider, { value: routeContext }, children);
|
|
}
|
|
function _renderMatches(matches, parentMatches = [], dataRouterState = null, unstable_onError = null, future = null) {
|
|
if (matches == null) {
|
|
if (!dataRouterState) {
|
|
return null;
|
|
}
|
|
if (dataRouterState.errors) {
|
|
matches = dataRouterState.matches;
|
|
} else if (parentMatches.length === 0 && !dataRouterState.initialized && dataRouterState.matches.length > 0) {
|
|
matches = dataRouterState.matches;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
let renderedMatches = matches;
|
|
let errors = dataRouterState?.errors;
|
|
if (errors != null) {
|
|
let errorIndex = renderedMatches.findIndex(
|
|
(m) => m.route.id && errors?.[m.route.id] !== void 0
|
|
);
|
|
invariant(
|
|
errorIndex >= 0,
|
|
`Could not find a matching route for errors on route IDs: ${Object.keys(
|
|
errors
|
|
).join(",")}`
|
|
);
|
|
renderedMatches = renderedMatches.slice(
|
|
0,
|
|
Math.min(renderedMatches.length, errorIndex + 1)
|
|
);
|
|
}
|
|
let renderFallback = false;
|
|
let fallbackIndex = -1;
|
|
if (dataRouterState) {
|
|
for (let i = 0; i < renderedMatches.length; i++) {
|
|
let match = renderedMatches[i];
|
|
if (match.route.HydrateFallback || match.route.hydrateFallbackElement) {
|
|
fallbackIndex = i;
|
|
}
|
|
if (match.route.id) {
|
|
let { loaderData, errors: errors2 } = dataRouterState;
|
|
let needsToRunLoader = match.route.loader && !loaderData.hasOwnProperty(match.route.id) && (!errors2 || errors2[match.route.id] === void 0);
|
|
if (match.route.lazy || needsToRunLoader) {
|
|
renderFallback = true;
|
|
if (fallbackIndex >= 0) {
|
|
renderedMatches = renderedMatches.slice(0, fallbackIndex + 1);
|
|
} else {
|
|
renderedMatches = [renderedMatches[0]];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return renderedMatches.reduceRight(
|
|
(outlet, match, index) => {
|
|
let error;
|
|
let shouldRenderHydrateFallback = false;
|
|
let errorElement = null;
|
|
let hydrateFallbackElement = null;
|
|
if (dataRouterState) {
|
|
error = errors && match.route.id ? errors[match.route.id] : void 0;
|
|
errorElement = match.route.errorElement || defaultErrorElement;
|
|
if (renderFallback) {
|
|
if (fallbackIndex < 0 && index === 0) {
|
|
warningOnce(
|
|
"route-fallback",
|
|
false,
|
|
"No `HydrateFallback` element provided to render during initial hydration"
|
|
);
|
|
shouldRenderHydrateFallback = true;
|
|
hydrateFallbackElement = null;
|
|
} else if (fallbackIndex === index) {
|
|
shouldRenderHydrateFallback = true;
|
|
hydrateFallbackElement = match.route.hydrateFallbackElement || null;
|
|
}
|
|
}
|
|
}
|
|
let matches2 = parentMatches.concat(renderedMatches.slice(0, index + 1));
|
|
let getChildren = () => {
|
|
let children;
|
|
if (error) {
|
|
children = errorElement;
|
|
} else if (shouldRenderHydrateFallback) {
|
|
children = hydrateFallbackElement;
|
|
} else if (match.route.Component) {
|
|
children = /* @__PURE__ */ React2.createElement(match.route.Component, null);
|
|
} else if (match.route.element) {
|
|
children = match.route.element;
|
|
} else {
|
|
children = outlet;
|
|
}
|
|
return /* @__PURE__ */ React2.createElement(
|
|
RenderedRoute,
|
|
{
|
|
match,
|
|
routeContext: {
|
|
outlet,
|
|
matches: matches2,
|
|
isDataRoute: dataRouterState != null
|
|
},
|
|
children
|
|
}
|
|
);
|
|
};
|
|
return dataRouterState && (match.route.ErrorBoundary || match.route.errorElement || index === 0) ? /* @__PURE__ */ React2.createElement(
|
|
RenderErrorBoundary,
|
|
{
|
|
location: dataRouterState.location,
|
|
revalidation: dataRouterState.revalidation,
|
|
component: errorElement,
|
|
error,
|
|
children: getChildren(),
|
|
routeContext: { outlet: null, matches: matches2, isDataRoute: true },
|
|
unstable_onError
|
|
}
|
|
) : getChildren();
|
|
},
|
|
null
|
|
);
|
|
}
|
|
function getDataRouterConsoleError(hookName) {
|
|
return `${hookName} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`;
|
|
}
|
|
function useDataRouterContext(hookName) {
|
|
let ctx = React2.useContext(DataRouterContext);
|
|
invariant(ctx, getDataRouterConsoleError(hookName));
|
|
return ctx;
|
|
}
|
|
function useDataRouterState(hookName) {
|
|
let state = React2.useContext(DataRouterStateContext);
|
|
invariant(state, getDataRouterConsoleError(hookName));
|
|
return state;
|
|
}
|
|
function useRouteContext(hookName) {
|
|
let route = React2.useContext(RouteContext);
|
|
invariant(route, getDataRouterConsoleError(hookName));
|
|
return route;
|
|
}
|
|
function useCurrentRouteId(hookName) {
|
|
let route = useRouteContext(hookName);
|
|
let thisRoute = route.matches[route.matches.length - 1];
|
|
invariant(
|
|
thisRoute.route.id,
|
|
`${hookName} can only be used on routes that contain a unique "id"`
|
|
);
|
|
return thisRoute.route.id;
|
|
}
|
|
function useRouteId() {
|
|
return useCurrentRouteId("useRouteId" /* UseRouteId */);
|
|
}
|
|
function useNavigation() {
|
|
let state = useDataRouterState("useNavigation" /* UseNavigation */);
|
|
return state.navigation;
|
|
}
|
|
function useRevalidator() {
|
|
let dataRouterContext = useDataRouterContext("useRevalidator" /* UseRevalidator */);
|
|
let state = useDataRouterState("useRevalidator" /* UseRevalidator */);
|
|
let revalidate = React2.useCallback(async () => {
|
|
await dataRouterContext.router.revalidate();
|
|
}, [dataRouterContext.router]);
|
|
return React2.useMemo(
|
|
() => ({ revalidate, state: state.revalidation }),
|
|
[revalidate, state.revalidation]
|
|
);
|
|
}
|
|
function useMatches() {
|
|
let { matches, loaderData } = useDataRouterState(
|
|
"useMatches" /* UseMatches */
|
|
);
|
|
return React2.useMemo(
|
|
() => matches.map((m) => convertRouteMatchToUiMatch(m, loaderData)),
|
|
[matches, loaderData]
|
|
);
|
|
}
|
|
function useLoaderData() {
|
|
let state = useDataRouterState("useLoaderData" /* UseLoaderData */);
|
|
let routeId = useCurrentRouteId("useLoaderData" /* UseLoaderData */);
|
|
return state.loaderData[routeId];
|
|
}
|
|
function useRouteLoaderData(routeId) {
|
|
let state = useDataRouterState("useRouteLoaderData" /* UseRouteLoaderData */);
|
|
return state.loaderData[routeId];
|
|
}
|
|
function useActionData() {
|
|
let state = useDataRouterState("useActionData" /* UseActionData */);
|
|
let routeId = useCurrentRouteId("useLoaderData" /* UseLoaderData */);
|
|
return state.actionData ? state.actionData[routeId] : void 0;
|
|
}
|
|
function useRouteError() {
|
|
let error = React2.useContext(RouteErrorContext);
|
|
let state = useDataRouterState("useRouteError" /* UseRouteError */);
|
|
let routeId = useCurrentRouteId("useRouteError" /* UseRouteError */);
|
|
if (error !== void 0) {
|
|
return error;
|
|
}
|
|
return state.errors?.[routeId];
|
|
}
|
|
function useAsyncValue() {
|
|
let value = React2.useContext(AwaitContext);
|
|
return value?._data;
|
|
}
|
|
function useAsyncError() {
|
|
let value = React2.useContext(AwaitContext);
|
|
return value?._error;
|
|
}
|
|
var blockerId = 0;
|
|
function useBlocker(shouldBlock) {
|
|
let { router, basename } = useDataRouterContext("useBlocker" /* UseBlocker */);
|
|
let state = useDataRouterState("useBlocker" /* UseBlocker */);
|
|
let [blockerKey, setBlockerKey] = React2.useState("");
|
|
let blockerFunction = React2.useCallback(
|
|
(arg) => {
|
|
if (typeof shouldBlock !== "function") {
|
|
return !!shouldBlock;
|
|
}
|
|
if (basename === "/") {
|
|
return shouldBlock(arg);
|
|
}
|
|
let { currentLocation, nextLocation, historyAction } = arg;
|
|
return shouldBlock({
|
|
currentLocation: {
|
|
...currentLocation,
|
|
pathname: stripBasename(currentLocation.pathname, basename) || currentLocation.pathname
|
|
},
|
|
nextLocation: {
|
|
...nextLocation,
|
|
pathname: stripBasename(nextLocation.pathname, basename) || nextLocation.pathname
|
|
},
|
|
historyAction
|
|
});
|
|
},
|
|
[basename, shouldBlock]
|
|
);
|
|
React2.useEffect(() => {
|
|
let key = String(++blockerId);
|
|
setBlockerKey(key);
|
|
return () => router.deleteBlocker(key);
|
|
}, [router]);
|
|
React2.useEffect(() => {
|
|
if (blockerKey !== "") {
|
|
router.getBlocker(blockerKey, blockerFunction);
|
|
}
|
|
}, [router, blockerKey, blockerFunction]);
|
|
return blockerKey && state.blockers.has(blockerKey) ? state.blockers.get(blockerKey) : IDLE_BLOCKER;
|
|
}
|
|
function useNavigateStable() {
|
|
let { router } = useDataRouterContext("useNavigate" /* UseNavigateStable */);
|
|
let id = useCurrentRouteId("useNavigate" /* UseNavigateStable */);
|
|
let activeRef = React2.useRef(false);
|
|
useIsomorphicLayoutEffect(() => {
|
|
activeRef.current = true;
|
|
});
|
|
let navigate = React2.useCallback(
|
|
async (to, options = {}) => {
|
|
warning(activeRef.current, navigateEffectWarning);
|
|
if (!activeRef.current) return;
|
|
if (typeof to === "number") {
|
|
router.navigate(to);
|
|
} else {
|
|
await router.navigate(to, { fromRouteId: id, ...options });
|
|
}
|
|
},
|
|
[router, id]
|
|
);
|
|
return navigate;
|
|
}
|
|
var alreadyWarned = {};
|
|
function warningOnce(key, cond, message) {
|
|
if (!cond && !alreadyWarned[key]) {
|
|
alreadyWarned[key] = true;
|
|
warning(false, message);
|
|
}
|
|
}
|
|
|
|
// lib/components.tsx
|
|
import * as React3 from "react";
|
|
|
|
// lib/server-runtime/warnings.ts
|
|
var alreadyWarned2 = {};
|
|
function warnOnce(condition, message) {
|
|
if (!condition && !alreadyWarned2[message]) {
|
|
alreadyWarned2[message] = true;
|
|
console.warn(message);
|
|
}
|
|
}
|
|
|
|
// lib/components.tsx
|
|
function mapRouteProperties(route) {
|
|
let updates = {
|
|
// Note: this check also occurs in createRoutesFromChildren so update
|
|
// there if you change this -- please and thank you!
|
|
hasErrorBoundary: route.hasErrorBoundary || route.ErrorBoundary != null || route.errorElement != null
|
|
};
|
|
if (route.Component) {
|
|
if (ENABLE_DEV_WARNINGS) {
|
|
if (route.element) {
|
|
warning(
|
|
false,
|
|
"You should not include both `Component` and `element` on your route - `Component` will be used."
|
|
);
|
|
}
|
|
}
|
|
Object.assign(updates, {
|
|
element: React3.createElement(route.Component),
|
|
Component: void 0
|
|
});
|
|
}
|
|
if (route.HydrateFallback) {
|
|
if (ENABLE_DEV_WARNINGS) {
|
|
if (route.hydrateFallbackElement) {
|
|
warning(
|
|
false,
|
|
"You should not include both `HydrateFallback` and `hydrateFallbackElement` on your route - `HydrateFallback` will be used."
|
|
);
|
|
}
|
|
}
|
|
Object.assign(updates, {
|
|
hydrateFallbackElement: React3.createElement(route.HydrateFallback),
|
|
HydrateFallback: void 0
|
|
});
|
|
}
|
|
if (route.ErrorBoundary) {
|
|
if (ENABLE_DEV_WARNINGS) {
|
|
if (route.errorElement) {
|
|
warning(
|
|
false,
|
|
"You should not include both `ErrorBoundary` and `errorElement` on your route - `ErrorBoundary` will be used."
|
|
);
|
|
}
|
|
}
|
|
Object.assign(updates, {
|
|
errorElement: React3.createElement(route.ErrorBoundary),
|
|
ErrorBoundary: void 0
|
|
});
|
|
}
|
|
return updates;
|
|
}
|
|
var hydrationRouteProperties = [
|
|
"HydrateFallback",
|
|
"hydrateFallbackElement"
|
|
];
|
|
function createMemoryRouter(routes, opts) {
|
|
return createRouter({
|
|
basename: opts?.basename,
|
|
getContext: opts?.getContext,
|
|
future: opts?.future,
|
|
history: createMemoryHistory({
|
|
initialEntries: opts?.initialEntries,
|
|
initialIndex: opts?.initialIndex
|
|
}),
|
|
hydrationData: opts?.hydrationData,
|
|
routes,
|
|
hydrationRouteProperties,
|
|
mapRouteProperties,
|
|
dataStrategy: opts?.dataStrategy,
|
|
patchRoutesOnNavigation: opts?.patchRoutesOnNavigation
|
|
}).initialize();
|
|
}
|
|
var Deferred = class {
|
|
constructor() {
|
|
this.status = "pending";
|
|
this.promise = new Promise((resolve, reject) => {
|
|
this.resolve = (value) => {
|
|
if (this.status === "pending") {
|
|
this.status = "resolved";
|
|
resolve(value);
|
|
}
|
|
};
|
|
this.reject = (reason) => {
|
|
if (this.status === "pending") {
|
|
this.status = "rejected";
|
|
reject(reason);
|
|
}
|
|
};
|
|
});
|
|
}
|
|
};
|
|
function shallowDiff(a, b) {
|
|
if (a === b) {
|
|
return false;
|
|
}
|
|
let aKeys = Object.keys(a);
|
|
let bKeys = Object.keys(b);
|
|
if (aKeys.length !== bKeys.length) {
|
|
return true;
|
|
}
|
|
for (let key of aKeys) {
|
|
if (a[key] !== b[key]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function UNSTABLE_TransitionEnabledRouterProvider({
|
|
router,
|
|
flushSync: reactDomFlushSyncImpl,
|
|
unstable_onError
|
|
}) {
|
|
let fetcherData = React3.useRef(/* @__PURE__ */ new Map());
|
|
let [revalidating, startRevalidation] = React3.useTransition();
|
|
let [state, setState] = React3.useState(router.state);
|
|
router.__setPendingRerender = (promise) => startRevalidation(
|
|
// @ts-expect-error - need react 19 types for this to be async
|
|
async () => {
|
|
const rerender = await promise;
|
|
startRevalidation(() => {
|
|
rerender();
|
|
});
|
|
}
|
|
);
|
|
let navigator = React3.useMemo(() => {
|
|
return {
|
|
createHref: router.createHref,
|
|
encodeLocation: router.encodeLocation,
|
|
go: (n) => router.navigate(n),
|
|
push: (to, state2, opts) => router.navigate(to, {
|
|
state: state2,
|
|
preventScrollReset: opts?.preventScrollReset
|
|
}),
|
|
replace: (to, state2, opts) => router.navigate(to, {
|
|
replace: true,
|
|
state: state2,
|
|
preventScrollReset: opts?.preventScrollReset
|
|
})
|
|
};
|
|
}, [router]);
|
|
let basename = router.basename || "/";
|
|
let dataRouterContext = React3.useMemo(
|
|
() => ({
|
|
router,
|
|
navigator,
|
|
static: false,
|
|
basename,
|
|
unstable_onError
|
|
}),
|
|
[router, navigator, basename, unstable_onError]
|
|
);
|
|
React3.useLayoutEffect(() => {
|
|
return router.subscribe(
|
|
(newState, { deletedFetchers, flushSync, viewTransitionOpts }) => {
|
|
newState.fetchers.forEach((fetcher, key) => {
|
|
if (fetcher.data !== void 0) {
|
|
fetcherData.current.set(key, fetcher.data);
|
|
}
|
|
});
|
|
deletedFetchers.forEach((key) => fetcherData.current.delete(key));
|
|
const diff = shallowDiff(state, newState);
|
|
if (!diff) return;
|
|
if (flushSync) {
|
|
if (reactDomFlushSyncImpl) {
|
|
reactDomFlushSyncImpl(() => setState(newState));
|
|
} else {
|
|
setState(newState);
|
|
}
|
|
} else {
|
|
React3.startTransition(() => {
|
|
setState(newState);
|
|
});
|
|
}
|
|
}
|
|
);
|
|
}, [router, reactDomFlushSyncImpl, state]);
|
|
return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(DataRouterContext.Provider, { value: dataRouterContext }, /* @__PURE__ */ React3.createElement(
|
|
DataRouterStateContext.Provider,
|
|
{
|
|
value: {
|
|
...state,
|
|
revalidation: revalidating ? "loading" : state.revalidation
|
|
}
|
|
},
|
|
/* @__PURE__ */ React3.createElement(FetchersContext.Provider, { value: fetcherData.current }, /* @__PURE__ */ React3.createElement(
|
|
Router,
|
|
{
|
|
basename,
|
|
location: state.location,
|
|
navigationType: state.historyAction,
|
|
navigator
|
|
},
|
|
/* @__PURE__ */ React3.createElement(
|
|
MemoizedDataRoutes,
|
|
{
|
|
routes: router.routes,
|
|
future: router.future,
|
|
state,
|
|
unstable_onError
|
|
}
|
|
)
|
|
))
|
|
)), null);
|
|
}
|
|
function RouterProvider({
|
|
router,
|
|
flushSync: reactDomFlushSyncImpl,
|
|
unstable_onError
|
|
}) {
|
|
let [state, setStateImpl] = React3.useState(router.state);
|
|
let [pendingState, setPendingState] = React3.useState();
|
|
let [vtContext, setVtContext] = React3.useState({
|
|
isTransitioning: false
|
|
});
|
|
let [renderDfd, setRenderDfd] = React3.useState();
|
|
let [transition, setTransition] = React3.useState();
|
|
let [interruption, setInterruption] = React3.useState();
|
|
let fetcherData = React3.useRef(/* @__PURE__ */ new Map());
|
|
let logErrorsAndSetState = React3.useCallback(
|
|
(newState) => {
|
|
setStateImpl((prevState) => {
|
|
if (newState.errors && unstable_onError) {
|
|
Object.entries(newState.errors).forEach(([routeId, error]) => {
|
|
if (prevState.errors?.[routeId] !== error) {
|
|
unstable_onError(error);
|
|
}
|
|
});
|
|
}
|
|
return newState;
|
|
});
|
|
},
|
|
[unstable_onError]
|
|
);
|
|
let setState = React3.useCallback(
|
|
(newState, { deletedFetchers, flushSync, viewTransitionOpts }) => {
|
|
newState.fetchers.forEach((fetcher, key) => {
|
|
if (fetcher.data !== void 0) {
|
|
fetcherData.current.set(key, fetcher.data);
|
|
}
|
|
});
|
|
deletedFetchers.forEach((key) => fetcherData.current.delete(key));
|
|
warnOnce(
|
|
flushSync === false || reactDomFlushSyncImpl != null,
|
|
'You provided the `flushSync` option to a router update, but you are not using the `<RouterProvider>` from `react-router/dom` so `ReactDOM.flushSync()` is unavailable. Please update your app to `import { RouterProvider } from "react-router/dom"` and ensure you have `react-dom` installed as a dependency to use the `flushSync` option.'
|
|
);
|
|
let isViewTransitionAvailable = router.window != null && router.window.document != null && typeof router.window.document.startViewTransition === "function";
|
|
warnOnce(
|
|
viewTransitionOpts == null || isViewTransitionAvailable,
|
|
"You provided the `viewTransition` option to a router update, but you do not appear to be running in a DOM environment as `window.startViewTransition` is not available."
|
|
);
|
|
if (!viewTransitionOpts || !isViewTransitionAvailable) {
|
|
if (reactDomFlushSyncImpl && flushSync) {
|
|
reactDomFlushSyncImpl(() => logErrorsAndSetState(newState));
|
|
} else {
|
|
React3.startTransition(() => logErrorsAndSetState(newState));
|
|
}
|
|
return;
|
|
}
|
|
if (reactDomFlushSyncImpl && flushSync) {
|
|
reactDomFlushSyncImpl(() => {
|
|
if (transition) {
|
|
renderDfd && renderDfd.resolve();
|
|
transition.skipTransition();
|
|
}
|
|
setVtContext({
|
|
isTransitioning: true,
|
|
flushSync: true,
|
|
currentLocation: viewTransitionOpts.currentLocation,
|
|
nextLocation: viewTransitionOpts.nextLocation
|
|
});
|
|
});
|
|
let t = router.window.document.startViewTransition(() => {
|
|
reactDomFlushSyncImpl(() => logErrorsAndSetState(newState));
|
|
});
|
|
t.finished.finally(() => {
|
|
reactDomFlushSyncImpl(() => {
|
|
setRenderDfd(void 0);
|
|
setTransition(void 0);
|
|
setPendingState(void 0);
|
|
setVtContext({ isTransitioning: false });
|
|
});
|
|
});
|
|
reactDomFlushSyncImpl(() => setTransition(t));
|
|
return;
|
|
}
|
|
if (transition) {
|
|
renderDfd && renderDfd.resolve();
|
|
transition.skipTransition();
|
|
setInterruption({
|
|
state: newState,
|
|
currentLocation: viewTransitionOpts.currentLocation,
|
|
nextLocation: viewTransitionOpts.nextLocation
|
|
});
|
|
} else {
|
|
setPendingState(newState);
|
|
setVtContext({
|
|
isTransitioning: true,
|
|
flushSync: false,
|
|
currentLocation: viewTransitionOpts.currentLocation,
|
|
nextLocation: viewTransitionOpts.nextLocation
|
|
});
|
|
}
|
|
},
|
|
[
|
|
router.window,
|
|
reactDomFlushSyncImpl,
|
|
transition,
|
|
renderDfd,
|
|
logErrorsAndSetState
|
|
]
|
|
);
|
|
React3.useLayoutEffect(() => router.subscribe(setState), [router, setState]);
|
|
React3.useEffect(() => {
|
|
if (vtContext.isTransitioning && !vtContext.flushSync) {
|
|
setRenderDfd(new Deferred());
|
|
}
|
|
}, [vtContext]);
|
|
React3.useEffect(() => {
|
|
if (renderDfd && pendingState && router.window) {
|
|
let newState = pendingState;
|
|
let renderPromise = renderDfd.promise;
|
|
let transition2 = router.window.document.startViewTransition(async () => {
|
|
React3.startTransition(() => logErrorsAndSetState(newState));
|
|
await renderPromise;
|
|
});
|
|
transition2.finished.finally(() => {
|
|
setRenderDfd(void 0);
|
|
setTransition(void 0);
|
|
setPendingState(void 0);
|
|
setVtContext({ isTransitioning: false });
|
|
});
|
|
setTransition(transition2);
|
|
}
|
|
}, [pendingState, renderDfd, router.window, logErrorsAndSetState]);
|
|
React3.useEffect(() => {
|
|
if (renderDfd && pendingState && state.location.key === pendingState.location.key) {
|
|
renderDfd.resolve();
|
|
}
|
|
}, [renderDfd, transition, state.location, pendingState]);
|
|
React3.useEffect(() => {
|
|
if (!vtContext.isTransitioning && interruption) {
|
|
setPendingState(interruption.state);
|
|
setVtContext({
|
|
isTransitioning: true,
|
|
flushSync: false,
|
|
currentLocation: interruption.currentLocation,
|
|
nextLocation: interruption.nextLocation
|
|
});
|
|
setInterruption(void 0);
|
|
}
|
|
}, [vtContext.isTransitioning, interruption]);
|
|
let navigator = React3.useMemo(() => {
|
|
return {
|
|
createHref: router.createHref,
|
|
encodeLocation: router.encodeLocation,
|
|
go: (n) => router.navigate(n),
|
|
push: (to, state2, opts) => router.navigate(to, {
|
|
state: state2,
|
|
preventScrollReset: opts?.preventScrollReset
|
|
}),
|
|
replace: (to, state2, opts) => router.navigate(to, {
|
|
replace: true,
|
|
state: state2,
|
|
preventScrollReset: opts?.preventScrollReset
|
|
})
|
|
};
|
|
}, [router]);
|
|
let basename = router.basename || "/";
|
|
let dataRouterContext = React3.useMemo(
|
|
() => ({
|
|
router,
|
|
navigator,
|
|
static: false,
|
|
basename,
|
|
unstable_onError
|
|
}),
|
|
[router, navigator, basename, unstable_onError]
|
|
);
|
|
return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(DataRouterContext.Provider, { value: dataRouterContext }, /* @__PURE__ */ React3.createElement(DataRouterStateContext.Provider, { value: state }, /* @__PURE__ */ React3.createElement(FetchersContext.Provider, { value: fetcherData.current }, /* @__PURE__ */ React3.createElement(ViewTransitionContext.Provider, { value: vtContext }, /* @__PURE__ */ React3.createElement(
|
|
Router,
|
|
{
|
|
basename,
|
|
location: state.location,
|
|
navigationType: state.historyAction,
|
|
navigator
|
|
},
|
|
/* @__PURE__ */ React3.createElement(
|
|
MemoizedDataRoutes,
|
|
{
|
|
routes: router.routes,
|
|
future: router.future,
|
|
state,
|
|
unstable_onError
|
|
}
|
|
)
|
|
))))), null);
|
|
}
|
|
var MemoizedDataRoutes = React3.memo(DataRoutes);
|
|
function DataRoutes({
|
|
routes,
|
|
future,
|
|
state,
|
|
unstable_onError
|
|
}) {
|
|
return useRoutesImpl(routes, void 0, state, unstable_onError, future);
|
|
}
|
|
function MemoryRouter({
|
|
basename,
|
|
children,
|
|
initialEntries,
|
|
initialIndex
|
|
}) {
|
|
let historyRef = React3.useRef();
|
|
if (historyRef.current == null) {
|
|
historyRef.current = createMemoryHistory({
|
|
initialEntries,
|
|
initialIndex,
|
|
v5Compat: true
|
|
});
|
|
}
|
|
let history = historyRef.current;
|
|
let [state, setStateImpl] = React3.useState({
|
|
action: history.action,
|
|
location: history.location
|
|
});
|
|
let setState = React3.useCallback(
|
|
(newState) => {
|
|
React3.startTransition(() => setStateImpl(newState));
|
|
},
|
|
[setStateImpl]
|
|
);
|
|
React3.useLayoutEffect(() => history.listen(setState), [history, setState]);
|
|
return /* @__PURE__ */ React3.createElement(
|
|
Router,
|
|
{
|
|
basename,
|
|
children,
|
|
location: state.location,
|
|
navigationType: state.action,
|
|
navigator: history
|
|
}
|
|
);
|
|
}
|
|
function Navigate({
|
|
to,
|
|
replace: replace2,
|
|
state,
|
|
relative
|
|
}) {
|
|
invariant(
|
|
useInRouterContext(),
|
|
// TODO: This error is probably because they somehow have 2 versions of
|
|
// the router loaded. We can help them understand how to avoid that.
|
|
`<Navigate> may be used only in the context of a <Router> component.`
|
|
);
|
|
let { static: isStatic } = React3.useContext(NavigationContext);
|
|
warning(
|
|
!isStatic,
|
|
`<Navigate> must not be used on the initial render in a <StaticRouter>. This is a no-op, but you should modify your code so the <Navigate> is only ever rendered in response to some user interaction or state change.`
|
|
);
|
|
let { matches } = React3.useContext(RouteContext);
|
|
let { pathname: locationPathname } = useLocation();
|
|
let navigate = useNavigate();
|
|
let path = resolveTo(
|
|
to,
|
|
getResolveToMatches(matches),
|
|
locationPathname,
|
|
relative === "path"
|
|
);
|
|
let jsonPath = JSON.stringify(path);
|
|
React3.useEffect(() => {
|
|
navigate(JSON.parse(jsonPath), { replace: replace2, state, relative });
|
|
}, [navigate, jsonPath, relative, replace2, state]);
|
|
return null;
|
|
}
|
|
function Outlet(props) {
|
|
return useOutlet(props.context);
|
|
}
|
|
function Route(props) {
|
|
invariant(
|
|
false,
|
|
`A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.`
|
|
);
|
|
}
|
|
function Router({
|
|
basename: basenameProp = "/",
|
|
children = null,
|
|
location: locationProp,
|
|
navigationType = "POP" /* Pop */,
|
|
navigator,
|
|
static: staticProp = false
|
|
}) {
|
|
invariant(
|
|
!useInRouterContext(),
|
|
`You cannot render a <Router> inside another <Router>. You should never have more than one in your app.`
|
|
);
|
|
let basename = basenameProp.replace(/^\/*/, "/");
|
|
let navigationContext = React3.useMemo(
|
|
() => ({
|
|
basename,
|
|
navigator,
|
|
static: staticProp,
|
|
future: {}
|
|
}),
|
|
[basename, navigator, staticProp]
|
|
);
|
|
if (typeof locationProp === "string") {
|
|
locationProp = parsePath(locationProp);
|
|
}
|
|
let {
|
|
pathname = "/",
|
|
search = "",
|
|
hash = "",
|
|
state = null,
|
|
key = "default"
|
|
} = locationProp;
|
|
let locationContext = React3.useMemo(() => {
|
|
let trailingPathname = stripBasename(pathname, basename);
|
|
if (trailingPathname == null) {
|
|
return null;
|
|
}
|
|
return {
|
|
location: {
|
|
pathname: trailingPathname,
|
|
search,
|
|
hash,
|
|
state,
|
|
key
|
|
},
|
|
navigationType
|
|
};
|
|
}, [basename, pathname, search, hash, state, key, navigationType]);
|
|
warning(
|
|
locationContext != null,
|
|
`<Router basename="${basename}"> is not able to match the URL "${pathname}${search}${hash}" because it does not start with the basename, so the <Router> won't render anything.`
|
|
);
|
|
if (locationContext == null) {
|
|
return null;
|
|
}
|
|
return /* @__PURE__ */ React3.createElement(NavigationContext.Provider, { value: navigationContext }, /* @__PURE__ */ React3.createElement(LocationContext.Provider, { children, value: locationContext }));
|
|
}
|
|
function Routes({
|
|
children,
|
|
location
|
|
}) {
|
|
return useRoutes(createRoutesFromChildren(children), location);
|
|
}
|
|
function Await({
|
|
children,
|
|
errorElement,
|
|
resolve
|
|
}) {
|
|
let dataRouterContext = React3.useContext(DataRouterContext);
|
|
return /* @__PURE__ */ React3.createElement(
|
|
AwaitErrorBoundary,
|
|
{
|
|
resolve,
|
|
errorElement,
|
|
unstable_onError: dataRouterContext?.unstable_onError
|
|
},
|
|
/* @__PURE__ */ React3.createElement(ResolveAwait, null, children)
|
|
);
|
|
}
|
|
var AwaitErrorBoundary = class extends React3.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = { error: null };
|
|
}
|
|
static getDerivedStateFromError(error) {
|
|
return { error };
|
|
}
|
|
componentDidCatch(error, errorInfo) {
|
|
if (this.props.unstable_onError) {
|
|
this.props.unstable_onError(error, errorInfo);
|
|
} else {
|
|
console.error(
|
|
"<Await> caught the following error during render",
|
|
error,
|
|
errorInfo
|
|
);
|
|
}
|
|
}
|
|
render() {
|
|
let { children, errorElement, resolve } = this.props;
|
|
let promise = null;
|
|
let status = 0 /* pending */;
|
|
if (!(resolve instanceof Promise)) {
|
|
status = 1 /* success */;
|
|
promise = Promise.resolve();
|
|
Object.defineProperty(promise, "_tracked", { get: () => true });
|
|
Object.defineProperty(promise, "_data", { get: () => resolve });
|
|
} else if (this.state.error) {
|
|
status = 2 /* error */;
|
|
let renderError = this.state.error;
|
|
promise = Promise.reject().catch(() => {
|
|
});
|
|
Object.defineProperty(promise, "_tracked", { get: () => true });
|
|
Object.defineProperty(promise, "_error", { get: () => renderError });
|
|
} else if (resolve._tracked) {
|
|
promise = resolve;
|
|
status = "_error" in promise ? 2 /* error */ : "_data" in promise ? 1 /* success */ : 0 /* pending */;
|
|
} else {
|
|
status = 0 /* pending */;
|
|
Object.defineProperty(resolve, "_tracked", { get: () => true });
|
|
promise = resolve.then(
|
|
(data2) => Object.defineProperty(resolve, "_data", { get: () => data2 }),
|
|
(error) => {
|
|
this.props.unstable_onError?.(error);
|
|
Object.defineProperty(resolve, "_error", { get: () => error });
|
|
}
|
|
);
|
|
}
|
|
if (status === 2 /* error */ && !errorElement) {
|
|
throw promise._error;
|
|
}
|
|
if (status === 2 /* error */) {
|
|
return /* @__PURE__ */ React3.createElement(AwaitContext.Provider, { value: promise, children: errorElement });
|
|
}
|
|
if (status === 1 /* success */) {
|
|
return /* @__PURE__ */ React3.createElement(AwaitContext.Provider, { value: promise, children });
|
|
}
|
|
throw promise;
|
|
}
|
|
};
|
|
function ResolveAwait({
|
|
children
|
|
}) {
|
|
let data2 = useAsyncValue();
|
|
let toRender = typeof children === "function" ? children(data2) : children;
|
|
return /* @__PURE__ */ React3.createElement(React3.Fragment, null, toRender);
|
|
}
|
|
function createRoutesFromChildren(children, parentPath = []) {
|
|
let routes = [];
|
|
React3.Children.forEach(children, (element, index) => {
|
|
if (!React3.isValidElement(element)) {
|
|
return;
|
|
}
|
|
let treePath = [...parentPath, index];
|
|
if (element.type === React3.Fragment) {
|
|
routes.push.apply(
|
|
routes,
|
|
createRoutesFromChildren(element.props.children, treePath)
|
|
);
|
|
return;
|
|
}
|
|
invariant(
|
|
element.type === Route,
|
|
`[${typeof element.type === "string" ? element.type : element.type.name}] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>`
|
|
);
|
|
invariant(
|
|
!element.props.index || !element.props.children,
|
|
"An index route cannot have child routes."
|
|
);
|
|
let route = {
|
|
id: element.props.id || treePath.join("-"),
|
|
caseSensitive: element.props.caseSensitive,
|
|
element: element.props.element,
|
|
Component: element.props.Component,
|
|
index: element.props.index,
|
|
path: element.props.path,
|
|
middleware: element.props.middleware,
|
|
loader: element.props.loader,
|
|
action: element.props.action,
|
|
hydrateFallbackElement: element.props.hydrateFallbackElement,
|
|
HydrateFallback: element.props.HydrateFallback,
|
|
errorElement: element.props.errorElement,
|
|
ErrorBoundary: element.props.ErrorBoundary,
|
|
hasErrorBoundary: element.props.hasErrorBoundary === true || element.props.ErrorBoundary != null || element.props.errorElement != null,
|
|
shouldRevalidate: element.props.shouldRevalidate,
|
|
handle: element.props.handle,
|
|
lazy: element.props.lazy
|
|
};
|
|
if (element.props.children) {
|
|
route.children = createRoutesFromChildren(
|
|
element.props.children,
|
|
treePath
|
|
);
|
|
}
|
|
routes.push(route);
|
|
});
|
|
return routes;
|
|
}
|
|
var createRoutesFromElements = createRoutesFromChildren;
|
|
function renderMatches(matches) {
|
|
return _renderMatches(matches);
|
|
}
|
|
function useRouteComponentProps() {
|
|
return {
|
|
params: useParams(),
|
|
loaderData: useLoaderData(),
|
|
actionData: useActionData(),
|
|
matches: useMatches()
|
|
};
|
|
}
|
|
function WithComponentProps({
|
|
children
|
|
}) {
|
|
const props = useRouteComponentProps();
|
|
return React3.cloneElement(children, props);
|
|
}
|
|
function withComponentProps(Component4) {
|
|
return function WithComponentProps2() {
|
|
const props = useRouteComponentProps();
|
|
return React3.createElement(Component4, props);
|
|
};
|
|
}
|
|
function useHydrateFallbackProps() {
|
|
return {
|
|
params: useParams(),
|
|
loaderData: useLoaderData(),
|
|
actionData: useActionData()
|
|
};
|
|
}
|
|
function WithHydrateFallbackProps({
|
|
children
|
|
}) {
|
|
const props = useHydrateFallbackProps();
|
|
return React3.cloneElement(children, props);
|
|
}
|
|
function withHydrateFallbackProps(HydrateFallback) {
|
|
return function WithHydrateFallbackProps2() {
|
|
const props = useHydrateFallbackProps();
|
|
return React3.createElement(HydrateFallback, props);
|
|
};
|
|
}
|
|
function useErrorBoundaryProps() {
|
|
return {
|
|
params: useParams(),
|
|
loaderData: useLoaderData(),
|
|
actionData: useActionData(),
|
|
error: useRouteError()
|
|
};
|
|
}
|
|
function WithErrorBoundaryProps({
|
|
children
|
|
}) {
|
|
const props = useErrorBoundaryProps();
|
|
return React3.cloneElement(children, props);
|
|
}
|
|
function withErrorBoundaryProps(ErrorBoundary) {
|
|
return function WithErrorBoundaryProps2() {
|
|
const props = useErrorBoundaryProps();
|
|
return React3.createElement(ErrorBoundary, props);
|
|
};
|
|
}
|
|
|
|
// lib/dom/dom.ts
|
|
var defaultMethod = "get";
|
|
var defaultEncType = "application/x-www-form-urlencoded";
|
|
function isHtmlElement(object) {
|
|
return object != null && typeof object.tagName === "string";
|
|
}
|
|
function isButtonElement(object) {
|
|
return isHtmlElement(object) && object.tagName.toLowerCase() === "button";
|
|
}
|
|
function isFormElement(object) {
|
|
return isHtmlElement(object) && object.tagName.toLowerCase() === "form";
|
|
}
|
|
function isInputElement(object) {
|
|
return isHtmlElement(object) && object.tagName.toLowerCase() === "input";
|
|
}
|
|
function isModifiedEvent(event) {
|
|
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
|
|
}
|
|
function shouldProcessLinkClick(event, target) {
|
|
return event.button === 0 && // Ignore everything but left clicks
|
|
(!target || target === "_self") && // Let browser handle "target=_blank" etc.
|
|
!isModifiedEvent(event);
|
|
}
|
|
function createSearchParams(init = "") {
|
|
return new URLSearchParams(
|
|
typeof init === "string" || Array.isArray(init) || init instanceof URLSearchParams ? init : Object.keys(init).reduce((memo2, key) => {
|
|
let value = init[key];
|
|
return memo2.concat(
|
|
Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]]
|
|
);
|
|
}, [])
|
|
);
|
|
}
|
|
function getSearchParamsForLocation(locationSearch, defaultSearchParams) {
|
|
let searchParams = createSearchParams(locationSearch);
|
|
if (defaultSearchParams) {
|
|
defaultSearchParams.forEach((_, key) => {
|
|
if (!searchParams.has(key)) {
|
|
defaultSearchParams.getAll(key).forEach((value) => {
|
|
searchParams.append(key, value);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
return searchParams;
|
|
}
|
|
var _formDataSupportsSubmitter = null;
|
|
function isFormDataSubmitterSupported() {
|
|
if (_formDataSupportsSubmitter === null) {
|
|
try {
|
|
new FormData(
|
|
document.createElement("form"),
|
|
// @ts-expect-error if FormData supports the submitter parameter, this will throw
|
|
0
|
|
);
|
|
_formDataSupportsSubmitter = false;
|
|
} catch (e) {
|
|
_formDataSupportsSubmitter = true;
|
|
}
|
|
}
|
|
return _formDataSupportsSubmitter;
|
|
}
|
|
var supportedFormEncTypes = /* @__PURE__ */ new Set([
|
|
"application/x-www-form-urlencoded",
|
|
"multipart/form-data",
|
|
"text/plain"
|
|
]);
|
|
function getFormEncType(encType) {
|
|
if (encType != null && !supportedFormEncTypes.has(encType)) {
|
|
warning(
|
|
false,
|
|
`"${encType}" is not a valid \`encType\` for \`<Form>\`/\`<fetcher.Form>\` and will default to "${defaultEncType}"`
|
|
);
|
|
return null;
|
|
}
|
|
return encType;
|
|
}
|
|
function getFormSubmissionInfo(target, basename) {
|
|
let method;
|
|
let action;
|
|
let encType;
|
|
let formData;
|
|
let body;
|
|
if (isFormElement(target)) {
|
|
let attr = target.getAttribute("action");
|
|
action = attr ? stripBasename(attr, basename) : null;
|
|
method = target.getAttribute("method") || defaultMethod;
|
|
encType = getFormEncType(target.getAttribute("enctype")) || defaultEncType;
|
|
formData = new FormData(target);
|
|
} else if (isButtonElement(target) || isInputElement(target) && (target.type === "submit" || target.type === "image")) {
|
|
let form = target.form;
|
|
if (form == null) {
|
|
throw new Error(
|
|
`Cannot submit a <button> or <input type="submit"> without a <form>`
|
|
);
|
|
}
|
|
let attr = target.getAttribute("formaction") || form.getAttribute("action");
|
|
action = attr ? stripBasename(attr, basename) : null;
|
|
method = target.getAttribute("formmethod") || form.getAttribute("method") || defaultMethod;
|
|
encType = getFormEncType(target.getAttribute("formenctype")) || getFormEncType(form.getAttribute("enctype")) || defaultEncType;
|
|
formData = new FormData(form, target);
|
|
if (!isFormDataSubmitterSupported()) {
|
|
let { name, type, value } = target;
|
|
if (type === "image") {
|
|
let prefix = name ? `${name}.` : "";
|
|
formData.append(`${prefix}x`, "0");
|
|
formData.append(`${prefix}y`, "0");
|
|
} else if (name) {
|
|
formData.append(name, value);
|
|
}
|
|
}
|
|
} else if (isHtmlElement(target)) {
|
|
throw new Error(
|
|
`Cannot submit element that is not <form>, <button>, or <input type="submit|image">`
|
|
);
|
|
} else {
|
|
method = defaultMethod;
|
|
action = null;
|
|
encType = defaultEncType;
|
|
body = target;
|
|
}
|
|
if (formData && encType === "text/plain") {
|
|
body = formData;
|
|
formData = void 0;
|
|
}
|
|
return { action, method: method.toLowerCase(), encType, formData, body };
|
|
}
|
|
|
|
// lib/dom/ssr/single-fetch.tsx
|
|
import * as React4 from "react";
|
|
|
|
// vendor/turbo-stream-v2/utils.ts
|
|
var HOLE = -1;
|
|
var NAN = -2;
|
|
var NEGATIVE_INFINITY = -3;
|
|
var NEGATIVE_ZERO = -4;
|
|
var NULL = -5;
|
|
var POSITIVE_INFINITY = -6;
|
|
var UNDEFINED = -7;
|
|
var TYPE_BIGINT = "B";
|
|
var TYPE_DATE = "D";
|
|
var TYPE_ERROR = "E";
|
|
var TYPE_MAP = "M";
|
|
var TYPE_NULL_OBJECT = "N";
|
|
var TYPE_PROMISE = "P";
|
|
var TYPE_REGEXP = "R";
|
|
var TYPE_SET = "S";
|
|
var TYPE_SYMBOL = "Y";
|
|
var TYPE_URL = "U";
|
|
var TYPE_PREVIOUS_RESOLVED = "Z";
|
|
var Deferred2 = class {
|
|
constructor() {
|
|
this.promise = new Promise((resolve, reject) => {
|
|
this.resolve = resolve;
|
|
this.reject = reject;
|
|
});
|
|
}
|
|
};
|
|
function createLineSplittingTransform() {
|
|
const decoder = new TextDecoder();
|
|
let leftover = "";
|
|
return new TransformStream({
|
|
transform(chunk, controller) {
|
|
const str = decoder.decode(chunk, { stream: true });
|
|
const parts = (leftover + str).split("\n");
|
|
leftover = parts.pop() || "";
|
|
for (const part of parts) {
|
|
controller.enqueue(part);
|
|
}
|
|
},
|
|
flush(controller) {
|
|
if (leftover) {
|
|
controller.enqueue(leftover);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// vendor/turbo-stream-v2/flatten.ts
|
|
function flatten(input) {
|
|
const { indices } = this;
|
|
const existing = indices.get(input);
|
|
if (existing) return [existing];
|
|
if (input === void 0) return UNDEFINED;
|
|
if (input === null) return NULL;
|
|
if (Number.isNaN(input)) return NAN;
|
|
if (input === Number.POSITIVE_INFINITY) return POSITIVE_INFINITY;
|
|
if (input === Number.NEGATIVE_INFINITY) return NEGATIVE_INFINITY;
|
|
if (input === 0 && 1 / input < 0) return NEGATIVE_ZERO;
|
|
const index = this.index++;
|
|
indices.set(input, index);
|
|
stringify.call(this, input, index);
|
|
return index;
|
|
}
|
|
function stringify(input, index) {
|
|
const { deferred, plugins, postPlugins } = this;
|
|
const str = this.stringified;
|
|
const stack = [[input, index]];
|
|
while (stack.length > 0) {
|
|
const [input2, index2] = stack.pop();
|
|
const partsForObj = (obj) => Object.keys(obj).map((k) => `"_${flatten.call(this, k)}":${flatten.call(this, obj[k])}`).join(",");
|
|
let error = null;
|
|
switch (typeof input2) {
|
|
case "boolean":
|
|
case "number":
|
|
case "string":
|
|
str[index2] = JSON.stringify(input2);
|
|
break;
|
|
case "bigint":
|
|
str[index2] = `["${TYPE_BIGINT}","${input2}"]`;
|
|
break;
|
|
case "symbol": {
|
|
const keyFor = Symbol.keyFor(input2);
|
|
if (!keyFor) {
|
|
error = new Error(
|
|
"Cannot encode symbol unless created with Symbol.for()"
|
|
);
|
|
} else {
|
|
str[index2] = `["${TYPE_SYMBOL}",${JSON.stringify(keyFor)}]`;
|
|
}
|
|
break;
|
|
}
|
|
case "object": {
|
|
if (!input2) {
|
|
str[index2] = `${NULL}`;
|
|
break;
|
|
}
|
|
const isArray = Array.isArray(input2);
|
|
let pluginHandled = false;
|
|
if (!isArray && plugins) {
|
|
for (const plugin of plugins) {
|
|
const pluginResult = plugin(input2);
|
|
if (Array.isArray(pluginResult)) {
|
|
pluginHandled = true;
|
|
const [pluginIdentifier, ...rest] = pluginResult;
|
|
str[index2] = `[${JSON.stringify(pluginIdentifier)}`;
|
|
if (rest.length > 0) {
|
|
str[index2] += `,${rest.map((v) => flatten.call(this, v)).join(",")}`;
|
|
}
|
|
str[index2] += "]";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!pluginHandled) {
|
|
let result = isArray ? "[" : "{";
|
|
if (isArray) {
|
|
for (let i = 0; i < input2.length; i++)
|
|
result += (i ? "," : "") + (i in input2 ? flatten.call(this, input2[i]) : HOLE);
|
|
str[index2] = `${result}]`;
|
|
} else if (input2 instanceof Date) {
|
|
const dateTime = input2.getTime();
|
|
str[index2] = `["${TYPE_DATE}",${Number.isNaN(dateTime) ? JSON.stringify("invalid") : dateTime}]`;
|
|
} else if (input2 instanceof URL) {
|
|
str[index2] = `["${TYPE_URL}",${JSON.stringify(input2.href)}]`;
|
|
} else if (input2 instanceof RegExp) {
|
|
str[index2] = `["${TYPE_REGEXP}",${JSON.stringify(
|
|
input2.source
|
|
)},${JSON.stringify(input2.flags)}]`;
|
|
} else if (input2 instanceof Set) {
|
|
if (input2.size > 0) {
|
|
str[index2] = `["${TYPE_SET}",${[...input2].map((val) => flatten.call(this, val)).join(",")}]`;
|
|
} else {
|
|
str[index2] = `["${TYPE_SET}"]`;
|
|
}
|
|
} else if (input2 instanceof Map) {
|
|
if (input2.size > 0) {
|
|
str[index2] = `["${TYPE_MAP}",${[...input2].flatMap(([k, v]) => [
|
|
flatten.call(this, k),
|
|
flatten.call(this, v)
|
|
]).join(",")}]`;
|
|
} else {
|
|
str[index2] = `["${TYPE_MAP}"]`;
|
|
}
|
|
} else if (input2 instanceof Promise) {
|
|
str[index2] = `["${TYPE_PROMISE}",${index2}]`;
|
|
deferred[index2] = input2;
|
|
} else if (input2 instanceof Error) {
|
|
str[index2] = `["${TYPE_ERROR}",${JSON.stringify(input2.message)}`;
|
|
if (input2.name !== "Error") {
|
|
str[index2] += `,${JSON.stringify(input2.name)}`;
|
|
}
|
|
str[index2] += "]";
|
|
} else if (Object.getPrototypeOf(input2) === null) {
|
|
str[index2] = `["${TYPE_NULL_OBJECT}",{${partsForObj(input2)}}]`;
|
|
} else if (isPlainObject(input2)) {
|
|
str[index2] = `{${partsForObj(input2)}}`;
|
|
} else {
|
|
error = new Error("Cannot encode object with prototype");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
const isArray = Array.isArray(input2);
|
|
let pluginHandled = false;
|
|
if (!isArray && plugins) {
|
|
for (const plugin of plugins) {
|
|
const pluginResult = plugin(input2);
|
|
if (Array.isArray(pluginResult)) {
|
|
pluginHandled = true;
|
|
const [pluginIdentifier, ...rest] = pluginResult;
|
|
str[index2] = `[${JSON.stringify(pluginIdentifier)}`;
|
|
if (rest.length > 0) {
|
|
str[index2] += `,${rest.map((v) => flatten.call(this, v)).join(",")}`;
|
|
}
|
|
str[index2] += "]";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!pluginHandled) {
|
|
error = new Error("Cannot encode function or unexpected type");
|
|
}
|
|
}
|
|
}
|
|
if (error) {
|
|
let pluginHandled = false;
|
|
if (postPlugins) {
|
|
for (const plugin of postPlugins) {
|
|
const pluginResult = plugin(input2);
|
|
if (Array.isArray(pluginResult)) {
|
|
pluginHandled = true;
|
|
const [pluginIdentifier, ...rest] = pluginResult;
|
|
str[index2] = `[${JSON.stringify(pluginIdentifier)}`;
|
|
if (rest.length > 0) {
|
|
str[index2] += `,${rest.map((v) => flatten.call(this, v)).join(",")}`;
|
|
}
|
|
str[index2] += "]";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!pluginHandled) {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var objectProtoNames = Object.getOwnPropertyNames(Object.prototype).sort().join("\0");
|
|
function isPlainObject(thing) {
|
|
const proto = Object.getPrototypeOf(thing);
|
|
return proto === Object.prototype || proto === null || Object.getOwnPropertyNames(proto).sort().join("\0") === objectProtoNames;
|
|
}
|
|
|
|
// vendor/turbo-stream-v2/unflatten.ts
|
|
var globalObj = typeof window !== "undefined" ? window : typeof globalThis !== "undefined" ? globalThis : void 0;
|
|
function unflatten(parsed) {
|
|
const { hydrated, values } = this;
|
|
if (typeof parsed === "number") return hydrate.call(this, parsed);
|
|
if (!Array.isArray(parsed) || !parsed.length) throw new SyntaxError();
|
|
const startIndex = values.length;
|
|
for (const value of parsed) {
|
|
values.push(value);
|
|
}
|
|
hydrated.length = values.length;
|
|
return hydrate.call(this, startIndex);
|
|
}
|
|
function hydrate(index) {
|
|
const { hydrated, values, deferred, plugins } = this;
|
|
let result;
|
|
const stack = [
|
|
[
|
|
index,
|
|
(v) => {
|
|
result = v;
|
|
}
|
|
]
|
|
];
|
|
let postRun = [];
|
|
while (stack.length > 0) {
|
|
const [index2, set] = stack.pop();
|
|
switch (index2) {
|
|
case UNDEFINED:
|
|
set(void 0);
|
|
continue;
|
|
case NULL:
|
|
set(null);
|
|
continue;
|
|
case NAN:
|
|
set(NaN);
|
|
continue;
|
|
case POSITIVE_INFINITY:
|
|
set(Infinity);
|
|
continue;
|
|
case NEGATIVE_INFINITY:
|
|
set(-Infinity);
|
|
continue;
|
|
case NEGATIVE_ZERO:
|
|
set(-0);
|
|
continue;
|
|
}
|
|
if (hydrated[index2]) {
|
|
set(hydrated[index2]);
|
|
continue;
|
|
}
|
|
const value = values[index2];
|
|
if (!value || typeof value !== "object") {
|
|
hydrated[index2] = value;
|
|
set(value);
|
|
continue;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
if (typeof value[0] === "string") {
|
|
const [type, b, c] = value;
|
|
switch (type) {
|
|
case TYPE_DATE:
|
|
set(hydrated[index2] = new Date(b));
|
|
continue;
|
|
case TYPE_URL:
|
|
set(hydrated[index2] = new URL(b));
|
|
continue;
|
|
case TYPE_BIGINT:
|
|
set(hydrated[index2] = BigInt(b));
|
|
continue;
|
|
case TYPE_REGEXP:
|
|
set(hydrated[index2] = new RegExp(b, c));
|
|
continue;
|
|
case TYPE_SYMBOL:
|
|
set(hydrated[index2] = Symbol.for(b));
|
|
continue;
|
|
case TYPE_SET:
|
|
const newSet = /* @__PURE__ */ new Set();
|
|
hydrated[index2] = newSet;
|
|
for (let i = value.length - 1; i > 0; i--)
|
|
stack.push([
|
|
value[i],
|
|
(v) => {
|
|
newSet.add(v);
|
|
}
|
|
]);
|
|
set(newSet);
|
|
continue;
|
|
case TYPE_MAP:
|
|
const map = /* @__PURE__ */ new Map();
|
|
hydrated[index2] = map;
|
|
for (let i = value.length - 2; i > 0; i -= 2) {
|
|
const r = [];
|
|
stack.push([
|
|
value[i + 1],
|
|
(v) => {
|
|
r[1] = v;
|
|
}
|
|
]);
|
|
stack.push([
|
|
value[i],
|
|
(k) => {
|
|
r[0] = k;
|
|
}
|
|
]);
|
|
postRun.push(() => {
|
|
map.set(r[0], r[1]);
|
|
});
|
|
}
|
|
set(map);
|
|
continue;
|
|
case TYPE_NULL_OBJECT:
|
|
const obj = /* @__PURE__ */ Object.create(null);
|
|
hydrated[index2] = obj;
|
|
for (const key of Object.keys(b).reverse()) {
|
|
const r = [];
|
|
stack.push([
|
|
b[key],
|
|
(v) => {
|
|
r[1] = v;
|
|
}
|
|
]);
|
|
stack.push([
|
|
Number(key.slice(1)),
|
|
(k) => {
|
|
r[0] = k;
|
|
}
|
|
]);
|
|
postRun.push(() => {
|
|
obj[r[0]] = r[1];
|
|
});
|
|
}
|
|
set(obj);
|
|
continue;
|
|
case TYPE_PROMISE:
|
|
if (hydrated[b]) {
|
|
set(hydrated[index2] = hydrated[b]);
|
|
} else {
|
|
const d = new Deferred2();
|
|
deferred[b] = d;
|
|
set(hydrated[index2] = d.promise);
|
|
}
|
|
continue;
|
|
case TYPE_ERROR:
|
|
const [, message, errorType] = value;
|
|
let error = errorType && globalObj && globalObj[errorType] ? new globalObj[errorType](message) : new Error(message);
|
|
hydrated[index2] = error;
|
|
set(error);
|
|
continue;
|
|
case TYPE_PREVIOUS_RESOLVED:
|
|
set(hydrated[index2] = hydrated[b]);
|
|
continue;
|
|
default:
|
|
if (Array.isArray(plugins)) {
|
|
const r = [];
|
|
const vals = value.slice(1);
|
|
for (let i = 0; i < vals.length; i++) {
|
|
const v = vals[i];
|
|
stack.push([
|
|
v,
|
|
(v2) => {
|
|
r[i] = v2;
|
|
}
|
|
]);
|
|
}
|
|
postRun.push(() => {
|
|
for (const plugin of plugins) {
|
|
const result2 = plugin(value[0], ...r);
|
|
if (result2) {
|
|
set(hydrated[index2] = result2.value);
|
|
return;
|
|
}
|
|
}
|
|
throw new SyntaxError();
|
|
});
|
|
continue;
|
|
}
|
|
throw new SyntaxError();
|
|
}
|
|
} else {
|
|
const array = [];
|
|
hydrated[index2] = array;
|
|
for (let i = 0; i < value.length; i++) {
|
|
const n = value[i];
|
|
if (n !== HOLE) {
|
|
stack.push([
|
|
n,
|
|
(v) => {
|
|
array[i] = v;
|
|
}
|
|
]);
|
|
}
|
|
}
|
|
set(array);
|
|
continue;
|
|
}
|
|
} else {
|
|
const object = {};
|
|
hydrated[index2] = object;
|
|
for (const key of Object.keys(value).reverse()) {
|
|
const r = [];
|
|
stack.push([
|
|
value[key],
|
|
(v) => {
|
|
r[1] = v;
|
|
}
|
|
]);
|
|
stack.push([
|
|
Number(key.slice(1)),
|
|
(k) => {
|
|
r[0] = k;
|
|
}
|
|
]);
|
|
postRun.push(() => {
|
|
object[r[0]] = r[1];
|
|
});
|
|
}
|
|
set(object);
|
|
continue;
|
|
}
|
|
}
|
|
while (postRun.length > 0) {
|
|
postRun.pop()();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// vendor/turbo-stream-v2/turbo-stream.ts
|
|
async function decode(readable, options) {
|
|
const { plugins } = options ?? {};
|
|
const done = new Deferred2();
|
|
const reader = readable.pipeThrough(createLineSplittingTransform()).getReader();
|
|
const decoder = {
|
|
values: [],
|
|
hydrated: [],
|
|
deferred: {},
|
|
plugins
|
|
};
|
|
const decoded = await decodeInitial.call(decoder, reader);
|
|
let donePromise = done.promise;
|
|
if (decoded.done) {
|
|
done.resolve();
|
|
} else {
|
|
donePromise = decodeDeferred.call(decoder, reader).then(done.resolve).catch((reason) => {
|
|
for (const deferred of Object.values(decoder.deferred)) {
|
|
deferred.reject(reason);
|
|
}
|
|
done.reject(reason);
|
|
});
|
|
}
|
|
return {
|
|
done: donePromise.then(() => reader.closed),
|
|
value: decoded.value
|
|
};
|
|
}
|
|
async function decodeInitial(reader) {
|
|
const read = await reader.read();
|
|
if (!read.value) {
|
|
throw new SyntaxError();
|
|
}
|
|
let line;
|
|
try {
|
|
line = JSON.parse(read.value);
|
|
} catch (reason) {
|
|
throw new SyntaxError();
|
|
}
|
|
return {
|
|
done: read.done,
|
|
value: unflatten.call(this, line)
|
|
};
|
|
}
|
|
async function decodeDeferred(reader) {
|
|
let read = await reader.read();
|
|
while (!read.done) {
|
|
if (!read.value) continue;
|
|
const line = read.value;
|
|
switch (line[0]) {
|
|
case TYPE_PROMISE: {
|
|
const colonIndex = line.indexOf(":");
|
|
const deferredId = Number(line.slice(1, colonIndex));
|
|
const deferred = this.deferred[deferredId];
|
|
if (!deferred) {
|
|
throw new Error(`Deferred ID ${deferredId} not found in stream`);
|
|
}
|
|
const lineData = line.slice(colonIndex + 1);
|
|
let jsonLine;
|
|
try {
|
|
jsonLine = JSON.parse(lineData);
|
|
} catch (reason) {
|
|
throw new SyntaxError();
|
|
}
|
|
const value = unflatten.call(this, jsonLine);
|
|
deferred.resolve(value);
|
|
break;
|
|
}
|
|
case TYPE_ERROR: {
|
|
const colonIndex = line.indexOf(":");
|
|
const deferredId = Number(line.slice(1, colonIndex));
|
|
const deferred = this.deferred[deferredId];
|
|
if (!deferred) {
|
|
throw new Error(`Deferred ID ${deferredId} not found in stream`);
|
|
}
|
|
const lineData = line.slice(colonIndex + 1);
|
|
let jsonLine;
|
|
try {
|
|
jsonLine = JSON.parse(lineData);
|
|
} catch (reason) {
|
|
throw new SyntaxError();
|
|
}
|
|
const value = unflatten.call(this, jsonLine);
|
|
deferred.reject(value);
|
|
break;
|
|
}
|
|
default:
|
|
throw new SyntaxError();
|
|
}
|
|
read = await reader.read();
|
|
}
|
|
}
|
|
function encode(input, options) {
|
|
const { plugins, postPlugins, signal } = options ?? {};
|
|
const encoder = {
|
|
deferred: {},
|
|
index: 0,
|
|
indices: /* @__PURE__ */ new Map(),
|
|
stringified: [],
|
|
plugins,
|
|
postPlugins,
|
|
signal
|
|
};
|
|
const textEncoder = new TextEncoder();
|
|
let lastSentIndex = 0;
|
|
const readable = new ReadableStream({
|
|
async start(controller) {
|
|
const id = flatten.call(encoder, input);
|
|
if (Array.isArray(id)) {
|
|
throw new Error("This should never happen");
|
|
}
|
|
if (id < 0) {
|
|
controller.enqueue(textEncoder.encode(`${id}
|
|
`));
|
|
} else {
|
|
controller.enqueue(
|
|
textEncoder.encode(`[${encoder.stringified.join(",")}]
|
|
`)
|
|
);
|
|
lastSentIndex = encoder.stringified.length - 1;
|
|
}
|
|
const seenPromises = /* @__PURE__ */ new WeakSet();
|
|
if (Object.keys(encoder.deferred).length) {
|
|
let raceDone;
|
|
const racePromise = new Promise((resolve, reject) => {
|
|
raceDone = resolve;
|
|
if (signal) {
|
|
const rejectPromise = () => reject(signal.reason || new Error("Signal was aborted."));
|
|
if (signal.aborted) {
|
|
rejectPromise();
|
|
} else {
|
|
signal.addEventListener("abort", (event) => {
|
|
rejectPromise();
|
|
});
|
|
}
|
|
}
|
|
});
|
|
while (Object.keys(encoder.deferred).length > 0) {
|
|
for (const [deferredId, deferred] of Object.entries(
|
|
encoder.deferred
|
|
)) {
|
|
if (seenPromises.has(deferred)) continue;
|
|
seenPromises.add(
|
|
// biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
|
|
encoder.deferred[Number(deferredId)] = Promise.race([
|
|
racePromise,
|
|
deferred
|
|
]).then(
|
|
(resolved) => {
|
|
const id2 = flatten.call(encoder, resolved);
|
|
if (Array.isArray(id2)) {
|
|
controller.enqueue(
|
|
textEncoder.encode(
|
|
`${TYPE_PROMISE}${deferredId}:[["${TYPE_PREVIOUS_RESOLVED}",${id2[0]}]]
|
|
`
|
|
)
|
|
);
|
|
encoder.index++;
|
|
lastSentIndex++;
|
|
} else if (id2 < 0) {
|
|
controller.enqueue(
|
|
textEncoder.encode(
|
|
`${TYPE_PROMISE}${deferredId}:${id2}
|
|
`
|
|
)
|
|
);
|
|
} else {
|
|
const values = encoder.stringified.slice(lastSentIndex + 1).join(",");
|
|
controller.enqueue(
|
|
textEncoder.encode(
|
|
`${TYPE_PROMISE}${deferredId}:[${values}]
|
|
`
|
|
)
|
|
);
|
|
lastSentIndex = encoder.stringified.length - 1;
|
|
}
|
|
},
|
|
(reason) => {
|
|
if (!reason || typeof reason !== "object" || !(reason instanceof Error)) {
|
|
reason = new Error("An unknown error occurred");
|
|
}
|
|
const id2 = flatten.call(encoder, reason);
|
|
if (Array.isArray(id2)) {
|
|
controller.enqueue(
|
|
textEncoder.encode(
|
|
`${TYPE_ERROR}${deferredId}:[["${TYPE_PREVIOUS_RESOLVED}",${id2[0]}]]
|
|
`
|
|
)
|
|
);
|
|
encoder.index++;
|
|
lastSentIndex++;
|
|
} else if (id2 < 0) {
|
|
controller.enqueue(
|
|
textEncoder.encode(
|
|
`${TYPE_ERROR}${deferredId}:${id2}
|
|
`
|
|
)
|
|
);
|
|
} else {
|
|
const values = encoder.stringified.slice(lastSentIndex + 1).join(",");
|
|
controller.enqueue(
|
|
textEncoder.encode(
|
|
`${TYPE_ERROR}${deferredId}:[${values}]
|
|
`
|
|
)
|
|
);
|
|
lastSentIndex = encoder.stringified.length - 1;
|
|
}
|
|
}
|
|
).finally(() => {
|
|
delete encoder.deferred[Number(deferredId)];
|
|
})
|
|
);
|
|
}
|
|
await Promise.race(Object.values(encoder.deferred));
|
|
}
|
|
raceDone();
|
|
}
|
|
await Promise.all(Object.values(encoder.deferred));
|
|
controller.close();
|
|
}
|
|
});
|
|
return readable;
|
|
}
|
|
|
|
// lib/dom/ssr/data.ts
|
|
async function createRequestInit(request) {
|
|
let init = { signal: request.signal };
|
|
if (request.method !== "GET") {
|
|
init.method = request.method;
|
|
let contentType = request.headers.get("Content-Type");
|
|
if (contentType && /\bapplication\/json\b/.test(contentType)) {
|
|
init.headers = { "Content-Type": contentType };
|
|
init.body = JSON.stringify(await request.json());
|
|
} else if (contentType && /\btext\/plain\b/.test(contentType)) {
|
|
init.headers = { "Content-Type": contentType };
|
|
init.body = await request.text();
|
|
} else if (contentType && /\bapplication\/x-www-form-urlencoded\b/.test(contentType)) {
|
|
init.body = new URLSearchParams(await request.text());
|
|
} else {
|
|
init.body = await request.formData();
|
|
}
|
|
}
|
|
return init;
|
|
}
|
|
|
|
// lib/dom/ssr/markup.ts
|
|
var ESCAPE_LOOKUP = {
|
|
"&": "\\u0026",
|
|
">": "\\u003e",
|
|
"<": "\\u003c",
|
|
"\u2028": "\\u2028",
|
|
"\u2029": "\\u2029"
|
|
};
|
|
var ESCAPE_REGEX = /[&><\u2028\u2029]/g;
|
|
function escapeHtml(html) {
|
|
return html.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
|
|
}
|
|
|
|
// lib/dom/ssr/invariant.ts
|
|
function invariant2(value, message) {
|
|
if (value === false || value === null || typeof value === "undefined") {
|
|
throw new Error(message);
|
|
}
|
|
}
|
|
|
|
// lib/dom/ssr/single-fetch.tsx
|
|
var SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect");
|
|
var SingleFetchNoResultError = class extends Error {
|
|
};
|
|
var SINGLE_FETCH_REDIRECT_STATUS = 202;
|
|
var NO_BODY_STATUS_CODES = /* @__PURE__ */ new Set([100, 101, 204, 205]);
|
|
function StreamTransfer({
|
|
context,
|
|
identifier,
|
|
reader,
|
|
textDecoder,
|
|
nonce
|
|
}) {
|
|
if (!context.renderMeta || !context.renderMeta.didRenderScripts) {
|
|
return null;
|
|
}
|
|
if (!context.renderMeta.streamCache) {
|
|
context.renderMeta.streamCache = {};
|
|
}
|
|
let { streamCache } = context.renderMeta;
|
|
let promise = streamCache[identifier];
|
|
if (!promise) {
|
|
promise = streamCache[identifier] = reader.read().then((result) => {
|
|
streamCache[identifier].result = {
|
|
done: result.done,
|
|
value: textDecoder.decode(result.value, { stream: true })
|
|
};
|
|
}).catch((e) => {
|
|
streamCache[identifier].error = e;
|
|
});
|
|
}
|
|
if (promise.error) {
|
|
throw promise.error;
|
|
}
|
|
if (promise.result === void 0) {
|
|
throw promise;
|
|
}
|
|
let { done, value } = promise.result;
|
|
let scriptTag = value ? /* @__PURE__ */ React4.createElement(
|
|
"script",
|
|
{
|
|
nonce,
|
|
dangerouslySetInnerHTML: {
|
|
__html: `window.__reactRouterContext.streamController.enqueue(${escapeHtml(
|
|
JSON.stringify(value)
|
|
)});`
|
|
}
|
|
}
|
|
) : null;
|
|
if (done) {
|
|
return /* @__PURE__ */ React4.createElement(React4.Fragment, null, scriptTag, /* @__PURE__ */ React4.createElement(
|
|
"script",
|
|
{
|
|
nonce,
|
|
dangerouslySetInnerHTML: {
|
|
__html: `window.__reactRouterContext.streamController.close();`
|
|
}
|
|
}
|
|
));
|
|
} else {
|
|
return /* @__PURE__ */ React4.createElement(React4.Fragment, null, scriptTag, /* @__PURE__ */ React4.createElement(React4.Suspense, null, /* @__PURE__ */ React4.createElement(
|
|
StreamTransfer,
|
|
{
|
|
context,
|
|
identifier: identifier + 1,
|
|
reader,
|
|
textDecoder,
|
|
nonce
|
|
}
|
|
)));
|
|
}
|
|
}
|
|
function getTurboStreamSingleFetchDataStrategy(getRouter, manifest, routeModules, ssr, basename) {
|
|
let dataStrategy = getSingleFetchDataStrategyImpl(
|
|
getRouter,
|
|
(match) => {
|
|
let manifestRoute = manifest.routes[match.route.id];
|
|
invariant2(manifestRoute, "Route not found in manifest");
|
|
let routeModule = routeModules[match.route.id];
|
|
return {
|
|
hasLoader: manifestRoute.hasLoader,
|
|
hasClientLoader: manifestRoute.hasClientLoader,
|
|
hasShouldRevalidate: Boolean(routeModule?.shouldRevalidate)
|
|
};
|
|
},
|
|
fetchAndDecodeViaTurboStream,
|
|
ssr,
|
|
basename
|
|
);
|
|
return async (args) => args.runClientMiddleware(dataStrategy);
|
|
}
|
|
function getSingleFetchDataStrategyImpl(getRouter, getRouteInfo, fetchAndDecode, ssr, basename, shouldAllowOptOut = () => true) {
|
|
return async (args) => {
|
|
let { request, matches, fetcherKey } = args;
|
|
let router = getRouter();
|
|
if (request.method !== "GET") {
|
|
return singleFetchActionStrategy(args, fetchAndDecode, basename);
|
|
}
|
|
let foundRevalidatingServerLoader = matches.some((m) => {
|
|
let { hasLoader, hasClientLoader } = getRouteInfo(m);
|
|
return m.unstable_shouldCallHandler() && hasLoader && !hasClientLoader;
|
|
});
|
|
if (!ssr && !foundRevalidatingServerLoader) {
|
|
return nonSsrStrategy(args, getRouteInfo, fetchAndDecode, basename);
|
|
}
|
|
if (fetcherKey) {
|
|
return singleFetchLoaderFetcherStrategy(args, fetchAndDecode, basename);
|
|
}
|
|
return singleFetchLoaderNavigationStrategy(
|
|
args,
|
|
router,
|
|
getRouteInfo,
|
|
fetchAndDecode,
|
|
ssr,
|
|
basename,
|
|
shouldAllowOptOut
|
|
);
|
|
};
|
|
}
|
|
async function singleFetchActionStrategy(args, fetchAndDecode, basename) {
|
|
let actionMatch = args.matches.find((m) => m.unstable_shouldCallHandler());
|
|
invariant2(actionMatch, "No action match found");
|
|
let actionStatus = void 0;
|
|
let result = await actionMatch.resolve(async (handler) => {
|
|
let result2 = await handler(async () => {
|
|
let { data: data2, status } = await fetchAndDecode(args, basename, [
|
|
actionMatch.route.id
|
|
]);
|
|
actionStatus = status;
|
|
return unwrapSingleFetchResult(data2, actionMatch.route.id);
|
|
});
|
|
return result2;
|
|
});
|
|
if (isResponse(result.result) || isRouteErrorResponse(result.result) || isDataWithResponseInit(result.result)) {
|
|
return { [actionMatch.route.id]: result };
|
|
}
|
|
return {
|
|
[actionMatch.route.id]: {
|
|
type: result.type,
|
|
result: data(result.result, actionStatus)
|
|
}
|
|
};
|
|
}
|
|
async function nonSsrStrategy(args, getRouteInfo, fetchAndDecode, basename) {
|
|
let matchesToLoad = args.matches.filter(
|
|
(m) => m.unstable_shouldCallHandler()
|
|
);
|
|
let results = {};
|
|
await Promise.all(
|
|
matchesToLoad.map(
|
|
(m) => m.resolve(async (handler) => {
|
|
try {
|
|
let { hasClientLoader } = getRouteInfo(m);
|
|
let routeId = m.route.id;
|
|
let result = hasClientLoader ? await handler(async () => {
|
|
let { data: data2 } = await fetchAndDecode(args, basename, [routeId]);
|
|
return unwrapSingleFetchResult(data2, routeId);
|
|
}) : await handler();
|
|
results[m.route.id] = { type: "data", result };
|
|
} catch (e) {
|
|
results[m.route.id] = { type: "error", result: e };
|
|
}
|
|
})
|
|
)
|
|
);
|
|
return results;
|
|
}
|
|
async function singleFetchLoaderNavigationStrategy(args, router, getRouteInfo, fetchAndDecode, ssr, basename, shouldAllowOptOut = () => true) {
|
|
let routesParams = /* @__PURE__ */ new Set();
|
|
let foundOptOutRoute = false;
|
|
let routeDfds = args.matches.map(() => createDeferred2());
|
|
let singleFetchDfd = createDeferred2();
|
|
let results = {};
|
|
let resolvePromise = Promise.all(
|
|
args.matches.map(
|
|
async (m, i) => m.resolve(async (handler) => {
|
|
routeDfds[i].resolve();
|
|
let routeId = m.route.id;
|
|
let { hasLoader, hasClientLoader, hasShouldRevalidate } = getRouteInfo(m);
|
|
let defaultShouldRevalidate = !m.unstable_shouldRevalidateArgs || m.unstable_shouldRevalidateArgs.actionStatus == null || m.unstable_shouldRevalidateArgs.actionStatus < 400;
|
|
let shouldCall = m.unstable_shouldCallHandler(defaultShouldRevalidate);
|
|
if (!shouldCall) {
|
|
foundOptOutRoute || (foundOptOutRoute = m.unstable_shouldRevalidateArgs != null && // This is a revalidation,
|
|
hasLoader && // for a route with a server loader,
|
|
hasShouldRevalidate === true);
|
|
return;
|
|
}
|
|
if (shouldAllowOptOut(m) && hasClientLoader) {
|
|
if (hasLoader) {
|
|
foundOptOutRoute = true;
|
|
}
|
|
try {
|
|
let result = await handler(async () => {
|
|
let { data: data2 } = await fetchAndDecode(args, basename, [routeId]);
|
|
return unwrapSingleFetchResult(data2, routeId);
|
|
});
|
|
results[routeId] = { type: "data", result };
|
|
} catch (e) {
|
|
results[routeId] = { type: "error", result: e };
|
|
}
|
|
return;
|
|
}
|
|
if (hasLoader) {
|
|
routesParams.add(routeId);
|
|
}
|
|
try {
|
|
let result = await handler(async () => {
|
|
let data2 = await singleFetchDfd.promise;
|
|
return unwrapSingleFetchResult(data2, routeId);
|
|
});
|
|
results[routeId] = { type: "data", result };
|
|
} catch (e) {
|
|
results[routeId] = { type: "error", result: e };
|
|
}
|
|
})
|
|
)
|
|
);
|
|
await Promise.all(routeDfds.map((d) => d.promise));
|
|
let isInitialLoad = !router.state.initialized && router.state.navigation.state === "idle";
|
|
if ((isInitialLoad || routesParams.size === 0) && !window.__reactRouterHdrActive) {
|
|
singleFetchDfd.resolve({ routes: {} });
|
|
} else {
|
|
let targetRoutes = ssr && foundOptOutRoute && routesParams.size > 0 ? [...routesParams.keys()] : void 0;
|
|
try {
|
|
let data2 = await fetchAndDecode(args, basename, targetRoutes);
|
|
singleFetchDfd.resolve(data2.data);
|
|
} catch (e) {
|
|
singleFetchDfd.reject(e);
|
|
}
|
|
}
|
|
await resolvePromise;
|
|
await bubbleMiddlewareErrors(
|
|
singleFetchDfd.promise,
|
|
args.matches,
|
|
routesParams,
|
|
results
|
|
);
|
|
return results;
|
|
}
|
|
async function bubbleMiddlewareErrors(singleFetchPromise, matches, routesParams, results) {
|
|
try {
|
|
let middlewareError;
|
|
let fetchedData = await singleFetchPromise;
|
|
if ("routes" in fetchedData) {
|
|
for (let match of matches) {
|
|
if (match.route.id in fetchedData.routes) {
|
|
let routeResult = fetchedData.routes[match.route.id];
|
|
if ("error" in routeResult) {
|
|
middlewareError = routeResult.error;
|
|
if (results[match.route.id]?.result == null) {
|
|
results[match.route.id] = {
|
|
type: "error",
|
|
result: middlewareError
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (middlewareError !== void 0) {
|
|
Array.from(routesParams.values()).forEach((routeId) => {
|
|
if (results[routeId].result instanceof SingleFetchNoResultError) {
|
|
results[routeId].result = middlewareError;
|
|
}
|
|
});
|
|
}
|
|
} catch (e) {
|
|
}
|
|
}
|
|
async function singleFetchLoaderFetcherStrategy(args, fetchAndDecode, basename) {
|
|
let fetcherMatch = args.matches.find((m) => m.unstable_shouldCallHandler());
|
|
invariant2(fetcherMatch, "No fetcher match found");
|
|
let routeId = fetcherMatch.route.id;
|
|
let result = await fetcherMatch.resolve(
|
|
async (handler) => handler(async () => {
|
|
let { data: data2 } = await fetchAndDecode(args, basename, [routeId]);
|
|
return unwrapSingleFetchResult(data2, routeId);
|
|
})
|
|
);
|
|
return { [fetcherMatch.route.id]: result };
|
|
}
|
|
function stripIndexParam(url) {
|
|
let indexValues = url.searchParams.getAll("index");
|
|
url.searchParams.delete("index");
|
|
let indexValuesToKeep = [];
|
|
for (let indexValue of indexValues) {
|
|
if (indexValue) {
|
|
indexValuesToKeep.push(indexValue);
|
|
}
|
|
}
|
|
for (let toKeep of indexValuesToKeep) {
|
|
url.searchParams.append("index", toKeep);
|
|
}
|
|
return url;
|
|
}
|
|
function singleFetchUrl(reqUrl, basename, extension) {
|
|
let url = typeof reqUrl === "string" ? new URL(
|
|
reqUrl,
|
|
// This can be called during the SSR flow via PrefetchPageLinksImpl so
|
|
// don't assume window is available
|
|
typeof window === "undefined" ? "server://singlefetch/" : window.location.origin
|
|
) : reqUrl;
|
|
if (url.pathname === "/") {
|
|
url.pathname = `_root.${extension}`;
|
|
} else if (basename && stripBasename(url.pathname, basename) === "/") {
|
|
url.pathname = `${basename.replace(/\/$/, "")}/_root.${extension}`;
|
|
} else {
|
|
url.pathname = `${url.pathname.replace(/\/$/, "")}.${extension}`;
|
|
}
|
|
return url;
|
|
}
|
|
async function fetchAndDecodeViaTurboStream(args, basename, targetRoutes) {
|
|
let { request } = args;
|
|
let url = singleFetchUrl(request.url, basename, "data");
|
|
if (request.method === "GET") {
|
|
url = stripIndexParam(url);
|
|
if (targetRoutes) {
|
|
url.searchParams.set("_routes", targetRoutes.join(","));
|
|
}
|
|
}
|
|
let res = await fetch(url, await createRequestInit(request));
|
|
if (res.status >= 400 && !res.headers.has("X-Remix-Response")) {
|
|
throw new ErrorResponseImpl(res.status, res.statusText, await res.text());
|
|
}
|
|
if (res.status === 204 && res.headers.has("X-Remix-Redirect")) {
|
|
return {
|
|
status: SINGLE_FETCH_REDIRECT_STATUS,
|
|
data: {
|
|
redirect: {
|
|
redirect: res.headers.get("X-Remix-Redirect"),
|
|
status: Number(res.headers.get("X-Remix-Status") || "302"),
|
|
revalidate: res.headers.get("X-Remix-Revalidate") === "true",
|
|
reload: res.headers.get("X-Remix-Reload-Document") === "true",
|
|
replace: res.headers.get("X-Remix-Replace") === "true"
|
|
}
|
|
}
|
|
};
|
|
}
|
|
if (NO_BODY_STATUS_CODES.has(res.status)) {
|
|
let routes = {};
|
|
if (targetRoutes && request.method !== "GET") {
|
|
routes[targetRoutes[0]] = { data: void 0 };
|
|
}
|
|
return {
|
|
status: res.status,
|
|
data: { routes }
|
|
};
|
|
}
|
|
invariant2(res.body, "No response body to decode");
|
|
try {
|
|
let decoded = await decodeViaTurboStream(res.body, window);
|
|
let data2;
|
|
if (request.method === "GET") {
|
|
let typed = decoded.value;
|
|
if (SingleFetchRedirectSymbol in typed) {
|
|
data2 = { redirect: typed[SingleFetchRedirectSymbol] };
|
|
} else {
|
|
data2 = { routes: typed };
|
|
}
|
|
} else {
|
|
let typed = decoded.value;
|
|
let routeId = targetRoutes?.[0];
|
|
invariant2(routeId, "No routeId found for single fetch call decoding");
|
|
if ("redirect" in typed) {
|
|
data2 = { redirect: typed };
|
|
} else {
|
|
data2 = { routes: { [routeId]: typed } };
|
|
}
|
|
}
|
|
return { status: res.status, data: data2 };
|
|
} catch (e) {
|
|
throw new Error("Unable to decode turbo-stream response");
|
|
}
|
|
}
|
|
function decodeViaTurboStream(body, global) {
|
|
return decode(body, {
|
|
plugins: [
|
|
(type, ...rest) => {
|
|
if (type === "SanitizedError") {
|
|
let [name, message, stack] = rest;
|
|
let Constructor = Error;
|
|
if (name && name in global && typeof global[name] === "function") {
|
|
Constructor = global[name];
|
|
}
|
|
let error = new Constructor(message);
|
|
error.stack = stack;
|
|
return { value: error };
|
|
}
|
|
if (type === "ErrorResponse") {
|
|
let [data2, status, statusText] = rest;
|
|
return {
|
|
value: new ErrorResponseImpl(status, statusText, data2)
|
|
};
|
|
}
|
|
if (type === "SingleFetchRedirect") {
|
|
return { value: { [SingleFetchRedirectSymbol]: rest[0] } };
|
|
}
|
|
if (type === "SingleFetchClassInstance") {
|
|
return { value: rest[0] };
|
|
}
|
|
if (type === "SingleFetchFallback") {
|
|
return { value: void 0 };
|
|
}
|
|
}
|
|
]
|
|
});
|
|
}
|
|
function unwrapSingleFetchResult(result, routeId) {
|
|
if ("redirect" in result) {
|
|
let {
|
|
redirect: location,
|
|
revalidate,
|
|
reload,
|
|
replace: replace2,
|
|
status
|
|
} = result.redirect;
|
|
throw redirect(location, {
|
|
status,
|
|
headers: {
|
|
// Three R's of redirecting (lol Veep)
|
|
...revalidate ? { "X-Remix-Revalidate": "yes" } : null,
|
|
...reload ? { "X-Remix-Reload-Document": "yes" } : null,
|
|
...replace2 ? { "X-Remix-Replace": "yes" } : null
|
|
}
|
|
});
|
|
}
|
|
let routeResult = result.routes[routeId];
|
|
if (routeResult == null) {
|
|
throw new SingleFetchNoResultError(
|
|
`No result found for routeId "${routeId}"`
|
|
);
|
|
} else if ("error" in routeResult) {
|
|
throw routeResult.error;
|
|
} else if ("data" in routeResult) {
|
|
return routeResult.data;
|
|
} else {
|
|
throw new Error(`Invalid response found for routeId "${routeId}"`);
|
|
}
|
|
}
|
|
function createDeferred2() {
|
|
let resolve;
|
|
let reject;
|
|
let promise = new Promise((res, rej) => {
|
|
resolve = async (val) => {
|
|
res(val);
|
|
try {
|
|
await promise;
|
|
} catch (e) {
|
|
}
|
|
};
|
|
reject = async (error) => {
|
|
rej(error);
|
|
try {
|
|
await promise;
|
|
} catch (e) {
|
|
}
|
|
};
|
|
});
|
|
return {
|
|
promise,
|
|
//@ts-ignore
|
|
resolve,
|
|
//@ts-ignore
|
|
reject
|
|
};
|
|
}
|
|
|
|
// lib/dom/ssr/errorBoundaries.tsx
|
|
import * as React9 from "react";
|
|
|
|
// lib/dom/ssr/components.tsx
|
|
import * as React8 from "react";
|
|
|
|
// lib/dom/ssr/routeModules.ts
|
|
async function loadRouteModule(route, routeModulesCache) {
|
|
if (route.id in routeModulesCache) {
|
|
return routeModulesCache[route.id];
|
|
}
|
|
try {
|
|
let routeModule = await import(
|
|
/* @vite-ignore */
|
|
/* webpackIgnore: true */
|
|
route.module
|
|
);
|
|
routeModulesCache[route.id] = routeModule;
|
|
return routeModule;
|
|
} catch (error) {
|
|
console.error(
|
|
`Error loading route module \`${route.module}\`, reloading page...`
|
|
);
|
|
console.error(error);
|
|
if (window.__reactRouterContext && window.__reactRouterContext.isSpaMode && // @ts-expect-error
|
|
import.meta.hot) {
|
|
throw error;
|
|
}
|
|
window.location.reload();
|
|
return new Promise(() => {
|
|
});
|
|
}
|
|
}
|
|
|
|
// lib/dom/ssr/links.ts
|
|
function getKeyedLinksForMatches(matches, routeModules, manifest) {
|
|
let descriptors = matches.map((match) => {
|
|
let module = routeModules[match.route.id];
|
|
let route = manifest.routes[match.route.id];
|
|
return [
|
|
route && route.css ? route.css.map((href) => ({ rel: "stylesheet", href })) : [],
|
|
module?.links?.() || []
|
|
];
|
|
}).flat(2);
|
|
let preloads = getModuleLinkHrefs(matches, manifest);
|
|
return dedupeLinkDescriptors(descriptors, preloads);
|
|
}
|
|
function getRouteCssDescriptors(route) {
|
|
if (!route.css) return [];
|
|
return route.css.map((href) => ({ rel: "stylesheet", href }));
|
|
}
|
|
async function prefetchRouteCss(route) {
|
|
if (!route.css) return;
|
|
let descriptors = getRouteCssDescriptors(route);
|
|
await Promise.all(descriptors.map(prefetchStyleLink));
|
|
}
|
|
async function prefetchStyleLinks(route, routeModule) {
|
|
if (!route.css && !routeModule.links || !isPreloadSupported()) return;
|
|
let descriptors = [];
|
|
if (route.css) {
|
|
descriptors.push(...getRouteCssDescriptors(route));
|
|
}
|
|
if (routeModule.links) {
|
|
descriptors.push(...routeModule.links());
|
|
}
|
|
if (descriptors.length === 0) return;
|
|
let styleLinks = [];
|
|
for (let descriptor of descriptors) {
|
|
if (!isPageLinkDescriptor(descriptor) && descriptor.rel === "stylesheet") {
|
|
styleLinks.push({
|
|
...descriptor,
|
|
rel: "preload",
|
|
as: "style"
|
|
});
|
|
}
|
|
}
|
|
await Promise.all(styleLinks.map(prefetchStyleLink));
|
|
}
|
|
async function prefetchStyleLink(descriptor) {
|
|
return new Promise((resolve) => {
|
|
if (descriptor.media && !window.matchMedia(descriptor.media).matches || document.querySelector(
|
|
`link[rel="stylesheet"][href="${descriptor.href}"]`
|
|
)) {
|
|
return resolve();
|
|
}
|
|
let link = document.createElement("link");
|
|
Object.assign(link, descriptor);
|
|
function removeLink() {
|
|
if (document.head.contains(link)) {
|
|
document.head.removeChild(link);
|
|
}
|
|
}
|
|
link.onload = () => {
|
|
removeLink();
|
|
resolve();
|
|
};
|
|
link.onerror = () => {
|
|
removeLink();
|
|
resolve();
|
|
};
|
|
document.head.appendChild(link);
|
|
});
|
|
}
|
|
function isPageLinkDescriptor(object) {
|
|
return object != null && typeof object.page === "string";
|
|
}
|
|
function isHtmlLinkDescriptor(object) {
|
|
if (object == null) {
|
|
return false;
|
|
}
|
|
if (object.href == null) {
|
|
return object.rel === "preload" && typeof object.imageSrcSet === "string" && typeof object.imageSizes === "string";
|
|
}
|
|
return typeof object.rel === "string" && typeof object.href === "string";
|
|
}
|
|
async function getKeyedPrefetchLinks(matches, manifest, routeModules) {
|
|
let links = await Promise.all(
|
|
matches.map(async (match) => {
|
|
let route = manifest.routes[match.route.id];
|
|
if (route) {
|
|
let mod = await loadRouteModule(route, routeModules);
|
|
return mod.links ? mod.links() : [];
|
|
}
|
|
return [];
|
|
})
|
|
);
|
|
return dedupeLinkDescriptors(
|
|
links.flat(1).filter(isHtmlLinkDescriptor).filter((link) => link.rel === "stylesheet" || link.rel === "preload").map(
|
|
(link) => link.rel === "stylesheet" ? { ...link, rel: "prefetch", as: "style" } : { ...link, rel: "prefetch" }
|
|
)
|
|
);
|
|
}
|
|
function getNewMatchesForLinks(page, nextMatches, currentMatches, manifest, location, mode) {
|
|
let isNew = (match, index) => {
|
|
if (!currentMatches[index]) return true;
|
|
return match.route.id !== currentMatches[index].route.id;
|
|
};
|
|
let matchPathChanged = (match, index) => {
|
|
return (
|
|
// param change, /users/123 -> /users/456
|
|
currentMatches[index].pathname !== match.pathname || // splat param changed, which is not present in match.path
|
|
// e.g. /files/images/avatar.jpg -> files/finances.xls
|
|
currentMatches[index].route.path?.endsWith("*") && currentMatches[index].params["*"] !== match.params["*"]
|
|
);
|
|
};
|
|
if (mode === "assets") {
|
|
return nextMatches.filter(
|
|
(match, index) => isNew(match, index) || matchPathChanged(match, index)
|
|
);
|
|
}
|
|
if (mode === "data") {
|
|
return nextMatches.filter((match, index) => {
|
|
let manifestRoute = manifest.routes[match.route.id];
|
|
if (!manifestRoute || !manifestRoute.hasLoader) {
|
|
return false;
|
|
}
|
|
if (isNew(match, index) || matchPathChanged(match, index)) {
|
|
return true;
|
|
}
|
|
if (match.route.shouldRevalidate) {
|
|
let routeChoice = match.route.shouldRevalidate({
|
|
currentUrl: new URL(
|
|
location.pathname + location.search + location.hash,
|
|
window.origin
|
|
),
|
|
currentParams: currentMatches[0]?.params || {},
|
|
nextUrl: new URL(page, window.origin),
|
|
nextParams: match.params,
|
|
defaultShouldRevalidate: true
|
|
});
|
|
if (typeof routeChoice === "boolean") {
|
|
return routeChoice;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
return [];
|
|
}
|
|
function getModuleLinkHrefs(matches, manifest, { includeHydrateFallback } = {}) {
|
|
return dedupeHrefs(
|
|
matches.map((match) => {
|
|
let route = manifest.routes[match.route.id];
|
|
if (!route) return [];
|
|
let hrefs = [route.module];
|
|
if (route.clientActionModule) {
|
|
hrefs = hrefs.concat(route.clientActionModule);
|
|
}
|
|
if (route.clientLoaderModule) {
|
|
hrefs = hrefs.concat(route.clientLoaderModule);
|
|
}
|
|
if (includeHydrateFallback && route.hydrateFallbackModule) {
|
|
hrefs = hrefs.concat(route.hydrateFallbackModule);
|
|
}
|
|
if (route.imports) {
|
|
hrefs = hrefs.concat(route.imports);
|
|
}
|
|
return hrefs;
|
|
}).flat(1)
|
|
);
|
|
}
|
|
function dedupeHrefs(hrefs) {
|
|
return [...new Set(hrefs)];
|
|
}
|
|
function sortKeys(obj) {
|
|
let sorted = {};
|
|
let keys = Object.keys(obj).sort();
|
|
for (let key of keys) {
|
|
sorted[key] = obj[key];
|
|
}
|
|
return sorted;
|
|
}
|
|
function dedupeLinkDescriptors(descriptors, preloads) {
|
|
let set = /* @__PURE__ */ new Set();
|
|
let preloadsSet = new Set(preloads);
|
|
return descriptors.reduce((deduped, descriptor) => {
|
|
let alreadyModulePreload = preloads && !isPageLinkDescriptor(descriptor) && descriptor.as === "script" && descriptor.href && preloadsSet.has(descriptor.href);
|
|
if (alreadyModulePreload) {
|
|
return deduped;
|
|
}
|
|
let key = JSON.stringify(sortKeys(descriptor));
|
|
if (!set.has(key)) {
|
|
set.add(key);
|
|
deduped.push({ key, link: descriptor });
|
|
}
|
|
return deduped;
|
|
}, []);
|
|
}
|
|
var _isPreloadSupported;
|
|
function isPreloadSupported() {
|
|
if (_isPreloadSupported !== void 0) {
|
|
return _isPreloadSupported;
|
|
}
|
|
let el = document.createElement("link");
|
|
_isPreloadSupported = el.relList.supports("preload");
|
|
el = null;
|
|
return _isPreloadSupported;
|
|
}
|
|
|
|
// lib/dom/ssr/fog-of-war.ts
|
|
import * as React7 from "react";
|
|
|
|
// lib/dom/ssr/routes.tsx
|
|
import * as React6 from "react";
|
|
|
|
// lib/dom/ssr/fallback.tsx
|
|
import * as React5 from "react";
|
|
function RemixRootDefaultHydrateFallback() {
|
|
return /* @__PURE__ */ React5.createElement(BoundaryShell, { title: "Loading...", renderScripts: true }, ENABLE_DEV_WARNINGS ? /* @__PURE__ */ React5.createElement(
|
|
"script",
|
|
{
|
|
dangerouslySetInnerHTML: {
|
|
__html: `
|
|
console.log(
|
|
"\u{1F4BF} Hey developer \u{1F44B}. You can provide a way better UX than this " +
|
|
"when your app is loading JS modules and/or running \`clientLoader\` " +
|
|
"functions. Check out https://reactrouter.com/start/framework/route-module#hydratefallback " +
|
|
"for more information."
|
|
);
|
|
`
|
|
}
|
|
}
|
|
) : null);
|
|
}
|
|
|
|
// lib/dom/ssr/routes.tsx
|
|
function groupRoutesByParentId(manifest) {
|
|
let routes = {};
|
|
Object.values(manifest).forEach((route) => {
|
|
if (route) {
|
|
let parentId = route.parentId || "";
|
|
if (!routes[parentId]) {
|
|
routes[parentId] = [];
|
|
}
|
|
routes[parentId].push(route);
|
|
}
|
|
});
|
|
return routes;
|
|
}
|
|
function getRouteComponents(route, routeModule, isSpaMode) {
|
|
let Component4 = getRouteModuleComponent(routeModule);
|
|
let HydrateFallback = routeModule.HydrateFallback && (!isSpaMode || route.id === "root") ? routeModule.HydrateFallback : route.id === "root" ? RemixRootDefaultHydrateFallback : void 0;
|
|
let ErrorBoundary = routeModule.ErrorBoundary ? routeModule.ErrorBoundary : route.id === "root" ? () => /* @__PURE__ */ React6.createElement(RemixRootDefaultErrorBoundary, { error: useRouteError() }) : void 0;
|
|
if (route.id === "root" && routeModule.Layout) {
|
|
return {
|
|
...Component4 ? {
|
|
element: /* @__PURE__ */ React6.createElement(routeModule.Layout, null, /* @__PURE__ */ React6.createElement(Component4, null))
|
|
} : { Component: Component4 },
|
|
...ErrorBoundary ? {
|
|
errorElement: /* @__PURE__ */ React6.createElement(routeModule.Layout, null, /* @__PURE__ */ React6.createElement(ErrorBoundary, null))
|
|
} : { ErrorBoundary },
|
|
...HydrateFallback ? {
|
|
hydrateFallbackElement: /* @__PURE__ */ React6.createElement(routeModule.Layout, null, /* @__PURE__ */ React6.createElement(HydrateFallback, null))
|
|
} : { HydrateFallback }
|
|
};
|
|
}
|
|
return { Component: Component4, ErrorBoundary, HydrateFallback };
|
|
}
|
|
function createServerRoutes(manifest, routeModules, future, isSpaMode, parentId = "", routesByParentId = groupRoutesByParentId(manifest), spaModeLazyPromise = Promise.resolve({ Component: () => null })) {
|
|
return (routesByParentId[parentId] || []).map((route) => {
|
|
let routeModule = routeModules[route.id];
|
|
invariant2(
|
|
routeModule,
|
|
"No `routeModule` available to create server routes"
|
|
);
|
|
let dataRoute = {
|
|
...getRouteComponents(route, routeModule, isSpaMode),
|
|
caseSensitive: route.caseSensitive,
|
|
id: route.id,
|
|
index: route.index,
|
|
path: route.path,
|
|
handle: routeModule.handle,
|
|
// For SPA Mode, all routes are lazy except root. However we tell the
|
|
// router root is also lazy here too since we don't need a full
|
|
// implementation - we just need a `lazy` prop to tell the RR rendering
|
|
// where to stop which is always at the root route in SPA mode
|
|
lazy: isSpaMode ? () => spaModeLazyPromise : void 0,
|
|
// For partial hydration rendering, we need to indicate when the route
|
|
// has a loader/clientLoader, but it won't ever be called during the static
|
|
// render, so just give it a no-op function so we can render down to the
|
|
// proper fallback
|
|
loader: route.hasLoader || route.hasClientLoader ? () => null : void 0
|
|
// We don't need middleware/action/shouldRevalidate on these routes since
|
|
// they're for a static render
|
|
};
|
|
let children = createServerRoutes(
|
|
manifest,
|
|
routeModules,
|
|
future,
|
|
isSpaMode,
|
|
route.id,
|
|
routesByParentId,
|
|
spaModeLazyPromise
|
|
);
|
|
if (children.length > 0) dataRoute.children = children;
|
|
return dataRoute;
|
|
});
|
|
}
|
|
function createClientRoutesWithHMRRevalidationOptOut(needsRevalidation, manifest, routeModulesCache, initialState, ssr, isSpaMode) {
|
|
return createClientRoutes(
|
|
manifest,
|
|
routeModulesCache,
|
|
initialState,
|
|
ssr,
|
|
isSpaMode,
|
|
"",
|
|
groupRoutesByParentId(manifest),
|
|
needsRevalidation
|
|
);
|
|
}
|
|
function preventInvalidServerHandlerCall(type, route) {
|
|
if (type === "loader" && !route.hasLoader || type === "action" && !route.hasAction) {
|
|
let fn = type === "action" ? "serverAction()" : "serverLoader()";
|
|
let msg = `You are trying to call ${fn} on a route that does not have a server ${type} (routeId: "${route.id}")`;
|
|
console.error(msg);
|
|
throw new ErrorResponseImpl(400, "Bad Request", new Error(msg), true);
|
|
}
|
|
}
|
|
function noActionDefinedError(type, routeId) {
|
|
let article = type === "clientAction" ? "a" : "an";
|
|
let msg = `Route "${routeId}" does not have ${article} ${type}, but you are trying to submit to it. To fix this, please add ${article} \`${type}\` function to the route`;
|
|
console.error(msg);
|
|
throw new ErrorResponseImpl(405, "Method Not Allowed", new Error(msg), true);
|
|
}
|
|
function createClientRoutes(manifest, routeModulesCache, initialState, ssr, isSpaMode, parentId = "", routesByParentId = groupRoutesByParentId(manifest), needsRevalidation) {
|
|
return (routesByParentId[parentId] || []).map((route) => {
|
|
let routeModule = routeModulesCache[route.id];
|
|
function fetchServerHandler(singleFetch) {
|
|
invariant2(
|
|
typeof singleFetch === "function",
|
|
"No single fetch function available for route handler"
|
|
);
|
|
return singleFetch();
|
|
}
|
|
function fetchServerLoader(singleFetch) {
|
|
if (!route.hasLoader) return Promise.resolve(null);
|
|
return fetchServerHandler(singleFetch);
|
|
}
|
|
function fetchServerAction(singleFetch) {
|
|
if (!route.hasAction) {
|
|
throw noActionDefinedError("action", route.id);
|
|
}
|
|
return fetchServerHandler(singleFetch);
|
|
}
|
|
function prefetchModule(modulePath) {
|
|
import(
|
|
/* @vite-ignore */
|
|
/* webpackIgnore: true */
|
|
modulePath
|
|
);
|
|
}
|
|
function prefetchRouteModuleChunks(route2) {
|
|
if (route2.clientActionModule) {
|
|
prefetchModule(route2.clientActionModule);
|
|
}
|
|
if (route2.clientLoaderModule) {
|
|
prefetchModule(route2.clientLoaderModule);
|
|
}
|
|
}
|
|
async function prefetchStylesAndCallHandler(handler) {
|
|
let cachedModule = routeModulesCache[route.id];
|
|
let linkPrefetchPromise = cachedModule ? prefetchStyleLinks(route, cachedModule) : Promise.resolve();
|
|
try {
|
|
return handler();
|
|
} finally {
|
|
await linkPrefetchPromise;
|
|
}
|
|
}
|
|
let dataRoute = {
|
|
id: route.id,
|
|
index: route.index,
|
|
path: route.path
|
|
};
|
|
if (routeModule) {
|
|
Object.assign(dataRoute, {
|
|
...dataRoute,
|
|
...getRouteComponents(route, routeModule, isSpaMode),
|
|
middleware: routeModule.clientMiddleware,
|
|
handle: routeModule.handle,
|
|
shouldRevalidate: getShouldRevalidateFunction(
|
|
dataRoute.path,
|
|
routeModule,
|
|
route,
|
|
ssr,
|
|
needsRevalidation
|
|
)
|
|
});
|
|
let hasInitialData = initialState && initialState.loaderData && route.id in initialState.loaderData;
|
|
let initialData = hasInitialData ? initialState?.loaderData?.[route.id] : void 0;
|
|
let hasInitialError = initialState && initialState.errors && route.id in initialState.errors;
|
|
let initialError = hasInitialError ? initialState?.errors?.[route.id] : void 0;
|
|
let isHydrationRequest = needsRevalidation == null && (routeModule.clientLoader?.hydrate === true || !route.hasLoader);
|
|
dataRoute.loader = async ({ request, params, context }, singleFetch) => {
|
|
try {
|
|
let result = await prefetchStylesAndCallHandler(async () => {
|
|
invariant2(
|
|
routeModule,
|
|
"No `routeModule` available for critical-route loader"
|
|
);
|
|
if (!routeModule.clientLoader) {
|
|
return fetchServerLoader(singleFetch);
|
|
}
|
|
return routeModule.clientLoader({
|
|
request,
|
|
params,
|
|
context,
|
|
async serverLoader() {
|
|
preventInvalidServerHandlerCall("loader", route);
|
|
if (isHydrationRequest) {
|
|
if (hasInitialData) {
|
|
return initialData;
|
|
}
|
|
if (hasInitialError) {
|
|
throw initialError;
|
|
}
|
|
}
|
|
return fetchServerLoader(singleFetch);
|
|
}
|
|
});
|
|
});
|
|
return result;
|
|
} finally {
|
|
isHydrationRequest = false;
|
|
}
|
|
};
|
|
dataRoute.loader.hydrate = shouldHydrateRouteLoader(
|
|
route.id,
|
|
routeModule.clientLoader,
|
|
route.hasLoader,
|
|
isSpaMode
|
|
);
|
|
dataRoute.action = ({ request, params, context }, singleFetch) => {
|
|
return prefetchStylesAndCallHandler(async () => {
|
|
invariant2(
|
|
routeModule,
|
|
"No `routeModule` available for critical-route action"
|
|
);
|
|
if (!routeModule.clientAction) {
|
|
if (isSpaMode) {
|
|
throw noActionDefinedError("clientAction", route.id);
|
|
}
|
|
return fetchServerAction(singleFetch);
|
|
}
|
|
return routeModule.clientAction({
|
|
request,
|
|
params,
|
|
context,
|
|
async serverAction() {
|
|
preventInvalidServerHandlerCall("action", route);
|
|
return fetchServerAction(singleFetch);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
} else {
|
|
if (!route.hasClientLoader) {
|
|
dataRoute.loader = (_, singleFetch) => prefetchStylesAndCallHandler(() => {
|
|
return fetchServerLoader(singleFetch);
|
|
});
|
|
}
|
|
if (!route.hasClientAction) {
|
|
dataRoute.action = (_, singleFetch) => prefetchStylesAndCallHandler(() => {
|
|
if (isSpaMode) {
|
|
throw noActionDefinedError("clientAction", route.id);
|
|
}
|
|
return fetchServerAction(singleFetch);
|
|
});
|
|
}
|
|
let lazyRoutePromise;
|
|
async function getLazyRoute() {
|
|
if (lazyRoutePromise) {
|
|
return await lazyRoutePromise;
|
|
}
|
|
lazyRoutePromise = (async () => {
|
|
if (route.clientLoaderModule || route.clientActionModule) {
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
}
|
|
let routeModulePromise = loadRouteModuleWithBlockingLinks(
|
|
route,
|
|
routeModulesCache
|
|
);
|
|
prefetchRouteModuleChunks(route);
|
|
return await routeModulePromise;
|
|
})();
|
|
return await lazyRoutePromise;
|
|
}
|
|
dataRoute.lazy = {
|
|
loader: route.hasClientLoader ? async () => {
|
|
let { clientLoader } = route.clientLoaderModule ? await import(
|
|
/* @vite-ignore */
|
|
/* webpackIgnore: true */
|
|
route.clientLoaderModule
|
|
) : await getLazyRoute();
|
|
invariant2(clientLoader, "No `clientLoader` export found");
|
|
return (args, singleFetch) => clientLoader({
|
|
...args,
|
|
async serverLoader() {
|
|
preventInvalidServerHandlerCall("loader", route);
|
|
return fetchServerLoader(singleFetch);
|
|
}
|
|
});
|
|
} : void 0,
|
|
action: route.hasClientAction ? async () => {
|
|
let clientActionPromise = route.clientActionModule ? import(
|
|
/* @vite-ignore */
|
|
/* webpackIgnore: true */
|
|
route.clientActionModule
|
|
) : getLazyRoute();
|
|
prefetchRouteModuleChunks(route);
|
|
let { clientAction } = await clientActionPromise;
|
|
invariant2(clientAction, "No `clientAction` export found");
|
|
return (args, singleFetch) => clientAction({
|
|
...args,
|
|
async serverAction() {
|
|
preventInvalidServerHandlerCall("action", route);
|
|
return fetchServerAction(singleFetch);
|
|
}
|
|
});
|
|
} : void 0,
|
|
middleware: route.hasClientMiddleware ? async () => {
|
|
let { clientMiddleware } = route.clientMiddlewareModule ? await import(
|
|
/* @vite-ignore */
|
|
/* webpackIgnore: true */
|
|
route.clientMiddlewareModule
|
|
) : await getLazyRoute();
|
|
invariant2(clientMiddleware, "No `clientMiddleware` export found");
|
|
return clientMiddleware;
|
|
} : void 0,
|
|
shouldRevalidate: async () => {
|
|
let lazyRoute = await getLazyRoute();
|
|
return getShouldRevalidateFunction(
|
|
dataRoute.path,
|
|
lazyRoute,
|
|
route,
|
|
ssr,
|
|
needsRevalidation
|
|
);
|
|
},
|
|
handle: async () => (await getLazyRoute()).handle,
|
|
// No need to wrap these in layout since the root route is never
|
|
// loaded via route.lazy()
|
|
Component: async () => (await getLazyRoute()).Component,
|
|
ErrorBoundary: route.hasErrorBoundary ? async () => (await getLazyRoute()).ErrorBoundary : void 0
|
|
};
|
|
}
|
|
let children = createClientRoutes(
|
|
manifest,
|
|
routeModulesCache,
|
|
initialState,
|
|
ssr,
|
|
isSpaMode,
|
|
route.id,
|
|
routesByParentId,
|
|
needsRevalidation
|
|
);
|
|
if (children.length > 0) dataRoute.children = children;
|
|
return dataRoute;
|
|
});
|
|
}
|
|
function getShouldRevalidateFunction(path, route, manifestRoute, ssr, needsRevalidation) {
|
|
if (needsRevalidation) {
|
|
return wrapShouldRevalidateForHdr(
|
|
manifestRoute.id,
|
|
route.shouldRevalidate,
|
|
needsRevalidation
|
|
);
|
|
}
|
|
if (!ssr && manifestRoute.hasLoader && !manifestRoute.hasClientLoader) {
|
|
let myParams = path ? compilePath(path)[1].map((p) => p.paramName) : [];
|
|
const didParamsChange = (opts) => myParams.some((p) => opts.currentParams[p] !== opts.nextParams[p]);
|
|
if (route.shouldRevalidate) {
|
|
let fn = route.shouldRevalidate;
|
|
return (opts) => fn({
|
|
...opts,
|
|
defaultShouldRevalidate: didParamsChange(opts)
|
|
});
|
|
} else {
|
|
return (opts) => didParamsChange(opts);
|
|
}
|
|
}
|
|
if (ssr && route.shouldRevalidate) {
|
|
let fn = route.shouldRevalidate;
|
|
return (opts) => fn({ ...opts, defaultShouldRevalidate: true });
|
|
}
|
|
return route.shouldRevalidate;
|
|
}
|
|
function wrapShouldRevalidateForHdr(routeId, routeShouldRevalidate, needsRevalidation) {
|
|
let handledRevalidation = false;
|
|
return (arg) => {
|
|
if (!handledRevalidation) {
|
|
handledRevalidation = true;
|
|
return needsRevalidation.has(routeId);
|
|
}
|
|
return routeShouldRevalidate ? routeShouldRevalidate(arg) : arg.defaultShouldRevalidate;
|
|
};
|
|
}
|
|
async function loadRouteModuleWithBlockingLinks(route, routeModules) {
|
|
let routeModulePromise = loadRouteModule(route, routeModules);
|
|
let prefetchRouteCssPromise = prefetchRouteCss(route);
|
|
let routeModule = await routeModulePromise;
|
|
await Promise.all([
|
|
prefetchRouteCssPromise,
|
|
prefetchStyleLinks(route, routeModule)
|
|
]);
|
|
return {
|
|
Component: getRouteModuleComponent(routeModule),
|
|
ErrorBoundary: routeModule.ErrorBoundary,
|
|
clientMiddleware: routeModule.clientMiddleware,
|
|
clientAction: routeModule.clientAction,
|
|
clientLoader: routeModule.clientLoader,
|
|
handle: routeModule.handle,
|
|
links: routeModule.links,
|
|
meta: routeModule.meta,
|
|
shouldRevalidate: routeModule.shouldRevalidate
|
|
};
|
|
}
|
|
function getRouteModuleComponent(routeModule) {
|
|
if (routeModule.default == null) return void 0;
|
|
let isEmptyObject = typeof routeModule.default === "object" && Object.keys(routeModule.default).length === 0;
|
|
if (!isEmptyObject) {
|
|
return routeModule.default;
|
|
}
|
|
}
|
|
function shouldHydrateRouteLoader(routeId, clientLoader, hasLoader, isSpaMode) {
|
|
return isSpaMode && routeId !== "root" || clientLoader != null && (clientLoader.hydrate === true || hasLoader !== true);
|
|
}
|
|
|
|
// lib/dom/ssr/fog-of-war.ts
|
|
var nextPaths = /* @__PURE__ */ new Set();
|
|
var discoveredPathsMaxSize = 1e3;
|
|
var discoveredPaths = /* @__PURE__ */ new Set();
|
|
var URL_LIMIT = 7680;
|
|
function isFogOfWarEnabled(routeDiscovery, ssr) {
|
|
return routeDiscovery.mode === "lazy" && ssr === true;
|
|
}
|
|
function getPartialManifest({ sri, ...manifest }, router) {
|
|
let routeIds = new Set(router.state.matches.map((m) => m.route.id));
|
|
let segments = router.state.location.pathname.split("/").filter(Boolean);
|
|
let paths = ["/"];
|
|
segments.pop();
|
|
while (segments.length > 0) {
|
|
paths.push(`/${segments.join("/")}`);
|
|
segments.pop();
|
|
}
|
|
paths.forEach((path) => {
|
|
let matches = matchRoutes(router.routes, path, router.basename);
|
|
if (matches) {
|
|
matches.forEach((m) => routeIds.add(m.route.id));
|
|
}
|
|
});
|
|
let initialRoutes = [...routeIds].reduce(
|
|
(acc, id) => Object.assign(acc, { [id]: manifest.routes[id] }),
|
|
{}
|
|
);
|
|
return {
|
|
...manifest,
|
|
routes: initialRoutes,
|
|
sri: sri ? true : void 0
|
|
};
|
|
}
|
|
function getPatchRoutesOnNavigationFunction(manifest, routeModules, ssr, routeDiscovery, isSpaMode, basename) {
|
|
if (!isFogOfWarEnabled(routeDiscovery, ssr)) {
|
|
return void 0;
|
|
}
|
|
return async ({ path, patch, signal, fetcherKey }) => {
|
|
if (discoveredPaths.has(path)) {
|
|
return;
|
|
}
|
|
await fetchAndApplyManifestPatches(
|
|
[path],
|
|
fetcherKey ? window.location.href : path,
|
|
manifest,
|
|
routeModules,
|
|
ssr,
|
|
isSpaMode,
|
|
basename,
|
|
routeDiscovery.manifestPath,
|
|
patch,
|
|
signal
|
|
);
|
|
};
|
|
}
|
|
function useFogOFWarDiscovery(router, manifest, routeModules, ssr, routeDiscovery, isSpaMode) {
|
|
React7.useEffect(() => {
|
|
if (!isFogOfWarEnabled(routeDiscovery, ssr) || // @ts-expect-error - TS doesn't know about this yet
|
|
window.navigator?.connection?.saveData === true) {
|
|
return;
|
|
}
|
|
function registerElement(el) {
|
|
let path = el.tagName === "FORM" ? el.getAttribute("action") : el.getAttribute("href");
|
|
if (!path) {
|
|
return;
|
|
}
|
|
let pathname = el.tagName === "A" ? el.pathname : new URL(path, window.location.origin).pathname;
|
|
if (!discoveredPaths.has(pathname)) {
|
|
nextPaths.add(pathname);
|
|
}
|
|
}
|
|
async function fetchPatches() {
|
|
document.querySelectorAll("a[data-discover], form[data-discover]").forEach(registerElement);
|
|
let lazyPaths = Array.from(nextPaths.keys()).filter((path) => {
|
|
if (discoveredPaths.has(path)) {
|
|
nextPaths.delete(path);
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
if (lazyPaths.length === 0) {
|
|
return;
|
|
}
|
|
try {
|
|
await fetchAndApplyManifestPatches(
|
|
lazyPaths,
|
|
null,
|
|
manifest,
|
|
routeModules,
|
|
ssr,
|
|
isSpaMode,
|
|
router.basename,
|
|
routeDiscovery.manifestPath,
|
|
router.patchRoutes
|
|
);
|
|
} catch (e) {
|
|
console.error("Failed to fetch manifest patches", e);
|
|
}
|
|
}
|
|
let debouncedFetchPatches = debounce(fetchPatches, 100);
|
|
fetchPatches();
|
|
let observer = new MutationObserver(() => debouncedFetchPatches());
|
|
observer.observe(document.documentElement, {
|
|
subtree: true,
|
|
childList: true,
|
|
attributes: true,
|
|
attributeFilter: ["data-discover", "href", "action"]
|
|
});
|
|
return () => observer.disconnect();
|
|
}, [ssr, isSpaMode, manifest, routeModules, router, routeDiscovery]);
|
|
}
|
|
function getManifestPath(_manifestPath, basename) {
|
|
let manifestPath = _manifestPath || "/__manifest";
|
|
if (basename == null) {
|
|
return manifestPath;
|
|
}
|
|
return `${basename}${manifestPath}`.replace(/\/+/g, "/");
|
|
}
|
|
var MANIFEST_VERSION_STORAGE_KEY = "react-router-manifest-version";
|
|
async function fetchAndApplyManifestPatches(paths, errorReloadPath, manifest, routeModules, ssr, isSpaMode, basename, manifestPath, patchRoutes, signal) {
|
|
const searchParams = new URLSearchParams();
|
|
searchParams.set("paths", paths.sort().join(","));
|
|
searchParams.set("version", manifest.version);
|
|
let url = new URL(
|
|
getManifestPath(manifestPath, basename),
|
|
window.location.origin
|
|
);
|
|
url.search = searchParams.toString();
|
|
if (url.toString().length > URL_LIMIT) {
|
|
nextPaths.clear();
|
|
return;
|
|
}
|
|
let serverPatches;
|
|
try {
|
|
let res = await fetch(url, { signal });
|
|
if (!res.ok) {
|
|
throw new Error(`${res.status} ${res.statusText}`);
|
|
} else if (res.status === 204 && res.headers.has("X-Remix-Reload-Document")) {
|
|
if (!errorReloadPath) {
|
|
console.warn(
|
|
"Detected a manifest version mismatch during eager route discovery. The next navigation/fetch to an undiscovered route will result in a new document navigation to sync up with the latest manifest."
|
|
);
|
|
return;
|
|
}
|
|
try {
|
|
if (sessionStorage.getItem(MANIFEST_VERSION_STORAGE_KEY) === manifest.version) {
|
|
console.error(
|
|
"Unable to discover routes due to manifest version mismatch."
|
|
);
|
|
return;
|
|
}
|
|
sessionStorage.setItem(MANIFEST_VERSION_STORAGE_KEY, manifest.version);
|
|
} catch {
|
|
}
|
|
window.location.href = errorReloadPath;
|
|
console.warn("Detected manifest version mismatch, reloading...");
|
|
await new Promise(() => {
|
|
});
|
|
} else if (res.status >= 400) {
|
|
throw new Error(await res.text());
|
|
}
|
|
try {
|
|
sessionStorage.removeItem(MANIFEST_VERSION_STORAGE_KEY);
|
|
} catch {
|
|
}
|
|
serverPatches = await res.json();
|
|
} catch (e) {
|
|
if (signal?.aborted) return;
|
|
throw e;
|
|
}
|
|
let knownRoutes = new Set(Object.keys(manifest.routes));
|
|
let patches = Object.values(serverPatches).reduce((acc, route) => {
|
|
if (route && !knownRoutes.has(route.id)) {
|
|
acc[route.id] = route;
|
|
}
|
|
return acc;
|
|
}, {});
|
|
Object.assign(manifest.routes, patches);
|
|
paths.forEach((p) => addToFifoQueue(p, discoveredPaths));
|
|
let parentIds = /* @__PURE__ */ new Set();
|
|
Object.values(patches).forEach((patch) => {
|
|
if (patch && (!patch.parentId || !patches[patch.parentId])) {
|
|
parentIds.add(patch.parentId);
|
|
}
|
|
});
|
|
parentIds.forEach(
|
|
(parentId) => patchRoutes(
|
|
parentId || null,
|
|
createClientRoutes(patches, routeModules, null, ssr, isSpaMode, parentId)
|
|
)
|
|
);
|
|
}
|
|
function addToFifoQueue(path, queue) {
|
|
if (queue.size >= discoveredPathsMaxSize) {
|
|
let first = queue.values().next().value;
|
|
queue.delete(first);
|
|
}
|
|
queue.add(path);
|
|
}
|
|
function debounce(callback, wait) {
|
|
let timeoutId;
|
|
return (...args) => {
|
|
window.clearTimeout(timeoutId);
|
|
timeoutId = window.setTimeout(() => callback(...args), wait);
|
|
};
|
|
}
|
|
|
|
// lib/dom/ssr/components.tsx
|
|
function useDataRouterContext2() {
|
|
let context = React8.useContext(DataRouterContext);
|
|
invariant2(
|
|
context,
|
|
"You must render this element inside a <DataRouterContext.Provider> element"
|
|
);
|
|
return context;
|
|
}
|
|
function useDataRouterStateContext() {
|
|
let context = React8.useContext(DataRouterStateContext);
|
|
invariant2(
|
|
context,
|
|
"You must render this element inside a <DataRouterStateContext.Provider> element"
|
|
);
|
|
return context;
|
|
}
|
|
var FrameworkContext = React8.createContext(void 0);
|
|
FrameworkContext.displayName = "FrameworkContext";
|
|
function useFrameworkContext() {
|
|
let context = React8.useContext(FrameworkContext);
|
|
invariant2(
|
|
context,
|
|
"You must render this element inside a <HydratedRouter> element"
|
|
);
|
|
return context;
|
|
}
|
|
function usePrefetchBehavior(prefetch, theirElementProps) {
|
|
let frameworkContext = React8.useContext(FrameworkContext);
|
|
let [maybePrefetch, setMaybePrefetch] = React8.useState(false);
|
|
let [shouldPrefetch, setShouldPrefetch] = React8.useState(false);
|
|
let { onFocus, onBlur, onMouseEnter, onMouseLeave, onTouchStart } = theirElementProps;
|
|
let ref = React8.useRef(null);
|
|
React8.useEffect(() => {
|
|
if (prefetch === "render") {
|
|
setShouldPrefetch(true);
|
|
}
|
|
if (prefetch === "viewport") {
|
|
let callback = (entries) => {
|
|
entries.forEach((entry) => {
|
|
setShouldPrefetch(entry.isIntersecting);
|
|
});
|
|
};
|
|
let observer = new IntersectionObserver(callback, { threshold: 0.5 });
|
|
if (ref.current) observer.observe(ref.current);
|
|
return () => {
|
|
observer.disconnect();
|
|
};
|
|
}
|
|
}, [prefetch]);
|
|
React8.useEffect(() => {
|
|
if (maybePrefetch) {
|
|
let id = setTimeout(() => {
|
|
setShouldPrefetch(true);
|
|
}, 100);
|
|
return () => {
|
|
clearTimeout(id);
|
|
};
|
|
}
|
|
}, [maybePrefetch]);
|
|
let setIntent = () => {
|
|
setMaybePrefetch(true);
|
|
};
|
|
let cancelIntent = () => {
|
|
setMaybePrefetch(false);
|
|
setShouldPrefetch(false);
|
|
};
|
|
if (!frameworkContext) {
|
|
return [false, ref, {}];
|
|
}
|
|
if (prefetch !== "intent") {
|
|
return [shouldPrefetch, ref, {}];
|
|
}
|
|
return [
|
|
shouldPrefetch,
|
|
ref,
|
|
{
|
|
onFocus: composeEventHandlers(onFocus, setIntent),
|
|
onBlur: composeEventHandlers(onBlur, cancelIntent),
|
|
onMouseEnter: composeEventHandlers(onMouseEnter, setIntent),
|
|
onMouseLeave: composeEventHandlers(onMouseLeave, cancelIntent),
|
|
onTouchStart: composeEventHandlers(onTouchStart, setIntent)
|
|
}
|
|
];
|
|
}
|
|
function composeEventHandlers(theirHandler, ourHandler) {
|
|
return (event) => {
|
|
theirHandler && theirHandler(event);
|
|
if (!event.defaultPrevented) {
|
|
ourHandler(event);
|
|
}
|
|
};
|
|
}
|
|
function getActiveMatches(matches, errors, isSpaMode) {
|
|
if (isSpaMode && !isHydrated) {
|
|
return [matches[0]];
|
|
}
|
|
if (errors) {
|
|
let errorIdx = matches.findIndex((m) => errors[m.route.id] !== void 0);
|
|
return matches.slice(0, errorIdx + 1);
|
|
}
|
|
return matches;
|
|
}
|
|
var CRITICAL_CSS_DATA_ATTRIBUTE = "data-react-router-critical-css";
|
|
function Links({ nonce }) {
|
|
let { isSpaMode, manifest, routeModules, criticalCss } = useFrameworkContext();
|
|
let { errors, matches: routerMatches } = useDataRouterStateContext();
|
|
let matches = getActiveMatches(routerMatches, errors, isSpaMode);
|
|
let keyedLinks = React8.useMemo(
|
|
() => getKeyedLinksForMatches(matches, routeModules, manifest),
|
|
[matches, routeModules, manifest]
|
|
);
|
|
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, typeof criticalCss === "string" ? /* @__PURE__ */ React8.createElement(
|
|
"style",
|
|
{
|
|
...{ [CRITICAL_CSS_DATA_ATTRIBUTE]: "" },
|
|
dangerouslySetInnerHTML: { __html: criticalCss }
|
|
}
|
|
) : null, typeof criticalCss === "object" ? /* @__PURE__ */ React8.createElement(
|
|
"link",
|
|
{
|
|
...{ [CRITICAL_CSS_DATA_ATTRIBUTE]: "" },
|
|
rel: "stylesheet",
|
|
href: criticalCss.href,
|
|
nonce
|
|
}
|
|
) : null, keyedLinks.map(
|
|
({ key, link }) => isPageLinkDescriptor(link) ? /* @__PURE__ */ React8.createElement(PrefetchPageLinks, { key, nonce, ...link }) : /* @__PURE__ */ React8.createElement("link", { key, nonce, ...link })
|
|
));
|
|
}
|
|
function PrefetchPageLinks({ page, ...linkProps }) {
|
|
let { router } = useDataRouterContext2();
|
|
let matches = React8.useMemo(
|
|
() => matchRoutes(router.routes, page, router.basename),
|
|
[router.routes, page, router.basename]
|
|
);
|
|
if (!matches) {
|
|
return null;
|
|
}
|
|
return /* @__PURE__ */ React8.createElement(PrefetchPageLinksImpl, { page, matches, ...linkProps });
|
|
}
|
|
function useKeyedPrefetchLinks(matches) {
|
|
let { manifest, routeModules } = useFrameworkContext();
|
|
let [keyedPrefetchLinks, setKeyedPrefetchLinks] = React8.useState([]);
|
|
React8.useEffect(() => {
|
|
let interrupted = false;
|
|
void getKeyedPrefetchLinks(matches, manifest, routeModules).then(
|
|
(links) => {
|
|
if (!interrupted) {
|
|
setKeyedPrefetchLinks(links);
|
|
}
|
|
}
|
|
);
|
|
return () => {
|
|
interrupted = true;
|
|
};
|
|
}, [matches, manifest, routeModules]);
|
|
return keyedPrefetchLinks;
|
|
}
|
|
function PrefetchPageLinksImpl({
|
|
page,
|
|
matches: nextMatches,
|
|
...linkProps
|
|
}) {
|
|
let location = useLocation();
|
|
let { manifest, routeModules } = useFrameworkContext();
|
|
let { basename } = useDataRouterContext2();
|
|
let { loaderData, matches } = useDataRouterStateContext();
|
|
let newMatchesForData = React8.useMemo(
|
|
() => getNewMatchesForLinks(
|
|
page,
|
|
nextMatches,
|
|
matches,
|
|
manifest,
|
|
location,
|
|
"data"
|
|
),
|
|
[page, nextMatches, matches, manifest, location]
|
|
);
|
|
let newMatchesForAssets = React8.useMemo(
|
|
() => getNewMatchesForLinks(
|
|
page,
|
|
nextMatches,
|
|
matches,
|
|
manifest,
|
|
location,
|
|
"assets"
|
|
),
|
|
[page, nextMatches, matches, manifest, location]
|
|
);
|
|
let dataHrefs = React8.useMemo(() => {
|
|
if (page === location.pathname + location.search + location.hash) {
|
|
return [];
|
|
}
|
|
let routesParams = /* @__PURE__ */ new Set();
|
|
let foundOptOutRoute = false;
|
|
nextMatches.forEach((m) => {
|
|
let manifestRoute = manifest.routes[m.route.id];
|
|
if (!manifestRoute || !manifestRoute.hasLoader) {
|
|
return;
|
|
}
|
|
if (!newMatchesForData.some((m2) => m2.route.id === m.route.id) && m.route.id in loaderData && routeModules[m.route.id]?.shouldRevalidate) {
|
|
foundOptOutRoute = true;
|
|
} else if (manifestRoute.hasClientLoader) {
|
|
foundOptOutRoute = true;
|
|
} else {
|
|
routesParams.add(m.route.id);
|
|
}
|
|
});
|
|
if (routesParams.size === 0) {
|
|
return [];
|
|
}
|
|
let url = singleFetchUrl(page, basename, "data");
|
|
if (foundOptOutRoute && routesParams.size > 0) {
|
|
url.searchParams.set(
|
|
"_routes",
|
|
nextMatches.filter((m) => routesParams.has(m.route.id)).map((m) => m.route.id).join(",")
|
|
);
|
|
}
|
|
return [url.pathname + url.search];
|
|
}, [
|
|
basename,
|
|
loaderData,
|
|
location,
|
|
manifest,
|
|
newMatchesForData,
|
|
nextMatches,
|
|
page,
|
|
routeModules
|
|
]);
|
|
let moduleHrefs = React8.useMemo(
|
|
() => getModuleLinkHrefs(newMatchesForAssets, manifest),
|
|
[newMatchesForAssets, manifest]
|
|
);
|
|
let keyedPrefetchLinks = useKeyedPrefetchLinks(newMatchesForAssets);
|
|
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, dataHrefs.map((href) => /* @__PURE__ */ React8.createElement("link", { key: href, rel: "prefetch", as: "fetch", href, ...linkProps })), moduleHrefs.map((href) => /* @__PURE__ */ React8.createElement("link", { key: href, rel: "modulepreload", href, ...linkProps })), keyedPrefetchLinks.map(({ key, link }) => (
|
|
// these don't spread `linkProps` because they are full link descriptors
|
|
// already with their own props
|
|
/* @__PURE__ */ React8.createElement("link", { key, nonce: linkProps.nonce, ...link })
|
|
)));
|
|
}
|
|
function Meta() {
|
|
let { isSpaMode, routeModules } = useFrameworkContext();
|
|
let {
|
|
errors,
|
|
matches: routerMatches,
|
|
loaderData
|
|
} = useDataRouterStateContext();
|
|
let location = useLocation();
|
|
let _matches = getActiveMatches(routerMatches, errors, isSpaMode);
|
|
let error = null;
|
|
if (errors) {
|
|
error = errors[_matches[_matches.length - 1].route.id];
|
|
}
|
|
let meta = [];
|
|
let leafMeta = null;
|
|
let matches = [];
|
|
for (let i = 0; i < _matches.length; i++) {
|
|
let _match = _matches[i];
|
|
let routeId = _match.route.id;
|
|
let data2 = loaderData[routeId];
|
|
let params = _match.params;
|
|
let routeModule = routeModules[routeId];
|
|
let routeMeta = [];
|
|
let match = {
|
|
id: routeId,
|
|
data: data2,
|
|
loaderData: data2,
|
|
meta: [],
|
|
params: _match.params,
|
|
pathname: _match.pathname,
|
|
handle: _match.route.handle,
|
|
error
|
|
};
|
|
matches[i] = match;
|
|
if (routeModule?.meta) {
|
|
routeMeta = typeof routeModule.meta === "function" ? routeModule.meta({
|
|
data: data2,
|
|
loaderData: data2,
|
|
params,
|
|
location,
|
|
matches,
|
|
error
|
|
}) : Array.isArray(routeModule.meta) ? [...routeModule.meta] : routeModule.meta;
|
|
} else if (leafMeta) {
|
|
routeMeta = [...leafMeta];
|
|
}
|
|
routeMeta = routeMeta || [];
|
|
if (!Array.isArray(routeMeta)) {
|
|
throw new Error(
|
|
"The route at " + _match.route.path + " returns an invalid value. All route meta functions must return an array of meta objects.\n\nTo reference the meta function API, see https://remix.run/route/meta"
|
|
);
|
|
}
|
|
match.meta = routeMeta;
|
|
matches[i] = match;
|
|
meta = [...routeMeta];
|
|
leafMeta = meta;
|
|
}
|
|
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, meta.flat().map((metaProps) => {
|
|
if (!metaProps) {
|
|
return null;
|
|
}
|
|
if ("tagName" in metaProps) {
|
|
let { tagName, ...rest } = metaProps;
|
|
if (!isValidMetaTag(tagName)) {
|
|
console.warn(
|
|
`A meta object uses an invalid tagName: ${tagName}. Expected either 'link' or 'meta'`
|
|
);
|
|
return null;
|
|
}
|
|
let Comp = tagName;
|
|
return /* @__PURE__ */ React8.createElement(Comp, { key: JSON.stringify(rest), ...rest });
|
|
}
|
|
if ("title" in metaProps) {
|
|
return /* @__PURE__ */ React8.createElement("title", { key: "title" }, String(metaProps.title));
|
|
}
|
|
if ("charset" in metaProps) {
|
|
metaProps.charSet ?? (metaProps.charSet = metaProps.charset);
|
|
delete metaProps.charset;
|
|
}
|
|
if ("charSet" in metaProps && metaProps.charSet != null) {
|
|
return typeof metaProps.charSet === "string" ? /* @__PURE__ */ React8.createElement("meta", { key: "charSet", charSet: metaProps.charSet }) : null;
|
|
}
|
|
if ("script:ld+json" in metaProps) {
|
|
try {
|
|
let json = JSON.stringify(metaProps["script:ld+json"]);
|
|
return /* @__PURE__ */ React8.createElement(
|
|
"script",
|
|
{
|
|
key: `script:ld+json:${json}`,
|
|
type: "application/ld+json",
|
|
dangerouslySetInnerHTML: { __html: escapeHtml(json) }
|
|
}
|
|
);
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
}
|
|
return /* @__PURE__ */ React8.createElement("meta", { key: JSON.stringify(metaProps), ...metaProps });
|
|
}));
|
|
}
|
|
function isValidMetaTag(tagName) {
|
|
return typeof tagName === "string" && /^(meta|link)$/.test(tagName);
|
|
}
|
|
var isHydrated = false;
|
|
function setIsHydrated() {
|
|
isHydrated = true;
|
|
}
|
|
function Scripts(scriptProps) {
|
|
let {
|
|
manifest,
|
|
serverHandoffString,
|
|
isSpaMode,
|
|
renderMeta,
|
|
routeDiscovery,
|
|
ssr
|
|
} = useFrameworkContext();
|
|
let { router, static: isStatic, staticContext } = useDataRouterContext2();
|
|
let { matches: routerMatches } = useDataRouterStateContext();
|
|
let isRSCRouterContext = useIsRSCRouterContext();
|
|
let enableFogOfWar = isFogOfWarEnabled(routeDiscovery, ssr);
|
|
if (renderMeta) {
|
|
renderMeta.didRenderScripts = true;
|
|
}
|
|
let matches = getActiveMatches(routerMatches, null, isSpaMode);
|
|
React8.useEffect(() => {
|
|
setIsHydrated();
|
|
}, []);
|
|
let initialScripts = React8.useMemo(() => {
|
|
if (isRSCRouterContext) {
|
|
return null;
|
|
}
|
|
let streamScript = "window.__reactRouterContext.stream = new ReadableStream({start(controller){window.__reactRouterContext.streamController = controller;}}).pipeThrough(new TextEncoderStream());";
|
|
let contextScript = staticContext ? `window.__reactRouterContext = ${serverHandoffString};${streamScript}` : " ";
|
|
let routeModulesScript = !isStatic ? " " : `${manifest.hmr?.runtime ? `import ${JSON.stringify(manifest.hmr.runtime)};` : ""}${!enableFogOfWar ? `import ${JSON.stringify(manifest.url)}` : ""};
|
|
${matches.map((match, routeIndex) => {
|
|
let routeVarName = `route${routeIndex}`;
|
|
let manifestEntry = manifest.routes[match.route.id];
|
|
invariant2(manifestEntry, `Route ${match.route.id} not found in manifest`);
|
|
let {
|
|
clientActionModule,
|
|
clientLoaderModule,
|
|
clientMiddlewareModule,
|
|
hydrateFallbackModule,
|
|
module
|
|
} = manifestEntry;
|
|
let chunks = [
|
|
...clientActionModule ? [
|
|
{
|
|
module: clientActionModule,
|
|
varName: `${routeVarName}_clientAction`
|
|
}
|
|
] : [],
|
|
...clientLoaderModule ? [
|
|
{
|
|
module: clientLoaderModule,
|
|
varName: `${routeVarName}_clientLoader`
|
|
}
|
|
] : [],
|
|
...clientMiddlewareModule ? [
|
|
{
|
|
module: clientMiddlewareModule,
|
|
varName: `${routeVarName}_clientMiddleware`
|
|
}
|
|
] : [],
|
|
...hydrateFallbackModule ? [
|
|
{
|
|
module: hydrateFallbackModule,
|
|
varName: `${routeVarName}_HydrateFallback`
|
|
}
|
|
] : [],
|
|
{ module, varName: `${routeVarName}_main` }
|
|
];
|
|
if (chunks.length === 1) {
|
|
return `import * as ${routeVarName} from ${JSON.stringify(module)};`;
|
|
}
|
|
let chunkImportsSnippet = chunks.map((chunk) => `import * as ${chunk.varName} from "${chunk.module}";`).join("\n");
|
|
let mergedChunksSnippet = `const ${routeVarName} = {${chunks.map((chunk) => `...${chunk.varName}`).join(",")}};`;
|
|
return [chunkImportsSnippet, mergedChunksSnippet].join("\n");
|
|
}).join("\n")}
|
|
${enableFogOfWar ? (
|
|
// Inline a minimal manifest with the SSR matches
|
|
`window.__reactRouterManifest = ${JSON.stringify(
|
|
getPartialManifest(manifest, router),
|
|
null,
|
|
2
|
|
)};`
|
|
) : ""}
|
|
window.__reactRouterRouteModules = {${matches.map((match, index) => `${JSON.stringify(match.route.id)}:route${index}`).join(",")}};
|
|
|
|
import(${JSON.stringify(manifest.entry.module)});`;
|
|
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(
|
|
"script",
|
|
{
|
|
...scriptProps,
|
|
suppressHydrationWarning: true,
|
|
dangerouslySetInnerHTML: { __html: contextScript },
|
|
type: void 0
|
|
}
|
|
), /* @__PURE__ */ React8.createElement(
|
|
"script",
|
|
{
|
|
...scriptProps,
|
|
suppressHydrationWarning: true,
|
|
dangerouslySetInnerHTML: { __html: routeModulesScript },
|
|
type: "module",
|
|
async: true
|
|
}
|
|
));
|
|
}, []);
|
|
let preloads = isHydrated || isRSCRouterContext ? [] : dedupe(
|
|
manifest.entry.imports.concat(
|
|
getModuleLinkHrefs(matches, manifest, {
|
|
includeHydrateFallback: true
|
|
})
|
|
)
|
|
);
|
|
let sri = typeof manifest.sri === "object" ? manifest.sri : {};
|
|
warnOnce(
|
|
!isRSCRouterContext,
|
|
"The <Scripts /> element is a no-op when using RSC and can be safely removed."
|
|
);
|
|
return isHydrated || isRSCRouterContext ? null : /* @__PURE__ */ React8.createElement(React8.Fragment, null, typeof manifest.sri === "object" ? /* @__PURE__ */ React8.createElement(
|
|
"script",
|
|
{
|
|
"rr-importmap": "",
|
|
type: "importmap",
|
|
suppressHydrationWarning: true,
|
|
dangerouslySetInnerHTML: {
|
|
__html: JSON.stringify({
|
|
integrity: sri
|
|
})
|
|
}
|
|
}
|
|
) : null, !enableFogOfWar ? /* @__PURE__ */ React8.createElement(
|
|
"link",
|
|
{
|
|
rel: "modulepreload",
|
|
href: manifest.url,
|
|
crossOrigin: scriptProps.crossOrigin,
|
|
integrity: sri[manifest.url],
|
|
suppressHydrationWarning: true
|
|
}
|
|
) : null, /* @__PURE__ */ React8.createElement(
|
|
"link",
|
|
{
|
|
rel: "modulepreload",
|
|
href: manifest.entry.module,
|
|
crossOrigin: scriptProps.crossOrigin,
|
|
integrity: sri[manifest.entry.module],
|
|
suppressHydrationWarning: true
|
|
}
|
|
), preloads.map((path) => /* @__PURE__ */ React8.createElement(
|
|
"link",
|
|
{
|
|
key: path,
|
|
rel: "modulepreload",
|
|
href: path,
|
|
crossOrigin: scriptProps.crossOrigin,
|
|
integrity: sri[path],
|
|
suppressHydrationWarning: true
|
|
}
|
|
)), initialScripts);
|
|
}
|
|
function dedupe(array) {
|
|
return [...new Set(array)];
|
|
}
|
|
function mergeRefs(...refs) {
|
|
return (value) => {
|
|
refs.forEach((ref) => {
|
|
if (typeof ref === "function") {
|
|
ref(value);
|
|
} else if (ref != null) {
|
|
ref.current = value;
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
// lib/dom/ssr/errorBoundaries.tsx
|
|
var RemixErrorBoundary = class extends React9.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = { error: props.error || null, location: props.location };
|
|
}
|
|
static getDerivedStateFromError(error) {
|
|
return { error };
|
|
}
|
|
static getDerivedStateFromProps(props, state) {
|
|
if (state.location !== props.location) {
|
|
return { error: props.error || null, location: props.location };
|
|
}
|
|
return { error: props.error || state.error, location: state.location };
|
|
}
|
|
render() {
|
|
if (this.state.error) {
|
|
return /* @__PURE__ */ React9.createElement(
|
|
RemixRootDefaultErrorBoundary,
|
|
{
|
|
error: this.state.error,
|
|
isOutsideRemixApp: true
|
|
}
|
|
);
|
|
} else {
|
|
return this.props.children;
|
|
}
|
|
}
|
|
};
|
|
function RemixRootDefaultErrorBoundary({
|
|
error,
|
|
isOutsideRemixApp
|
|
}) {
|
|
console.error(error);
|
|
let heyDeveloper = /* @__PURE__ */ React9.createElement(
|
|
"script",
|
|
{
|
|
dangerouslySetInnerHTML: {
|
|
__html: `
|
|
console.log(
|
|
"\u{1F4BF} Hey developer \u{1F44B}. You can provide a way better UX than this when your app throws errors. Check out https://reactrouter.com/how-to/error-boundary for more information."
|
|
);
|
|
`
|
|
}
|
|
}
|
|
);
|
|
if (isRouteErrorResponse(error)) {
|
|
return /* @__PURE__ */ React9.createElement(BoundaryShell, { title: "Unhandled Thrown Response!" }, /* @__PURE__ */ React9.createElement("h1", { style: { fontSize: "24px" } }, error.status, " ", error.statusText), ENABLE_DEV_WARNINGS ? heyDeveloper : null);
|
|
}
|
|
let errorInstance;
|
|
if (error instanceof Error) {
|
|
errorInstance = error;
|
|
} else {
|
|
let errorString = error == null ? "Unknown Error" : typeof error === "object" && "toString" in error ? error.toString() : JSON.stringify(error);
|
|
errorInstance = new Error(errorString);
|
|
}
|
|
return /* @__PURE__ */ React9.createElement(
|
|
BoundaryShell,
|
|
{
|
|
title: "Application Error!",
|
|
isOutsideRemixApp
|
|
},
|
|
/* @__PURE__ */ React9.createElement("h1", { style: { fontSize: "24px" } }, "Application Error"),
|
|
/* @__PURE__ */ React9.createElement(
|
|
"pre",
|
|
{
|
|
style: {
|
|
padding: "2rem",
|
|
background: "hsla(10, 50%, 50%, 0.1)",
|
|
color: "red",
|
|
overflow: "auto"
|
|
}
|
|
},
|
|
errorInstance.stack
|
|
),
|
|
heyDeveloper
|
|
);
|
|
}
|
|
function BoundaryShell({
|
|
title,
|
|
renderScripts,
|
|
isOutsideRemixApp,
|
|
children
|
|
}) {
|
|
let { routeModules } = useFrameworkContext();
|
|
if (routeModules.root?.Layout && !isOutsideRemixApp) {
|
|
return children;
|
|
}
|
|
return /* @__PURE__ */ React9.createElement("html", { lang: "en" }, /* @__PURE__ */ React9.createElement("head", null, /* @__PURE__ */ React9.createElement("meta", { charSet: "utf-8" }), /* @__PURE__ */ React9.createElement(
|
|
"meta",
|
|
{
|
|
name: "viewport",
|
|
content: "width=device-width,initial-scale=1,viewport-fit=cover"
|
|
}
|
|
), /* @__PURE__ */ React9.createElement("title", null, title)), /* @__PURE__ */ React9.createElement("body", null, /* @__PURE__ */ React9.createElement("main", { style: { fontFamily: "system-ui, sans-serif", padding: "2rem" } }, children, renderScripts ? /* @__PURE__ */ React9.createElement(Scripts, null) : null)));
|
|
}
|
|
|
|
// lib/dom/lib.tsx
|
|
import * as React10 from "react";
|
|
var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
|
|
try {
|
|
if (isBrowser) {
|
|
window.__reactRouterVersion = // @ts-expect-error
|
|
"7.9.3";
|
|
}
|
|
} catch (e) {
|
|
}
|
|
function createBrowserRouter(routes, opts) {
|
|
return createRouter({
|
|
basename: opts?.basename,
|
|
getContext: opts?.getContext,
|
|
future: opts?.future,
|
|
history: createBrowserHistory({ window: opts?.window }),
|
|
hydrationData: opts?.hydrationData || parseHydrationData(),
|
|
routes,
|
|
mapRouteProperties,
|
|
hydrationRouteProperties,
|
|
dataStrategy: opts?.dataStrategy,
|
|
patchRoutesOnNavigation: opts?.patchRoutesOnNavigation,
|
|
window: opts?.window
|
|
}).initialize();
|
|
}
|
|
function createHashRouter(routes, opts) {
|
|
return createRouter({
|
|
basename: opts?.basename,
|
|
getContext: opts?.getContext,
|
|
future: opts?.future,
|
|
history: createHashHistory({ window: opts?.window }),
|
|
hydrationData: opts?.hydrationData || parseHydrationData(),
|
|
routes,
|
|
mapRouteProperties,
|
|
hydrationRouteProperties,
|
|
dataStrategy: opts?.dataStrategy,
|
|
patchRoutesOnNavigation: opts?.patchRoutesOnNavigation,
|
|
window: opts?.window
|
|
}).initialize();
|
|
}
|
|
function parseHydrationData() {
|
|
let state = window?.__staticRouterHydrationData;
|
|
if (state && state.errors) {
|
|
state = {
|
|
...state,
|
|
errors: deserializeErrors(state.errors)
|
|
};
|
|
}
|
|
return state;
|
|
}
|
|
function deserializeErrors(errors) {
|
|
if (!errors) return null;
|
|
let entries = Object.entries(errors);
|
|
let serialized = {};
|
|
for (let [key, val] of entries) {
|
|
if (val && val.__type === "RouteErrorResponse") {
|
|
serialized[key] = new ErrorResponseImpl(
|
|
val.status,
|
|
val.statusText,
|
|
val.data,
|
|
val.internal === true
|
|
);
|
|
} else if (val && val.__type === "Error") {
|
|
if (val.__subType) {
|
|
let ErrorConstructor = window[val.__subType];
|
|
if (typeof ErrorConstructor === "function") {
|
|
try {
|
|
let error = new ErrorConstructor(val.message);
|
|
error.stack = "";
|
|
serialized[key] = error;
|
|
} catch (e) {
|
|
}
|
|
}
|
|
}
|
|
if (serialized[key] == null) {
|
|
let error = new Error(val.message);
|
|
error.stack = "";
|
|
serialized[key] = error;
|
|
}
|
|
} else {
|
|
serialized[key] = val;
|
|
}
|
|
}
|
|
return serialized;
|
|
}
|
|
function BrowserRouter({
|
|
basename,
|
|
children,
|
|
window: window2
|
|
}) {
|
|
let historyRef = React10.useRef();
|
|
if (historyRef.current == null) {
|
|
historyRef.current = createBrowserHistory({ window: window2, v5Compat: true });
|
|
}
|
|
let history = historyRef.current;
|
|
let [state, setStateImpl] = React10.useState({
|
|
action: history.action,
|
|
location: history.location
|
|
});
|
|
let setState = React10.useCallback(
|
|
(newState) => {
|
|
React10.startTransition(() => setStateImpl(newState));
|
|
},
|
|
[setStateImpl]
|
|
);
|
|
React10.useLayoutEffect(() => history.listen(setState), [history, setState]);
|
|
return /* @__PURE__ */ React10.createElement(
|
|
Router,
|
|
{
|
|
basename,
|
|
children,
|
|
location: state.location,
|
|
navigationType: state.action,
|
|
navigator: history
|
|
}
|
|
);
|
|
}
|
|
function HashRouter({ basename, children, window: window2 }) {
|
|
let historyRef = React10.useRef();
|
|
if (historyRef.current == null) {
|
|
historyRef.current = createHashHistory({ window: window2, v5Compat: true });
|
|
}
|
|
let history = historyRef.current;
|
|
let [state, setStateImpl] = React10.useState({
|
|
action: history.action,
|
|
location: history.location
|
|
});
|
|
let setState = React10.useCallback(
|
|
(newState) => {
|
|
React10.startTransition(() => setStateImpl(newState));
|
|
},
|
|
[setStateImpl]
|
|
);
|
|
React10.useLayoutEffect(() => history.listen(setState), [history, setState]);
|
|
return /* @__PURE__ */ React10.createElement(
|
|
Router,
|
|
{
|
|
basename,
|
|
children,
|
|
location: state.location,
|
|
navigationType: state.action,
|
|
navigator: history
|
|
}
|
|
);
|
|
}
|
|
function HistoryRouter({
|
|
basename,
|
|
children,
|
|
history
|
|
}) {
|
|
let [state, setStateImpl] = React10.useState({
|
|
action: history.action,
|
|
location: history.location
|
|
});
|
|
let setState = React10.useCallback(
|
|
(newState) => {
|
|
React10.startTransition(() => setStateImpl(newState));
|
|
},
|
|
[setStateImpl]
|
|
);
|
|
React10.useLayoutEffect(() => history.listen(setState), [history, setState]);
|
|
return /* @__PURE__ */ React10.createElement(
|
|
Router,
|
|
{
|
|
basename,
|
|
children,
|
|
location: state.location,
|
|
navigationType: state.action,
|
|
navigator: history
|
|
}
|
|
);
|
|
}
|
|
HistoryRouter.displayName = "unstable_HistoryRouter";
|
|
var ABSOLUTE_URL_REGEX2 = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
var Link = React10.forwardRef(
|
|
function LinkWithRef({
|
|
onClick,
|
|
discover = "render",
|
|
prefetch = "none",
|
|
relative,
|
|
reloadDocument,
|
|
replace: replace2,
|
|
state,
|
|
target,
|
|
to,
|
|
preventScrollReset,
|
|
viewTransition,
|
|
...rest
|
|
}, forwardedRef) {
|
|
let { basename } = React10.useContext(NavigationContext);
|
|
let isAbsolute = typeof to === "string" && ABSOLUTE_URL_REGEX2.test(to);
|
|
let absoluteHref;
|
|
let isExternal = false;
|
|
if (typeof to === "string" && isAbsolute) {
|
|
absoluteHref = to;
|
|
if (isBrowser) {
|
|
try {
|
|
let currentUrl = new URL(window.location.href);
|
|
let targetUrl = to.startsWith("//") ? new URL(currentUrl.protocol + to) : new URL(to);
|
|
let path = stripBasename(targetUrl.pathname, basename);
|
|
if (targetUrl.origin === currentUrl.origin && path != null) {
|
|
to = path + targetUrl.search + targetUrl.hash;
|
|
} else {
|
|
isExternal = true;
|
|
}
|
|
} catch (e) {
|
|
warning(
|
|
false,
|
|
`<Link to="${to}"> contains an invalid URL which will probably break when clicked - please update to a valid URL path.`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
let href = useHref(to, { relative });
|
|
let [shouldPrefetch, prefetchRef, prefetchHandlers] = usePrefetchBehavior(
|
|
prefetch,
|
|
rest
|
|
);
|
|
let internalOnClick = useLinkClickHandler(to, {
|
|
replace: replace2,
|
|
state,
|
|
target,
|
|
preventScrollReset,
|
|
relative,
|
|
viewTransition
|
|
});
|
|
function handleClick(event) {
|
|
if (onClick) onClick(event);
|
|
if (!event.defaultPrevented) {
|
|
internalOnClick(event);
|
|
}
|
|
}
|
|
let link = (
|
|
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
|
/* @__PURE__ */ React10.createElement(
|
|
"a",
|
|
{
|
|
...rest,
|
|
...prefetchHandlers,
|
|
href: absoluteHref || href,
|
|
onClick: isExternal || reloadDocument ? onClick : handleClick,
|
|
ref: mergeRefs(forwardedRef, prefetchRef),
|
|
target,
|
|
"data-discover": !isAbsolute && discover === "render" ? "true" : void 0
|
|
}
|
|
)
|
|
);
|
|
return shouldPrefetch && !isAbsolute ? /* @__PURE__ */ React10.createElement(React10.Fragment, null, link, /* @__PURE__ */ React10.createElement(PrefetchPageLinks, { page: href })) : link;
|
|
}
|
|
);
|
|
Link.displayName = "Link";
|
|
var NavLink = React10.forwardRef(
|
|
function NavLinkWithRef({
|
|
"aria-current": ariaCurrentProp = "page",
|
|
caseSensitive = false,
|
|
className: classNameProp = "",
|
|
end = false,
|
|
style: styleProp,
|
|
to,
|
|
viewTransition,
|
|
children,
|
|
...rest
|
|
}, ref) {
|
|
let path = useResolvedPath(to, { relative: rest.relative });
|
|
let location = useLocation();
|
|
let routerState = React10.useContext(DataRouterStateContext);
|
|
let { navigator, basename } = React10.useContext(NavigationContext);
|
|
let isTransitioning = routerState != null && // Conditional usage is OK here because the usage of a data router is static
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
useViewTransitionState(path) && viewTransition === true;
|
|
let toPathname = navigator.encodeLocation ? navigator.encodeLocation(path).pathname : path.pathname;
|
|
let locationPathname = location.pathname;
|
|
let nextLocationPathname = routerState && routerState.navigation && routerState.navigation.location ? routerState.navigation.location.pathname : null;
|
|
if (!caseSensitive) {
|
|
locationPathname = locationPathname.toLowerCase();
|
|
nextLocationPathname = nextLocationPathname ? nextLocationPathname.toLowerCase() : null;
|
|
toPathname = toPathname.toLowerCase();
|
|
}
|
|
if (nextLocationPathname && basename) {
|
|
nextLocationPathname = stripBasename(nextLocationPathname, basename) || nextLocationPathname;
|
|
}
|
|
const endSlashPosition = toPathname !== "/" && toPathname.endsWith("/") ? toPathname.length - 1 : toPathname.length;
|
|
let isActive = locationPathname === toPathname || !end && locationPathname.startsWith(toPathname) && locationPathname.charAt(endSlashPosition) === "/";
|
|
let isPending = nextLocationPathname != null && (nextLocationPathname === toPathname || !end && nextLocationPathname.startsWith(toPathname) && nextLocationPathname.charAt(toPathname.length) === "/");
|
|
let renderProps = {
|
|
isActive,
|
|
isPending,
|
|
isTransitioning
|
|
};
|
|
let ariaCurrent = isActive ? ariaCurrentProp : void 0;
|
|
let className;
|
|
if (typeof classNameProp === "function") {
|
|
className = classNameProp(renderProps);
|
|
} else {
|
|
className = [
|
|
classNameProp,
|
|
isActive ? "active" : null,
|
|
isPending ? "pending" : null,
|
|
isTransitioning ? "transitioning" : null
|
|
].filter(Boolean).join(" ");
|
|
}
|
|
let style = typeof styleProp === "function" ? styleProp(renderProps) : styleProp;
|
|
return /* @__PURE__ */ React10.createElement(
|
|
Link,
|
|
{
|
|
...rest,
|
|
"aria-current": ariaCurrent,
|
|
className,
|
|
ref,
|
|
style,
|
|
to,
|
|
viewTransition
|
|
},
|
|
typeof children === "function" ? children(renderProps) : children
|
|
);
|
|
}
|
|
);
|
|
NavLink.displayName = "NavLink";
|
|
var Form = React10.forwardRef(
|
|
({
|
|
discover = "render",
|
|
fetcherKey,
|
|
navigate,
|
|
reloadDocument,
|
|
replace: replace2,
|
|
state,
|
|
method = defaultMethod,
|
|
action,
|
|
onSubmit,
|
|
relative,
|
|
preventScrollReset,
|
|
viewTransition,
|
|
...props
|
|
}, forwardedRef) => {
|
|
let submit = useSubmit();
|
|
let formAction = useFormAction(action, { relative });
|
|
let formMethod = method.toLowerCase() === "get" ? "get" : "post";
|
|
let isAbsolute = typeof action === "string" && ABSOLUTE_URL_REGEX2.test(action);
|
|
let submitHandler = (event) => {
|
|
onSubmit && onSubmit(event);
|
|
if (event.defaultPrevented) return;
|
|
event.preventDefault();
|
|
let submitter = event.nativeEvent.submitter;
|
|
let submitMethod = submitter?.getAttribute("formmethod") || method;
|
|
submit(submitter || event.currentTarget, {
|
|
fetcherKey,
|
|
method: submitMethod,
|
|
navigate,
|
|
replace: replace2,
|
|
state,
|
|
relative,
|
|
preventScrollReset,
|
|
viewTransition
|
|
});
|
|
};
|
|
return /* @__PURE__ */ React10.createElement(
|
|
"form",
|
|
{
|
|
ref: forwardedRef,
|
|
method: formMethod,
|
|
action: formAction,
|
|
onSubmit: reloadDocument ? onSubmit : submitHandler,
|
|
...props,
|
|
"data-discover": !isAbsolute && discover === "render" ? "true" : void 0
|
|
}
|
|
);
|
|
}
|
|
);
|
|
Form.displayName = "Form";
|
|
function ScrollRestoration({
|
|
getKey,
|
|
storageKey,
|
|
...props
|
|
}) {
|
|
let remixContext = React10.useContext(FrameworkContext);
|
|
let { basename } = React10.useContext(NavigationContext);
|
|
let location = useLocation();
|
|
let matches = useMatches();
|
|
useScrollRestoration({ getKey, storageKey });
|
|
let ssrKey = React10.useMemo(
|
|
() => {
|
|
if (!remixContext || !getKey) return null;
|
|
let userKey = getScrollRestorationKey(
|
|
location,
|
|
matches,
|
|
basename,
|
|
getKey
|
|
);
|
|
return userKey !== location.key ? userKey : null;
|
|
},
|
|
// Nah, we only need this the first time for the SSR render
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[]
|
|
);
|
|
if (!remixContext || remixContext.isSpaMode) {
|
|
return null;
|
|
}
|
|
let restoreScroll = ((storageKey2, restoreKey) => {
|
|
if (!window.history.state || !window.history.state.key) {
|
|
let key = Math.random().toString(32).slice(2);
|
|
window.history.replaceState({ key }, "");
|
|
}
|
|
try {
|
|
let positions = JSON.parse(sessionStorage.getItem(storageKey2) || "{}");
|
|
let storedY = positions[restoreKey || window.history.state.key];
|
|
if (typeof storedY === "number") {
|
|
window.scrollTo(0, storedY);
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
sessionStorage.removeItem(storageKey2);
|
|
}
|
|
}).toString();
|
|
return /* @__PURE__ */ React10.createElement(
|
|
"script",
|
|
{
|
|
...props,
|
|
suppressHydrationWarning: true,
|
|
dangerouslySetInnerHTML: {
|
|
__html: `(${restoreScroll})(${JSON.stringify(
|
|
storageKey || SCROLL_RESTORATION_STORAGE_KEY
|
|
)}, ${JSON.stringify(ssrKey)})`
|
|
}
|
|
}
|
|
);
|
|
}
|
|
ScrollRestoration.displayName = "ScrollRestoration";
|
|
function getDataRouterConsoleError2(hookName) {
|
|
return `${hookName} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`;
|
|
}
|
|
function useDataRouterContext3(hookName) {
|
|
let ctx = React10.useContext(DataRouterContext);
|
|
invariant(ctx, getDataRouterConsoleError2(hookName));
|
|
return ctx;
|
|
}
|
|
function useDataRouterState2(hookName) {
|
|
let state = React10.useContext(DataRouterStateContext);
|
|
invariant(state, getDataRouterConsoleError2(hookName));
|
|
return state;
|
|
}
|
|
function useLinkClickHandler(to, {
|
|
target,
|
|
replace: replaceProp,
|
|
state,
|
|
preventScrollReset,
|
|
relative,
|
|
viewTransition
|
|
} = {}) {
|
|
let navigate = useNavigate();
|
|
let location = useLocation();
|
|
let path = useResolvedPath(to, { relative });
|
|
return React10.useCallback(
|
|
(event) => {
|
|
if (shouldProcessLinkClick(event, target)) {
|
|
event.preventDefault();
|
|
let replace2 = replaceProp !== void 0 ? replaceProp : createPath(location) === createPath(path);
|
|
navigate(to, {
|
|
replace: replace2,
|
|
state,
|
|
preventScrollReset,
|
|
relative,
|
|
viewTransition
|
|
});
|
|
}
|
|
},
|
|
[
|
|
location,
|
|
navigate,
|
|
path,
|
|
replaceProp,
|
|
state,
|
|
target,
|
|
to,
|
|
preventScrollReset,
|
|
relative,
|
|
viewTransition
|
|
]
|
|
);
|
|
}
|
|
function useSearchParams(defaultInit) {
|
|
warning(
|
|
typeof URLSearchParams !== "undefined",
|
|
`You cannot use the \`useSearchParams\` hook in a browser that does not support the URLSearchParams API. If you need to support Internet Explorer 11, we recommend you load a polyfill such as https://github.com/ungap/url-search-params.`
|
|
);
|
|
let defaultSearchParamsRef = React10.useRef(createSearchParams(defaultInit));
|
|
let hasSetSearchParamsRef = React10.useRef(false);
|
|
let location = useLocation();
|
|
let searchParams = React10.useMemo(
|
|
() => (
|
|
// Only merge in the defaults if we haven't yet called setSearchParams.
|
|
// Once we call that we want those to take precedence, otherwise you can't
|
|
// remove a param with setSearchParams({}) if it has an initial value
|
|
getSearchParamsForLocation(
|
|
location.search,
|
|
hasSetSearchParamsRef.current ? null : defaultSearchParamsRef.current
|
|
)
|
|
),
|
|
[location.search]
|
|
);
|
|
let navigate = useNavigate();
|
|
let setSearchParams = React10.useCallback(
|
|
(nextInit, navigateOptions) => {
|
|
const newSearchParams = createSearchParams(
|
|
typeof nextInit === "function" ? nextInit(new URLSearchParams(searchParams)) : nextInit
|
|
);
|
|
hasSetSearchParamsRef.current = true;
|
|
navigate("?" + newSearchParams, navigateOptions);
|
|
},
|
|
[navigate, searchParams]
|
|
);
|
|
return [searchParams, setSearchParams];
|
|
}
|
|
var fetcherId = 0;
|
|
var getUniqueFetcherId = () => `__${String(++fetcherId)}__`;
|
|
function useSubmit() {
|
|
let { router } = useDataRouterContext3("useSubmit" /* UseSubmit */);
|
|
let { basename } = React10.useContext(NavigationContext);
|
|
let currentRouteId = useRouteId();
|
|
return React10.useCallback(
|
|
async (target, options = {}) => {
|
|
let { action, method, encType, formData, body } = getFormSubmissionInfo(
|
|
target,
|
|
basename
|
|
);
|
|
if (options.navigate === false) {
|
|
let key = options.fetcherKey || getUniqueFetcherId();
|
|
await router.fetch(key, currentRouteId, options.action || action, {
|
|
preventScrollReset: options.preventScrollReset,
|
|
formData,
|
|
body,
|
|
formMethod: options.method || method,
|
|
formEncType: options.encType || encType,
|
|
flushSync: options.flushSync
|
|
});
|
|
} else {
|
|
await router.navigate(options.action || action, {
|
|
preventScrollReset: options.preventScrollReset,
|
|
formData,
|
|
body,
|
|
formMethod: options.method || method,
|
|
formEncType: options.encType || encType,
|
|
replace: options.replace,
|
|
state: options.state,
|
|
fromRouteId: currentRouteId,
|
|
flushSync: options.flushSync,
|
|
viewTransition: options.viewTransition
|
|
});
|
|
}
|
|
},
|
|
[router, basename, currentRouteId]
|
|
);
|
|
}
|
|
function useFormAction(action, { relative } = {}) {
|
|
let { basename } = React10.useContext(NavigationContext);
|
|
let routeContext = React10.useContext(RouteContext);
|
|
invariant(routeContext, "useFormAction must be used inside a RouteContext");
|
|
let [match] = routeContext.matches.slice(-1);
|
|
let path = { ...useResolvedPath(action ? action : ".", { relative }) };
|
|
let location = useLocation();
|
|
if (action == null) {
|
|
path.search = location.search;
|
|
let params = new URLSearchParams(path.search);
|
|
let indexValues = params.getAll("index");
|
|
let hasNakedIndexParam = indexValues.some((v) => v === "");
|
|
if (hasNakedIndexParam) {
|
|
params.delete("index");
|
|
indexValues.filter((v) => v).forEach((v) => params.append("index", v));
|
|
let qs = params.toString();
|
|
path.search = qs ? `?${qs}` : "";
|
|
}
|
|
}
|
|
if ((!action || action === ".") && match.route.index) {
|
|
path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
|
|
}
|
|
if (basename !== "/") {
|
|
path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
|
|
}
|
|
return createPath(path);
|
|
}
|
|
function useFetcher({
|
|
key
|
|
} = {}) {
|
|
let { router } = useDataRouterContext3("useFetcher" /* UseFetcher */);
|
|
let state = useDataRouterState2("useFetcher" /* UseFetcher */);
|
|
let fetcherData = React10.useContext(FetchersContext);
|
|
let route = React10.useContext(RouteContext);
|
|
let routeId = route.matches[route.matches.length - 1]?.route.id;
|
|
invariant(fetcherData, `useFetcher must be used inside a FetchersContext`);
|
|
invariant(route, `useFetcher must be used inside a RouteContext`);
|
|
invariant(
|
|
routeId != null,
|
|
`useFetcher can only be used on routes that contain a unique "id"`
|
|
);
|
|
let defaultKey = React10.useId();
|
|
let [fetcherKey, setFetcherKey] = React10.useState(key || defaultKey);
|
|
if (key && key !== fetcherKey) {
|
|
setFetcherKey(key);
|
|
}
|
|
React10.useEffect(() => {
|
|
router.getFetcher(fetcherKey);
|
|
return () => router.deleteFetcher(fetcherKey);
|
|
}, [router, fetcherKey]);
|
|
let load = React10.useCallback(
|
|
async (href, opts) => {
|
|
invariant(routeId, "No routeId available for fetcher.load()");
|
|
await router.fetch(fetcherKey, routeId, href, opts);
|
|
},
|
|
[fetcherKey, routeId, router]
|
|
);
|
|
let submitImpl = useSubmit();
|
|
let submit = React10.useCallback(
|
|
async (target, opts) => {
|
|
await submitImpl(target, {
|
|
...opts,
|
|
navigate: false,
|
|
fetcherKey
|
|
});
|
|
},
|
|
[fetcherKey, submitImpl]
|
|
);
|
|
let unstable_reset = React10.useCallback((opts) => router.resetFetcher(fetcherKey, opts), [router, fetcherKey]);
|
|
let FetcherForm = React10.useMemo(() => {
|
|
let FetcherForm2 = React10.forwardRef(
|
|
(props, ref) => {
|
|
return /* @__PURE__ */ React10.createElement(Form, { ...props, navigate: false, fetcherKey, ref });
|
|
}
|
|
);
|
|
FetcherForm2.displayName = "fetcher.Form";
|
|
return FetcherForm2;
|
|
}, [fetcherKey]);
|
|
let fetcher = state.fetchers.get(fetcherKey) || IDLE_FETCHER;
|
|
let data2 = fetcherData.get(fetcherKey);
|
|
let fetcherWithComponents = React10.useMemo(
|
|
() => ({
|
|
Form: FetcherForm,
|
|
submit,
|
|
load,
|
|
unstable_reset,
|
|
...fetcher,
|
|
data: data2
|
|
}),
|
|
[FetcherForm, submit, load, unstable_reset, fetcher, data2]
|
|
);
|
|
return fetcherWithComponents;
|
|
}
|
|
function useFetchers() {
|
|
let state = useDataRouterState2("useFetchers" /* UseFetchers */);
|
|
return Array.from(state.fetchers.entries()).map(([key, fetcher]) => ({
|
|
...fetcher,
|
|
key
|
|
}));
|
|
}
|
|
var SCROLL_RESTORATION_STORAGE_KEY = "react-router-scroll-positions";
|
|
var savedScrollPositions = {};
|
|
function getScrollRestorationKey(location, matches, basename, getKey) {
|
|
let key = null;
|
|
if (getKey) {
|
|
if (basename !== "/") {
|
|
key = getKey(
|
|
{
|
|
...location,
|
|
pathname: stripBasename(location.pathname, basename) || location.pathname
|
|
},
|
|
matches
|
|
);
|
|
} else {
|
|
key = getKey(location, matches);
|
|
}
|
|
}
|
|
if (key == null) {
|
|
key = location.key;
|
|
}
|
|
return key;
|
|
}
|
|
function useScrollRestoration({
|
|
getKey,
|
|
storageKey
|
|
} = {}) {
|
|
let { router } = useDataRouterContext3("useScrollRestoration" /* UseScrollRestoration */);
|
|
let { restoreScrollPosition, preventScrollReset } = useDataRouterState2(
|
|
"useScrollRestoration" /* UseScrollRestoration */
|
|
);
|
|
let { basename } = React10.useContext(NavigationContext);
|
|
let location = useLocation();
|
|
let matches = useMatches();
|
|
let navigation = useNavigation();
|
|
React10.useEffect(() => {
|
|
window.history.scrollRestoration = "manual";
|
|
return () => {
|
|
window.history.scrollRestoration = "auto";
|
|
};
|
|
}, []);
|
|
usePageHide(
|
|
React10.useCallback(() => {
|
|
if (navigation.state === "idle") {
|
|
let key = getScrollRestorationKey(location, matches, basename, getKey);
|
|
savedScrollPositions[key] = window.scrollY;
|
|
}
|
|
try {
|
|
sessionStorage.setItem(
|
|
storageKey || SCROLL_RESTORATION_STORAGE_KEY,
|
|
JSON.stringify(savedScrollPositions)
|
|
);
|
|
} catch (error) {
|
|
warning(
|
|
false,
|
|
`Failed to save scroll positions in sessionStorage, <ScrollRestoration /> will not work properly (${error}).`
|
|
);
|
|
}
|
|
window.history.scrollRestoration = "auto";
|
|
}, [navigation.state, getKey, basename, location, matches, storageKey])
|
|
);
|
|
if (typeof document !== "undefined") {
|
|
React10.useLayoutEffect(() => {
|
|
try {
|
|
let sessionPositions = sessionStorage.getItem(
|
|
storageKey || SCROLL_RESTORATION_STORAGE_KEY
|
|
);
|
|
if (sessionPositions) {
|
|
savedScrollPositions = JSON.parse(sessionPositions);
|
|
}
|
|
} catch (e) {
|
|
}
|
|
}, [storageKey]);
|
|
React10.useLayoutEffect(() => {
|
|
let disableScrollRestoration = router?.enableScrollRestoration(
|
|
savedScrollPositions,
|
|
() => window.scrollY,
|
|
getKey ? (location2, matches2) => getScrollRestorationKey(location2, matches2, basename, getKey) : void 0
|
|
);
|
|
return () => disableScrollRestoration && disableScrollRestoration();
|
|
}, [router, basename, getKey]);
|
|
React10.useLayoutEffect(() => {
|
|
if (restoreScrollPosition === false) {
|
|
return;
|
|
}
|
|
if (typeof restoreScrollPosition === "number") {
|
|
window.scrollTo(0, restoreScrollPosition);
|
|
return;
|
|
}
|
|
try {
|
|
if (location.hash) {
|
|
let el = document.getElementById(
|
|
decodeURIComponent(location.hash.slice(1))
|
|
);
|
|
if (el) {
|
|
el.scrollIntoView();
|
|
return;
|
|
}
|
|
}
|
|
} catch {
|
|
warning(
|
|
false,
|
|
`"${location.hash.slice(
|
|
1
|
|
)}" is not a decodable element ID. The view will not scroll to it.`
|
|
);
|
|
}
|
|
if (preventScrollReset === true) {
|
|
return;
|
|
}
|
|
window.scrollTo(0, 0);
|
|
}, [location, restoreScrollPosition, preventScrollReset]);
|
|
}
|
|
}
|
|
function useBeforeUnload(callback, options) {
|
|
let { capture } = options || {};
|
|
React10.useEffect(() => {
|
|
let opts = capture != null ? { capture } : void 0;
|
|
window.addEventListener("beforeunload", callback, opts);
|
|
return () => {
|
|
window.removeEventListener("beforeunload", callback, opts);
|
|
};
|
|
}, [callback, capture]);
|
|
}
|
|
function usePageHide(callback, options) {
|
|
let { capture } = options || {};
|
|
React10.useEffect(() => {
|
|
let opts = capture != null ? { capture } : void 0;
|
|
window.addEventListener("pagehide", callback, opts);
|
|
return () => {
|
|
window.removeEventListener("pagehide", callback, opts);
|
|
};
|
|
}, [callback, capture]);
|
|
}
|
|
function usePrompt({
|
|
when,
|
|
message
|
|
}) {
|
|
let blocker = useBlocker(when);
|
|
React10.useEffect(() => {
|
|
if (blocker.state === "blocked") {
|
|
let proceed = window.confirm(message);
|
|
if (proceed) {
|
|
setTimeout(blocker.proceed, 0);
|
|
} else {
|
|
blocker.reset();
|
|
}
|
|
}
|
|
}, [blocker, message]);
|
|
React10.useEffect(() => {
|
|
if (blocker.state === "blocked" && !when) {
|
|
blocker.reset();
|
|
}
|
|
}, [blocker, when]);
|
|
}
|
|
function useViewTransitionState(to, { relative } = {}) {
|
|
let vtContext = React10.useContext(ViewTransitionContext);
|
|
invariant(
|
|
vtContext != null,
|
|
"`useViewTransitionState` must be used within `react-router-dom`'s `RouterProvider`. Did you accidentally import `RouterProvider` from `react-router`?"
|
|
);
|
|
let { basename } = useDataRouterContext3(
|
|
"useViewTransitionState" /* useViewTransitionState */
|
|
);
|
|
let path = useResolvedPath(to, { relative });
|
|
if (!vtContext.isTransitioning) {
|
|
return false;
|
|
}
|
|
let currentPath = stripBasename(vtContext.currentLocation.pathname, basename) || vtContext.currentLocation.pathname;
|
|
let nextPath = stripBasename(vtContext.nextLocation.pathname, basename) || vtContext.nextLocation.pathname;
|
|
return matchPath(path.pathname, nextPath) != null || matchPath(path.pathname, currentPath) != null;
|
|
}
|
|
|
|
// lib/dom/server.tsx
|
|
import * as React11 from "react";
|
|
function StaticRouter({
|
|
basename,
|
|
children,
|
|
location: locationProp = "/"
|
|
}) {
|
|
if (typeof locationProp === "string") {
|
|
locationProp = parsePath(locationProp);
|
|
}
|
|
let action = "POP" /* Pop */;
|
|
let location = {
|
|
pathname: locationProp.pathname || "/",
|
|
search: locationProp.search || "",
|
|
hash: locationProp.hash || "",
|
|
state: locationProp.state != null ? locationProp.state : null,
|
|
key: locationProp.key || "default"
|
|
};
|
|
let staticNavigator = getStatelessNavigator();
|
|
return /* @__PURE__ */ React11.createElement(
|
|
Router,
|
|
{
|
|
basename,
|
|
children,
|
|
location,
|
|
navigationType: action,
|
|
navigator: staticNavigator,
|
|
static: true
|
|
}
|
|
);
|
|
}
|
|
function StaticRouterProvider({
|
|
context,
|
|
router,
|
|
hydrate: hydrate2 = true,
|
|
nonce
|
|
}) {
|
|
invariant(
|
|
router && context,
|
|
"You must provide `router` and `context` to <StaticRouterProvider>"
|
|
);
|
|
let dataRouterContext = {
|
|
router,
|
|
navigator: getStatelessNavigator(),
|
|
static: true,
|
|
staticContext: context,
|
|
basename: context.basename || "/"
|
|
};
|
|
let fetchersContext = /* @__PURE__ */ new Map();
|
|
let hydrateScript = "";
|
|
if (hydrate2 !== false) {
|
|
let data2 = {
|
|
loaderData: context.loaderData,
|
|
actionData: context.actionData,
|
|
errors: serializeErrors(context.errors)
|
|
};
|
|
let json = htmlEscape(JSON.stringify(JSON.stringify(data2)));
|
|
hydrateScript = `window.__staticRouterHydrationData = JSON.parse(${json});`;
|
|
}
|
|
let { state } = dataRouterContext.router;
|
|
return /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(DataRouterContext.Provider, { value: dataRouterContext }, /* @__PURE__ */ React11.createElement(DataRouterStateContext.Provider, { value: state }, /* @__PURE__ */ React11.createElement(FetchersContext.Provider, { value: fetchersContext }, /* @__PURE__ */ React11.createElement(ViewTransitionContext.Provider, { value: { isTransitioning: false } }, /* @__PURE__ */ React11.createElement(
|
|
Router,
|
|
{
|
|
basename: dataRouterContext.basename,
|
|
location: state.location,
|
|
navigationType: state.historyAction,
|
|
navigator: dataRouterContext.navigator,
|
|
static: dataRouterContext.static
|
|
},
|
|
/* @__PURE__ */ React11.createElement(
|
|
DataRoutes2,
|
|
{
|
|
routes: router.routes,
|
|
future: router.future,
|
|
state
|
|
}
|
|
)
|
|
))))), hydrateScript ? /* @__PURE__ */ React11.createElement(
|
|
"script",
|
|
{
|
|
suppressHydrationWarning: true,
|
|
nonce,
|
|
dangerouslySetInnerHTML: { __html: hydrateScript }
|
|
}
|
|
) : null);
|
|
}
|
|
function DataRoutes2({
|
|
routes,
|
|
future,
|
|
state
|
|
}) {
|
|
return useRoutesImpl(routes, void 0, state, void 0, future);
|
|
}
|
|
function serializeErrors(errors) {
|
|
if (!errors) return null;
|
|
let entries = Object.entries(errors);
|
|
let serialized = {};
|
|
for (let [key, val] of entries) {
|
|
if (isRouteErrorResponse(val)) {
|
|
serialized[key] = { ...val, __type: "RouteErrorResponse" };
|
|
} else if (val instanceof Error) {
|
|
serialized[key] = {
|
|
message: val.message,
|
|
__type: "Error",
|
|
// If this is a subclass (i.e., ReferenceError), send up the type so we
|
|
// can re-create the same type during hydration.
|
|
...val.name !== "Error" ? {
|
|
__subType: val.name
|
|
} : {}
|
|
};
|
|
} else {
|
|
serialized[key] = val;
|
|
}
|
|
}
|
|
return serialized;
|
|
}
|
|
function getStatelessNavigator() {
|
|
return {
|
|
createHref,
|
|
encodeLocation,
|
|
push(to) {
|
|
throw new Error(
|
|
`You cannot use navigator.push() on the server because it is a stateless environment. This error was probably triggered when you did a \`navigate(${JSON.stringify(to)})\` somewhere in your app.`
|
|
);
|
|
},
|
|
replace(to) {
|
|
throw new Error(
|
|
`You cannot use navigator.replace() on the server because it is a stateless environment. This error was probably triggered when you did a \`navigate(${JSON.stringify(to)}, { replace: true })\` somewhere in your app.`
|
|
);
|
|
},
|
|
go(delta) {
|
|
throw new Error(
|
|
`You cannot use navigator.go() on the server because it is a stateless environment. This error was probably triggered when you did a \`navigate(${delta})\` somewhere in your app.`
|
|
);
|
|
},
|
|
back() {
|
|
throw new Error(
|
|
`You cannot use navigator.back() on the server because it is a stateless environment.`
|
|
);
|
|
},
|
|
forward() {
|
|
throw new Error(
|
|
`You cannot use navigator.forward() on the server because it is a stateless environment.`
|
|
);
|
|
}
|
|
};
|
|
}
|
|
function createStaticHandler2(routes, opts) {
|
|
return createStaticHandler(routes, {
|
|
...opts,
|
|
mapRouteProperties
|
|
});
|
|
}
|
|
function createStaticRouter(routes, context, opts = {}) {
|
|
let manifest = {};
|
|
let dataRoutes = convertRoutesToDataRoutes(
|
|
routes,
|
|
mapRouteProperties,
|
|
void 0,
|
|
manifest
|
|
);
|
|
let matches = context.matches.map((match) => {
|
|
let route = manifest[match.route.id] || match.route;
|
|
return {
|
|
...match,
|
|
route
|
|
};
|
|
});
|
|
let msg = (method) => `You cannot use router.${method}() on the server because it is a stateless environment`;
|
|
return {
|
|
get basename() {
|
|
return context.basename;
|
|
},
|
|
get future() {
|
|
return {
|
|
v8_middleware: false,
|
|
...opts?.future
|
|
};
|
|
},
|
|
get state() {
|
|
return {
|
|
historyAction: "POP" /* Pop */,
|
|
location: context.location,
|
|
matches,
|
|
loaderData: context.loaderData,
|
|
actionData: context.actionData,
|
|
errors: context.errors,
|
|
initialized: true,
|
|
navigation: IDLE_NAVIGATION,
|
|
restoreScrollPosition: null,
|
|
preventScrollReset: false,
|
|
revalidation: "idle",
|
|
fetchers: /* @__PURE__ */ new Map(),
|
|
blockers: /* @__PURE__ */ new Map()
|
|
};
|
|
},
|
|
get routes() {
|
|
return dataRoutes;
|
|
},
|
|
get window() {
|
|
return void 0;
|
|
},
|
|
initialize() {
|
|
throw msg("initialize");
|
|
},
|
|
subscribe() {
|
|
throw msg("subscribe");
|
|
},
|
|
enableScrollRestoration() {
|
|
throw msg("enableScrollRestoration");
|
|
},
|
|
navigate() {
|
|
throw msg("navigate");
|
|
},
|
|
fetch() {
|
|
throw msg("fetch");
|
|
},
|
|
revalidate() {
|
|
throw msg("revalidate");
|
|
},
|
|
createHref,
|
|
encodeLocation,
|
|
getFetcher() {
|
|
return IDLE_FETCHER;
|
|
},
|
|
deleteFetcher() {
|
|
throw msg("deleteFetcher");
|
|
},
|
|
resetFetcher() {
|
|
throw msg("resetFetcher");
|
|
},
|
|
dispose() {
|
|
throw msg("dispose");
|
|
},
|
|
getBlocker() {
|
|
return IDLE_BLOCKER;
|
|
},
|
|
deleteBlocker() {
|
|
throw msg("deleteBlocker");
|
|
},
|
|
patchRoutes() {
|
|
throw msg("patchRoutes");
|
|
},
|
|
_internalFetchControllers: /* @__PURE__ */ new Map(),
|
|
_internalSetRoutes() {
|
|
throw msg("_internalSetRoutes");
|
|
},
|
|
_internalSetStateDoNotUseOrYouWillBreakYourApp() {
|
|
throw msg("_internalSetStateDoNotUseOrYouWillBreakYourApp");
|
|
}
|
|
};
|
|
}
|
|
function createHref(to) {
|
|
return typeof to === "string" ? to : createPath(to);
|
|
}
|
|
function encodeLocation(to) {
|
|
let href = typeof to === "string" ? to : createPath(to);
|
|
href = href.replace(/ $/, "%20");
|
|
let encoded = ABSOLUTE_URL_REGEX3.test(href) ? new URL(href) : new URL(href, "http://localhost");
|
|
return {
|
|
pathname: encoded.pathname,
|
|
search: encoded.search,
|
|
hash: encoded.hash
|
|
};
|
|
}
|
|
var ABSOLUTE_URL_REGEX3 = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
var ESCAPE_LOOKUP2 = {
|
|
"&": "\\u0026",
|
|
">": "\\u003e",
|
|
"<": "\\u003c",
|
|
"\u2028": "\\u2028",
|
|
"\u2029": "\\u2029"
|
|
};
|
|
var ESCAPE_REGEX2 = /[&><\u2028\u2029]/g;
|
|
function htmlEscape(str) {
|
|
return str.replace(ESCAPE_REGEX2, (match) => ESCAPE_LOOKUP2[match]);
|
|
}
|
|
|
|
export {
|
|
Action,
|
|
createBrowserHistory,
|
|
invariant,
|
|
createPath,
|
|
parsePath,
|
|
createContext,
|
|
RouterContextProvider,
|
|
convertRoutesToDataRoutes,
|
|
matchRoutes,
|
|
generatePath,
|
|
matchPath,
|
|
stripBasename,
|
|
resolvePath,
|
|
data,
|
|
redirect,
|
|
redirectDocument,
|
|
replace,
|
|
ErrorResponseImpl,
|
|
isRouteErrorResponse,
|
|
IDLE_NAVIGATION,
|
|
IDLE_FETCHER,
|
|
IDLE_BLOCKER,
|
|
createRouter,
|
|
createStaticHandler,
|
|
getStaticContextFromError,
|
|
isDataWithResponseInit,
|
|
isResponse,
|
|
isRedirectStatusCode,
|
|
isRedirectResponse,
|
|
isMutationMethod,
|
|
DataRouterContext,
|
|
DataRouterStateContext,
|
|
RSCRouterContext,
|
|
ViewTransitionContext,
|
|
FetchersContext,
|
|
AwaitContextProvider,
|
|
NavigationContext,
|
|
LocationContext,
|
|
RouteContext,
|
|
ENABLE_DEV_WARNINGS,
|
|
useHref,
|
|
useInRouterContext,
|
|
useLocation,
|
|
useNavigationType,
|
|
useMatch,
|
|
useNavigate,
|
|
useOutletContext,
|
|
useOutlet,
|
|
useParams,
|
|
useResolvedPath,
|
|
useRoutes,
|
|
useNavigation,
|
|
useRevalidator,
|
|
useMatches,
|
|
useLoaderData,
|
|
useRouteLoaderData,
|
|
useActionData,
|
|
useRouteError,
|
|
useAsyncValue,
|
|
useAsyncError,
|
|
useBlocker,
|
|
warnOnce,
|
|
mapRouteProperties,
|
|
hydrationRouteProperties,
|
|
createMemoryRouter,
|
|
UNSTABLE_TransitionEnabledRouterProvider,
|
|
RouterProvider,
|
|
MemoryRouter,
|
|
Navigate,
|
|
Outlet,
|
|
Route,
|
|
Router,
|
|
Routes,
|
|
Await,
|
|
createRoutesFromChildren,
|
|
createRoutesFromElements,
|
|
renderMatches,
|
|
WithComponentProps,
|
|
withComponentProps,
|
|
WithHydrateFallbackProps,
|
|
withHydrateFallbackProps,
|
|
WithErrorBoundaryProps,
|
|
withErrorBoundaryProps,
|
|
createSearchParams,
|
|
escapeHtml,
|
|
encode,
|
|
createRequestInit,
|
|
SingleFetchRedirectSymbol,
|
|
SINGLE_FETCH_REDIRECT_STATUS,
|
|
NO_BODY_STATUS_CODES,
|
|
StreamTransfer,
|
|
getTurboStreamSingleFetchDataStrategy,
|
|
getSingleFetchDataStrategyImpl,
|
|
stripIndexParam,
|
|
singleFetchUrl,
|
|
decodeViaTurboStream,
|
|
RemixErrorBoundary,
|
|
createServerRoutes,
|
|
createClientRoutesWithHMRRevalidationOptOut,
|
|
noActionDefinedError,
|
|
createClientRoutes,
|
|
shouldHydrateRouteLoader,
|
|
getPatchRoutesOnNavigationFunction,
|
|
useFogOFWarDiscovery,
|
|
getManifestPath,
|
|
FrameworkContext,
|
|
CRITICAL_CSS_DATA_ATTRIBUTE,
|
|
Links,
|
|
PrefetchPageLinks,
|
|
Meta,
|
|
setIsHydrated,
|
|
Scripts,
|
|
createBrowserRouter,
|
|
createHashRouter,
|
|
BrowserRouter,
|
|
HashRouter,
|
|
HistoryRouter,
|
|
Link,
|
|
NavLink,
|
|
Form,
|
|
ScrollRestoration,
|
|
useLinkClickHandler,
|
|
useSearchParams,
|
|
useSubmit,
|
|
useFormAction,
|
|
useFetcher,
|
|
useFetchers,
|
|
useScrollRestoration,
|
|
useBeforeUnload,
|
|
usePrompt,
|
|
useViewTransitionState,
|
|
StaticRouter,
|
|
StaticRouterProvider,
|
|
createStaticHandler2,
|
|
createStaticRouter
|
|
};
|