import type { ChildrenList } from '../models/children-list.js' import type { PartialElement } from '../models/partial-element.js' import { LocationService } from '../services/location-service.js' import { createComponent } from '../shade-component.js' import { Shade } from '../shade.js' import { buildNestedNavigateUrl } from './nested-navigate.js' import type { ExtractRouteHash, ExtractRouteQuery, ExtractRoutePaths, RouteAt, TypedHashArg, TypedParamsArg, TypedQueryArg, } from './nested-route-types.js' import type { NestedRoute } from './nested-router.js' /** * Props for the NestedRouteLink component. * Combines SPA navigation with parameter compilation for nested routes, * plus optional query string and hash fragment composition. */ export type NestedRouteLinkProps = { path: string params?: Record query?: Record hash?: string } & PartialElement> /** * Props for a type-safe nested route link. * When the path contains parameters (e.g. `:id`), the `params` prop becomes required. * `query` and `hash` remain loose (untyped) on this variant; use * {@link createNestedRouteLink} to narrow them against a specific route tree. * @typeParam TPath - A specific route path string */ export type TypedNestedRouteLinkProps = { path: TPath query?: Record hash?: string } & TypedParamsArg & PartialElement> /** * Props for a route-tree-aware nested route link. Narrows `query` and `hash` * against the specific route at `TPath` in `TRoutes`. * @typeParam TRoutes - The route tree * @typeParam TPath - A composed route path within `TRoutes` */ export type TreeAwareNestedRouteLinkProps< TRoutes extends Record>, TPath extends string, > = { path: TPath } & TypedParamsArg & TypedQueryArg>> & TypedHashArg>> & PartialElement> const _NestedRouteLink = Shade({ customElementName: 'nested-route-link', elementBase: HTMLAnchorElement, elementBaseName: 'a', css: { color: 'inherit', textDecoration: 'inherit', }, render: ({ children, props, injector, useHostProps }) => { const { path, params, query, hash } = props const resolvedUrl = buildNestedNavigateUrl({ path, params, query, hash }) useHostProps({ href: resolvedUrl, onclick: (ev: MouseEvent) => { ev.preventDefault() // eslint-disable-next-line furystack/prefer-location-service -- This IS the SPA link component; it must call pushState directly. history.pushState('', props.title || '', resolvedUrl) injector.get(LocationService).updateState() }, }) return <>{children} }, }) /** * A link component for NestedRouter that supports SPA navigation with * type-safe route parameter compilation. * * Intercepts click events to use `history.pushState` for client-side navigation, * compiles parameterized routes (e.g. `/users/:id`) when `params` is provided, * serializes `query` to the URL search string and appends `hash` when set. * * Route parameters are automatically inferred from the `path` pattern: * - `path="/buttons"` — `params` is optional * - `path="/users/:id"` — `params: { id: string }` is required * * For additional URL validation against a route tree (including `query` and * `hash` narrowing), use {@link createNestedRouteLink}. */ export const NestedRouteLink = _NestedRouteLink as unknown as ( props: TypedNestedRouteLinkProps, children?: ChildrenList, ) => JSX.Element /** * Creates a type-safe wrapper around NestedRouteLink constrained to a specific * route tree. The returned component has the same runtime behavior but narrows * `path` to only accept valid route paths, requires `params` when the route * has parameters, and enforces the route's declared `query` and `hash` schemas. * * @typeParam TRoutes - The route tree type (use `typeof yourRoutes`) * @returns A narrowed NestedRouteLink component * * @example * ```typescript * const AppLink = createNestedRouteLink() * * Buttons * User * User * ``` */ export const createNestedRouteLink = >>() => { return _NestedRouteLink as unknown as >( props: TreeAwareNestedRouteLinkProps, children?: ChildrenList, ) => JSX.Element }