UNPKG

fully-react

Version:
246 lines (203 loc) 5.52 kB
import { AnyPathParams } from "./route"; import { MatchLocation } from "./router"; import { last } from "./utils"; export interface Segment { type: "pathname" | "param" | "wildcard"; value: string; } const dynamicParamIndicator = "$"; export function joinPaths(paths: (string | undefined)[]) { return cleanPath(paths.filter(Boolean).join("/")); } export function cleanPath(path: string) { // remove double slashes return path.replace(/\/{2,}/g, "/"); } export function trimPathLeft(path: string) { return path === "/" ? path : path.replace(/^\/{1,}/, ""); } export function trimPathRight(path: string) { return path === "/" ? path : path.replace(/\/{1,}$/, ""); } export function trimPath(path: string) { return trimPathRight(trimPathLeft(path)); } export function resolvePath(basepath: string, base: string, to: string) { base = base.replace(new RegExp(`^${basepath}`), "/"); to = to.replace(new RegExp(`^${basepath}`), "/"); let baseSegments = parsePathname(base); const toSegments = parsePathname(to); toSegments.forEach((toSegment, index) => { if (toSegment.value === "/") { if (!index) { // Leading slash baseSegments = [toSegment]; } else if (index === toSegments.length - 1) { // Trailing Slash baseSegments.push(toSegment); } else { // ignore inter-slashes } } else if (toSegment.value === "..") { // Extra trailing slash? pop it off if (baseSegments.length > 1 && last(baseSegments)?.value === "/") { baseSegments.pop(); } baseSegments.pop(); } else if (toSegment.value === ".") { return; } else { baseSegments.push(toSegment); } }); const joined = joinPaths([basepath, ...baseSegments.map((d) => d.value)]); return cleanPath(joined); } export function parsePathname(pathname?: string): Segment[] { if (!pathname) { return []; } pathname = cleanPath(pathname); const segments: Segment[] = []; if (pathname.slice(0, 1) === "/") { pathname = pathname.substring(1); segments.push({ type: "pathname", value: "/", }); } if (!pathname) { return segments; } // Remove empty segments and '.' segments const split = pathname.split("/").filter(Boolean); segments.push( ...split.map((part): Segment => { if (part === ":" || part === "*") { return { type: "wildcard", value: part, }; } if (part.charAt(0) === ":") { return { type: "param", value: part, }; } return { type: "pathname", value: part, }; }), ); if (pathname.slice(-1) === "/") { pathname = pathname.substring(1); segments.push({ type: "pathname", value: "/", }); } return segments; } export function interpolatePath( path: string | undefined, params: any, leaveWildcard?: boolean, ) { const interpolatedPathSegments = parsePathname(path); return joinPaths( interpolatedPathSegments.map((segment) => { if ([":", "*"].includes(segment.value) && !leaveWildcard) { return ""; } if (segment.type === "param") { return params![segment.value.substring(1)] ?? ""; } return segment.value; }), ); } export function matchPathname( basepath: string, currentPathname: string, matchLocation: Pick<MatchLocation, "to" | "fuzzy" | "caseSensitive">, ): AnyPathParams | undefined { const pathParams = matchByPath(basepath, currentPathname, matchLocation); // const searchMatched = matchBySearch(currentLocation.search, matchLocation) if (matchLocation.to && !pathParams) { return; } return pathParams ?? {}; } export function matchByPath( basepath: string, from: string, matchLocation: Pick<MatchLocation, "to" | "caseSensitive" | "fuzzy">, ): Record<string, string> | undefined { if (!from.startsWith(basepath)) { return undefined; } from = basepath != "/" ? from.substring(basepath.length) : from; const baseSegments = parsePathname(from); const to = `${matchLocation.to ?? "$"}`; const routeSegments = parsePathname(to); if (last(baseSegments)?.value === "/") { baseSegments.pop(); } const params: Record<string, string> = {}; const isMatch = (() => { for ( let i = 0; i < Math.max(baseSegments.length, routeSegments.length); i++ ) { const baseSegment = baseSegments[i]; const routeSegment = routeSegments[i]; const isLastRouteSegment = i === routeSegments.length - 1; const isLastBaseSegment = i === baseSegments.length - 1; if (routeSegment) { if (routeSegment.type === "wildcard") { if (baseSegment?.value) { params["*"] = joinPaths(baseSegments.slice(i).map((d) => d.value)); return true; } return false; } if (routeSegment.type === "pathname") { if (routeSegment.value === "/" && !baseSegment?.value) { return true; } if (baseSegment) { if (matchLocation.caseSensitive) { if (routeSegment.value !== baseSegment.value) { return false; } } else if ( routeSegment.value.toLowerCase() !== baseSegment.value.toLowerCase() ) { return false; } } } if (!baseSegment) { return false; } if (routeSegment.type === "param") { if (baseSegment?.value === "/") { return false; } if (baseSegment.value.charAt(0) !== "$") { params[routeSegment.value.substring(1)] = baseSegment.value; } } } if (isLastRouteSegment && !isLastBaseSegment) { return !!matchLocation.fuzzy; } } return true; })(); return isMatch ? (params as Record<string, string>) : undefined; }