@tanstack/router-core
Version:
Modern and scalable routing for React applications
155 lines (154 loc) • 5.92 kB
JavaScript
const require_utils = require("./utils.cjs");
//#region src/path.ts
/** Join path segments, cleaning duplicate slashes between parts. */
function joinPaths(paths) {
return cleanPath(paths.filter((val) => {
return val !== void 0;
}).join("/"));
}
/** Remove repeated slashes from a path string. */
function cleanPath(path) {
return path.replace(/\/{2,}/g, "/");
}
/** Trim leading slashes (except preserving root '/'). */
function trimPathLeft(path) {
return path === "/" ? path : path.replace(/^\/+/, "");
}
/** Trim trailing slashes (except preserving root '/'). */
function trimPathRight(path) {
const len = path.length;
return len > 1 && path[len - 1] === "/" ? path.replace(/\/+$/, "") : path;
}
/** Trim both leading and trailing slashes. */
function trimPath(path) {
return trimPathRight(trimPathLeft(path));
}
/** Remove a trailing slash from value when appropriate for comparisons. */
function removeTrailingSlash(value, basepath) {
if (value?.endsWith("/") && value !== "/" && value !== `${basepath}/`) return value.slice(0, -1);
return value;
}
/**
* Compare two pathnames for exact equality after normalizing trailing slashes
* relative to the provided `basepath`.
*/
function exactPathTest(pathName1, pathName2, basepath) {
return removeTrailingSlash(pathName1, basepath) === removeTrailingSlash(pathName2, basepath);
}
/**
* Resolve a destination path against a base, honoring trailing-slash policy
* and supporting relative segments (`.`/`..`) and absolute `to` values.
*/
function resolvePath({ base, to, trailingSlash = "never", cache }) {
if (to.includes("//")) to = cleanPath(to);
if (to.startsWith("/")) {
if (to.length === 1 || trailingSlash === "preserve") return to;
if (trailingSlash === "always") return to.endsWith("/") ? to : `${to}/`;
return to.endsWith("/") ? to.slice(0, -1) : to;
}
const isBase = to === ".";
let key;
if (cache) {
key = isBase ? base : base + "\0" + to;
const cached = cache.get(key);
if (cached) return cached;
}
let baseSegments;
if (isBase) baseSegments = base.split("/");
else {
if (base.includes("//")) base = cleanPath(base);
baseSegments = base.split("/");
while (baseSegments.length > 1 && require_utils.last(baseSegments) === "") baseSegments.pop();
const toSegments = to.split("/");
for (let index = 0, length = toSegments.length; index < length; index++) {
const value = toSegments[index];
if (value === "") {
if (!index) baseSegments = [value];
else if (index === length - 1) baseSegments.push(value);
} else if (value === "..") if (baseSegments.length > 1) baseSegments.pop();
else baseSegments = [""];
else if (value === ".") {} else baseSegments.push(value);
}
}
if (baseSegments.length > 1) {
if (require_utils.last(baseSegments) === "") {
if (trailingSlash === "never") baseSegments.pop();
} else if (trailingSlash === "always") baseSegments.push("");
}
const joined = baseSegments.join("/");
const result = (isBase ? cleanPath(joined) : joined) || "/";
if (key && cache) cache.set(key, result);
return result;
}
/**
* Create a pre-compiled decode config from allowed characters.
* Created once for the router's fixed encoding configuration.
*/
function compileDecodeCharMap(pathParamsAllowedCharacters) {
const charMap = new Map(pathParamsAllowedCharacters.map((char) => [encodeURIComponent(char), char]));
const regex = new RegExp([...charMap.keys()].join("|").replace(/[.*()]/g, "\\$&"), "g");
return (encoded) => encoded.replace(regex, (match) => charMap.get(match) ?? match);
}
function getRouteSegments(route) {
return route._interpolation;
}
/** A splat is missing when it has no value; `0` and `false` are stringified like any other param. */
function isMissingSplat(value) {
return value == null || value === "";
}
/** Devtools checks navigation availability separately from the hot formatter. */
function hasMissingPathParams(segments, params) {
return segments.some((part) => {
if (typeof part === "string") return false;
const [kind, key] = part;
return kind === 2 ? isMissingSplat(params[key]) : kind === 1 && !(key in params);
});
}
function encodeParam(key, value, decoder) {
if (typeof value !== "string") return "" + (value ?? void 0);
const splat = key === "_splat";
if (splat && (!value || /^[a-zA-Z0-9\-._~!/]*$/.test(value))) return value;
let encoded = encodeURIComponent(value);
if (splat) encoded = encoded.replaceAll("%2F", "/");
return decoder ? decoder(encoded) : encoded;
}
/** Substitute current values into parsed segments, optionally collecting raw params. */
function interpolatePath(path, segments, params, decoder, usedParams) {
const trailingSlash = path.endsWith("/") ? "/" : "";
let joined = "";
for (const part of segments) {
if (typeof part === "string") {
joined += part;
continue;
}
const [kind, key, prefix, rawSuffix] = part;
const splat = kind === 2;
const suffix = splat && rawSuffix !== void 0 ? rawSuffix + trailingSlash : rawSuffix;
let paramValue = params[key];
if (kind === 3 && paramValue == null) continue;
if (usedParams) {
usedParams[key] = paramValue;
if (splat) usedParams["*"] = paramValue;
}
if (splat && isMissingSplat(paramValue)) {
if (prefix === "/" && !suffix) continue;
paramValue = "";
}
joined += prefix + encodeParam(key, paramValue, decoder) + (suffix || "");
}
return joined + trailingSlash || "/";
}
//#endregion
exports.cleanPath = cleanPath;
exports.compileDecodeCharMap = compileDecodeCharMap;
exports.exactPathTest = exactPathTest;
exports.getRouteSegments = getRouteSegments;
exports.hasMissingPathParams = hasMissingPathParams;
exports.interpolatePath = interpolatePath;
exports.joinPaths = joinPaths;
exports.removeTrailingSlash = removeTrailingSlash;
exports.resolvePath = resolvePath;
exports.trimPath = trimPath;
exports.trimPathLeft = trimPathLeft;
exports.trimPathRight = trimPathRight;
//# sourceMappingURL=path.cjs.map