UNPKG

@tanstack/router-core

Version:

Modern and scalable routing for React applications

155 lines (154 loc) 5.92 kB
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