one
Version:
One is a new React Framework that makes Vite serve both native and web.
201 lines (173 loc) • 5.56 kB
text/typescript
import type { RouteNode } from './Route'
/**
* Find a RouteNode from the route tree based on the navigation state.
* Walks through the state's routes recursively to find the deepest matching route.
*/
export function findRouteNodeFromState(
state: { routes: Array<{ name: string; state?: any }> } | undefined,
rootNode: RouteNode | null
): RouteNode | null {
if (!state || !rootNode) {
return null
}
// Get the current route from state (the active one based on index)
const currentRoute = state.routes[state.routes.length - 1]
if (!currentRoute) {
return null
}
// Find the matching child node
const matchingNode = findNodeByRouteName(rootNode, currentRoute.name)
if (!matchingNode) {
return null
}
// If there's a nested state, continue recursively
if (currentRoute.state && currentRoute.state.routes) {
const nestedResult = findRouteNodeFromState(currentRoute.state, matchingNode)
if (nestedResult) {
return nestedResult
}
}
return matchingNode
}
/**
* Find a node by its route name in the tree.
* Searches children recursively.
*/
function findNodeByRouteName(node: RouteNode, routeName: string): RouteNode | null {
// Check if this node matches
if (node.route === routeName) {
return node
}
// Search children
for (const child of node.children) {
const found = findNodeByRouteName(child, routeName)
if (found) {
return found
}
}
return null
}
/**
* Extract params from navigation state.
* Collects params from all routes in the state hierarchy.
*/
export function extractParamsFromState(
state:
| { routes: Array<{ name: string; params?: Record<string, any>; state?: any }> }
| undefined
): Record<string, string | string[]> {
if (!state) {
return {}
}
const params: Record<string, string | string[]> = {}
// Collect params from all routes in the state
for (const route of state.routes) {
if (route.params) {
Object.assign(params, route.params)
}
// Recurse into nested state
if (route.state) {
Object.assign(params, extractParamsFromState(route.state))
}
}
return params
}
/**
* Extract search params from href string.
*/
export function extractSearchFromHref(href: string): Record<string, string | string[]> {
const searchIndex = href.indexOf('?')
if (searchIndex === -1) {
return {}
}
const searchString = href.slice(searchIndex + 1)
const params: Record<string, string | string[]> = {}
const searchParams = new URLSearchParams(searchString)
searchParams.forEach((value, key) => {
const existing = params[key]
if (existing === undefined) {
params[key] = value
} else if (Array.isArray(existing)) {
existing.push(value)
} else {
params[key] = [existing, value]
}
})
return params
}
/**
* Extract pathname from href string.
*/
export function extractPathnameFromHref(href: string): string {
const searchIndex = href.indexOf('?')
const hashIndex = href.indexOf('#')
let endIndex = href.length
if (searchIndex !== -1) endIndex = Math.min(endIndex, searchIndex)
if (hashIndex !== -1) endIndex = Math.min(endIndex, hashIndex)
return href.slice(0, endIndex)
}
/**
* Find all route nodes from root to the current page based on navigation state.
* Returns an array of RouteNodes in order from root layout to the page.
* This is used on native to build the full matches array including layouts.
*/
export function findAllRouteNodesFromState(
state: { routes: Array<{ name: string; state?: any }> } | undefined,
rootNode: RouteNode | null
): RouteNode[] {
if (!state || !rootNode) {
return []
}
const nodes: RouteNode[] = []
function collectNodes(
currentState:
| { routes: Array<{ name: string; state?: any; params?: Record<string, any> }> }
| undefined,
parentNode: RouteNode | null
) {
if (!currentState || !parentNode) {
return
}
// get the current route from state (the active one based on index)
const currentRoute = currentState.routes[currentState.routes.length - 1]
if (!currentRoute) {
return
}
// find the matching child node
const matchingNode = findNodeByRouteName(parentNode, currentRoute.name)
if (!matchingNode) {
if (process.env.NODE_ENV === 'development') {
console.log(
'[one] findAllRouteNodesFromState: could not find node for',
currentRoute.name,
'in',
parentNode.route
)
}
return
}
// add this node to the list
nodes.push(matchingNode)
// if there's a nested state, continue recursively
if (currentRoute.state && currentRoute.state.routes) {
collectNodes(currentRoute.state, matchingNode)
} else if (currentRoute.params?.screen) {
// react navigation uses params.screen to specify child route when state isn't created yet
// this happens on initial render before the nested navigator mounts
const childRouteName = currentRoute.params.screen as string
const childNode = matchingNode.children.find((c) => c.route === childRouteName)
if (childNode) {
nodes.push(childNode)
// if there are nested params, continue recursively
if (currentRoute.params.params && (currentRoute.params.params as any).screen) {
collectNodes(
{ routes: [{ name: childRouteName, params: currentRoute.params.params }] },
childNode
)
}
}
}
}
collectNodes(state, rootNode)
return nodes
}