Skip to content

Commit feb29b4

Browse files
committed
use _asChild prop on the link for solid, like radix ui
1 parent b0ec93e commit feb29b4

File tree

3 files changed

+60
-4
lines changed

3 files changed

+60
-4
lines changed

e2e/solid-router/basic/src/main.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,25 @@ function RootComponent() {
7070
}}
7171
>
7272
This Route Does Not Exist
73-
</Link>
73+
</Link>{' '}
74+
<div class="flex items-center">
75+
<svg width="20" height="20" viewBox="0 0 20 20" role="img">
76+
<title id="rectTitle">Link in SVG</title>
77+
<Link to="/posts" aria-label="Open posts from SVG" _asChild>
78+
<a>
79+
<rect
80+
x="0"
81+
y="0"
82+
width="20"
83+
height="20"
84+
rx="4"
85+
fill="blue"
86+
stroke-width="2"
87+
/>
88+
</a>
89+
</Link>
90+
</svg>
91+
</div>
7492
</div>
7593
<Outlet />
7694
<TanStackRouterDevtools position="bottom-right" />

e2e/solid-router/basic/tests/app.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { expect, test } from '@playwright/test'
2+
import { getTestServerPort } from '@tanstack/router-e2e-utils'
3+
import packageJson from '../package.json' with { type: 'json' }
4+
5+
const PORT = await getTestServerPort(packageJson.name)
26

37
test.beforeEach(async ({ page }) => {
48
await page.goto('/')
@@ -45,3 +49,16 @@ test('Navigating to a post page with viewTransition types', async ({
4549
await page.getByRole('link', { name: 'sunt aut facere repe' }).click()
4650
await expect(page.getByRole('heading')).toContainText('sunt aut facere')
4751
})
52+
53+
test('Link in SVG does not trigger a full page reload', async ({ page }) => {
54+
let fullPageLoad = false
55+
page.on('domcontentloaded', () => {
56+
fullPageLoad = true
57+
})
58+
59+
await page.getByRole('link', { name: 'Open posts from SVG' }).click()
60+
const url = `http://localhost:${PORT}/posts`
61+
await page.waitForURL(url)
62+
63+
expect(fullPageLoad).toBeFalsy()
64+
})

packages/solid-router/src/link.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,10 @@ export function useLinkProps<
281281
// The click handler
282282
const handleClick = (e: MouseEvent) => {
283283
// Check actual element's target attribute as fallback
284-
const elementTarget = (e.currentTarget as HTMLAnchorElement).target
284+
const elementTarget = (
285+
e.currentTarget as HTMLAnchorElement | SVGAElement
286+
).getAttribute('target')
287+
285288
const effectiveTarget =
286289
local.target !== undefined ? local.target : elementTarget
287290

@@ -470,6 +473,12 @@ export interface ActiveLinkOptionProps<TComp = 'a'> {
470473
* These props override other props passed to the link (`style`'s are merged, `class`'s are concatenated)
471474
*/
472475
inactiveProps?: ActiveLinkProps<TComp> | (() => ActiveLinkProps<TComp>)
476+
/**
477+
* When true, the Link will not render its own wrapper element.
478+
* Instead, it will pass its props to its single child element.
479+
* Useful for custom components or SVG elements where you need precise control over the rendered element.
480+
*/
481+
_asChild?: boolean | Solid.ValidComponent
473482
}
474483

475484
export type LinkProps<
@@ -579,10 +588,22 @@ export const Link: LinkComponent<'a'> = (props) => {
579588
return ch satisfies Solid.JSX.Element
580589
})
581590

591+
// For _asChild, use Dynamic to render the custom component
592+
if (local._asChild) {
593+
return (
594+
<Dynamic component={local._asChild} {...(linkProps as any)}>
595+
{children()}
596+
</Dynamic>
597+
)
598+
}
599+
600+
// Default rendering - just use regular anchor element
601+
// This works for HTML contexts; SVG links inside SVGs won't work properly
602+
// due to Solid's compile-time namespace determination
582603
return (
583-
<Dynamic component={local._asChild ? local._asChild : 'a'} {...linkProps}>
604+
<a {...(linkProps as any)}>
584605
{children()}
585-
</Dynamic>
606+
</a>
586607
)
587608
}
588609

0 commit comments

Comments
 (0)