import type { RenderProps } from "@nifrajs/web" import { createElement, type FunctionComponent, type ReactNode } from "react" import { RouterContext } from "./router.ts" // Stable empty params so the provider value's `params` has a fixed reference when a route has none. const EMPTY_PARAMS: Readonly> = Object.freeze({}) // Stable empty search for the same reason: a render with no search context (or a non-router usage). const EMPTY_SEARCH: Readonly> = Object.freeze({}) /** * Fold a layout chain (outermost layout → page) into a single React tree: the page * (innermost) receives `props` (the loader data); each layout wraps the child via its * `children`. Shared by the server adapter (renderToString) and the client (hydrateRoot). * * The whole tree is wrapped in a {@link RouterContext} provider carrying the matched `params`, the * current `path`, the validated `search`, and the client `pending` flag (all threaded through * `RenderProps` identically on SSR and client), so the routing hooks * (`useParams`/`useLocation`/`useSearch`/`useNavigation`) read the same value on both sides - no * hydration mismatch (`pending` is `false` on SSR and on the initial client render). * A render with no routing fields (a non-router adapter usage) provides the empty default. */ export function compose(chain: readonly unknown[], props: RenderProps): ReactNode { const last = chain.length - 1 let node: ReactNode = createElement(chain[last] as FunctionComponent, props) for (let i = last - 1; i >= 0; i--) { // children passed as the 3rd arg (not a `children` prop) - React's canonical form. // Each layout receives its own loader data at its own index. Layouts are the chain's leading // prefix, so `layoutData[i]` belongs to `chain[i]`; anything past that end (a client-only `_error` // boundary marker, the page) reads `undefined` and is unaffected. node = createElement( chain[i] as FunctionComponent<{ data: unknown }>, { data: props.layoutData?.[i] ?? null, ...(props.boundaries !== undefined ? { boundaries: props.boundaries } : {}), }, node, ) } return createElement( RouterContext.Provider, { value: { params: props.params ?? EMPTY_PARAMS, path: props.path ?? "", search: props.search ?? EMPTY_SEARCH, pending: props.pending ?? false, pendingPath: props.pendingPath, }, }, node, ) }