diff --git a/packages/router-core/src/path.ts b/packages/router-core/src/path.ts index f2daff5104f..4d002673cdb 100644 --- a/packages/router-core/src/path.ts +++ b/packages/router-core/src/path.ts @@ -216,6 +216,7 @@ interface InterpolatePathOptions { type InterPolatePathResult = { interpolatedPath: string usedParams: Record + isMissingParams: boolean // true if any params were not available when being looked up in the params object } export function interpolatePath({ path, @@ -238,6 +239,10 @@ export function interpolatePath({ } } + // Tracking if any params are missing in the `params` object + // when interpolating the path + let isMissingParams = false + const usedParams: Record = {} const interpolatedPath = joinPaths( interpolatedPathSegments.map((segment) => { @@ -250,6 +255,9 @@ export function interpolatePath({ if (segment.type === 'param') { const key = segment.value.substring(1) + if (!isMissingParams && !(key in params)) { + isMissingParams = true + } usedParams[key] = params[key] if (leaveParams) { const value = encodeParam(segment.value) @@ -261,7 +269,7 @@ export function interpolatePath({ return segment.value }), ) - return { usedParams, interpolatedPath } + return { usedParams, interpolatedPath, isMissingParams } } function encodePathParam(value: string, decodeCharMap?: Map) { diff --git a/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx b/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx index b05428c9260..a84f318bc6e 100644 --- a/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx +++ b/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx @@ -1,7 +1,7 @@ import { clsx as cx } from 'clsx' import { default as invariant } from 'tiny-invariant' -import { rootRouteId, trimPath } from '@tanstack/router-core' -import { createMemo } from 'solid-js' +import { interpolatePath, rootRouteId, trimPath } from '@tanstack/router-core' +import { Show, createMemo } from 'solid-js' import { useDevtoolsOnClose } from './context' import { useStyles } from './useStyles' import useLocalStorage from './useLocalStorage' @@ -10,6 +10,7 @@ import { getRouteStatusColor, getStatusColor, multiSortBy } from './utils' import { AgeTicker } from './AgeTicker' // import type { DevtoolsPanelOptions } from './TanStackRouterDevtoolsPanel' +import { NavigateButton } from './NavigateButton' import type { AnyContext, AnyRoute, @@ -64,6 +65,28 @@ function Logo(props: any) { ) } +function NavigateLink(props: { + class?: string + left?: JSX.Element + children?: JSX.Element + right?: JSX.Element +}) { + return ( +
+ {props.left} +
{props.children}
+ {props.right} +
+ ) +} + function RouteComp({ routerState, router, @@ -126,6 +149,30 @@ function RouteComp({ } }) + const navigationTarget = createMemo(() => { + if (isRoot) return undefined // rootRouteId has no path + if (!route.path) return undefined // no path to navigate to + + // flatten all params in the router state, into a single object + const allParams = Object.assign({}, ...matches().map((m) => m.params)) + + // interpolatePath is used by router-core to generate the `to` + // path for the navigate function in the router + const interpolated = interpolatePath({ + path: route.fullPath, + params: allParams, + leaveWildcards: false, + leaveParams: false, + decodeCharMap: router().pathParamsDecodeCharMap, + }) + + // only if `interpolated` is not missing params, return the path since this + // means that all the params are present for a successful navigation + return !interpolated.isMissingParams + ? interpolated.interpolatedPath + : undefined + }) + return (
-
-
- - {isRoot ? rootRouteId : route.path || trimPath(route.id)}{' '} - - {param()} -
- -
+ + {(navigate) => } + + } + right={} + > + + {isRoot ? rootRouteId : route.path || trimPath(route.id)}{' '} + + {param()} +
{route.children?.length ? (
@@ -406,7 +458,7 @@ export const BaseTanStackRouterDevtoolsPanel = {(routerState().pendingMatches?.length ? routerState().pendingMatches : routerState().matches - )?.map((match: any, i: any) => { + )?.map((match: any, _i: any) => { return (
- - {`${match.routeId === rootRouteId ? rootRouteId : match.pathname}`} - + + } + right={} + > + + {`${match.routeId === rootRouteId ? rootRouteId : match.pathname}`} + +
) })} @@ -457,10 +519,19 @@ export const BaseTanStackRouterDevtoolsPanel = styles().matchIndicator(getStatusColor(match)), )} /> - - {`${match.id}`} - - + + } + right={} + > + {`${match.id}`} +
) })} diff --git a/packages/router-devtools-core/src/NavigateButton.tsx b/packages/router-devtools-core/src/NavigateButton.tsx new file mode 100644 index 00000000000..8f9e5161fc5 --- /dev/null +++ b/packages/router-devtools-core/src/NavigateButton.tsx @@ -0,0 +1,25 @@ +import { useStyles } from './useStyles' +import type { AnyRouter, NavigateOptions } from '@tanstack/router-core' +import type { Accessor } from 'solid-js' + +interface Props extends NavigateOptions { + router: Accessor +} + +export function NavigateButton({ to, params, search, router }: Props) { + const styles = useStyles() + + return ( + + ) +} diff --git a/packages/router-devtools-core/src/useStyles.tsx b/packages/router-devtools-core/src/useStyles.tsx index 3ba7a9785dc..d1a12ad5753 100644 --- a/packages/router-devtools-core/src/useStyles.tsx +++ b/packages/router-devtools-core/src/useStyles.tsx @@ -370,6 +370,12 @@ const stylesFactory = (shadowDOMTarget?: ShadowRoot) => { return classes }, + routesRowInner: css` + display: 'flex'; + align-items: 'center'; + flex-grow: 1; + min-width: 0; + `, routeParamInfo: css` color: ${colors.gray[400]}; font-size: ${fontSize.xs}; @@ -385,6 +391,9 @@ const stylesFactory = (shadowDOMTarget?: ShadowRoot) => { code: css` font-size: ${fontSize.xs}; line-height: ${lineHeight['xs']}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; `, matchesContainer: css` flex: 1 1 auto; @@ -579,6 +588,22 @@ const stylesFactory = (shadowDOMTarget?: ShadowRoot) => { width: ${size[2]}; height: ${size[2]}; `, + navigateButton: css` + background: none; + border: none; + padding: 0 0 0 4px; + margin: 0; + color: ${colors.gray[400]}; + font-size: ${fontSize.md}; + cursor: pointer; + line-height: 1; + vertical-align: middle; + margin-right: 0.5ch; + flex-shrink: 0; + &:hover { + color: ${colors.blue[300]}; + } + `, } }