diff --git a/src/router.ts b/src/router.ts index 1ecef12..d36d3bc 100644 --- a/src/router.ts +++ b/src/router.ts @@ -63,15 +63,22 @@ function lookup( // Exact matches take precedence over placeholders const nextNode = node.children.get(section); if (nextNode === undefined) { - node = node.placeholderChildNode; - if (node === null) { - break; + if (node && node.placeholderChildren.length > 1) { + // https://github.com/unjs/radix3/issues/95 + const remaining = sections.length - i; + node = + node.placeholderChildren.find((c) => c.maxDepth === remaining) || + null; } else { - if (node.paramName) { - params[node.paramName] = section; - } - paramsFound = true; + node = node.placeholderChildren[0] || null; + } + if (!node) { + break; + } + if (node.paramName) { + params[node.paramName] = section; } + paramsFound = true; } else { node = nextNode; } @@ -106,6 +113,8 @@ function insert(ctx: RadixRouterContext, path: string, data: any) { let _unnamedPlaceholderCtr = 0; + const matchedNodes = [node]; + for (const section of sections) { let childNode: RadixNode | undefined; @@ -122,7 +131,7 @@ function insert(ctx: RadixRouterContext, path: string, data: any) { if (type === NODE_TYPES.PLACEHOLDER) { childNode.paramName = section === "*" ? `_${_unnamedPlaceholderCtr++}` : section.slice(1); - node.placeholderChildNode = childNode; + node.placeholderChildren.push(childNode); isStaticRoute = false; } else if (type === NODE_TYPES.WILDCARD) { node.wildcardChildNode = childNode; @@ -130,10 +139,15 @@ function insert(ctx: RadixRouterContext, path: string, data: any) { isStaticRoute = false; } + matchedNodes.push(childNode); node = childNode; } } + for (const [depth, node] of matchedNodes.entries()) { + node.maxDepth = Math.max(matchedNodes.length - depth, node.maxDepth || 0); + } + // Store whatever data was provided into the node node.data = data; @@ -164,7 +178,7 @@ function remove(ctx: RadixRouterContext, path: string) { if (Object.keys(node.children).length === 0 && node.parent) { node.parent.children.delete(lastSection); node.parent.wildcardChildNode = null; - node.parent.placeholderChildNode = null; + node.parent.placeholderChildren = []; } success = true; } @@ -175,12 +189,13 @@ function remove(ctx: RadixRouterContext, path: string) { function createRadixNode(options: Partial = {}): RadixNode { return { type: options.type || NODE_TYPES.NORMAL, + maxDepth: 0, parent: options.parent || null, children: new Map(), data: options.data || null, paramName: options.paramName || null, wildcardChildNode: null, - placeholderChildNode: null, + placeholderChildren: [], }; } diff --git a/src/types.ts b/src/types.ts index 8394537..a3e1d0d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,12 +18,13 @@ export type MatchedRoute = Omit< export interface RadixNode { type: NODE_TYPE; + maxDepth: number; parent: RadixNode | null; children: Map>; data: RadixNodeData | null; paramName: string | null; wildcardChildNode: RadixNode | null; - placeholderChildNode: RadixNode | null; + placeholderChildren: RadixNode[]; } export interface RadixRouterOptions { diff --git a/tests/router.test.ts b/tests/router.test.ts index 156bac0..670a7af 100644 --- a/tests/router.test.ts +++ b/tests/router.test.ts @@ -62,6 +62,40 @@ describe("Router lookup", function () { }, }, ); + + testRouter(["/", "/:a", "/:a/:y/:x/:b", "/:a/:x/:b", "/:a/:b"], { + "/": { path: "/" }, + "/a": { + path: "/:a", + params: { + a: "a", + }, + }, + "/a/b": { + path: "/:a/:b", + params: { + a: "a", + b: "b", + }, + }, + "/a/x/b": { + path: "/:a/:x/:b", + params: { + a: "a", + b: "b", + x: "x", + }, + }, + "/a/y/x/b": { + path: "/:a/:y/:x/:b", + params: { + a: "a", + b: "b", + x: "x", + y: "y", + }, + }, + }); }); describe("should be able to perform wildcard lookups", function () {