-
-
Notifications
You must be signed in to change notification settings - Fork 457
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[2.x] Allow custom component on Link "as" prop in React, Vue2 and Vue3 #1940
base: v1
Are you sure you want to change the base?
Conversation
Thanks @guizoxxv, I'll test your implementation and get back to you soon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this PR! I tested it today and everything works smoothly except for one small accessibility issue. When a custom component passed to the as
prop renders an anchor tag, it doesn't receive the href
attribute, which breaks accessibility:
<Link :as="CustomAnchor" href="/" class="hover:underline">Home</Link>
<a class="hover:underline">Home</a>
The link still functions due to the onclick
handler, but without the href
, it’s not fully accessible. Could you ensure that the href
attribute is passed when the custom component renders an anchor element?
@pedroborges Thanks for the review. I didn't find an effective way to discover the HTML element of the custom component main node without mounting it. Something like this: const getCustomComponentTagName = (customComponent: Component): string => {
const app = createApp(customComponent);
const instance = app.mount(document.createElement('div'));
const tagName = instance.$el.tagName; // single root component example
app.unmount();
return tagName;
} I'm not sure if this temporary rendering would be viable considering performance/resources. In that case, do you know an altertive way? |
@guizoxxv Yeah, this solution is not good for performance. Here’s an alternative approach that should be more performant:
This way, you can avoid unnecessary temporary rendering and still maintain accessibility when an anchor element is used. What do you think of this approach? Let me know if you need help implementing it 😉 |
You mean something like this (Vue3 example)? setup(props, { slots, attrs }) {
const elementRef = ref(null)
nextTick(() => {
if (elementRef.value) {
const element = elementRef.value.$el
if (element.tagName !== 'A') {
element.removeAttribute('href')
}
}
})
return () => {
const isAnchor = props.as === 'a' || props.as === 'A'
const isCustomComponent = typeof props.as !== 'string'
const method = props.method.toLowerCase() as Method
const [href, data] = mergeDataIntoQueryString(method, props.href || '', props.data, props.queryStringArrayFormat)
if (isAnchor && method !== 'get') {
console.warn(
`Creating POST/PUT/PATCH/DELETE <a> links is discouraged as it causes "Open Link in New Tab/Window" accessibility issues.\n\nPlease specify a more appropriate element using the "as" attribute. For example:\n\n<Link href="${href}" method="${method}" as="button">...</Link>`,
)
}
return h(
props.as,
{
...attrs,
...(isAnchor || isCustomComponent ? { href } : {}),
...(isCustomComponent ? { ref: elementRef } : {}),
onClick: (event) => {
if (shouldIntercept(event)) {
event.preventDefault()
router.visit(href, {
data: data,
method: method,
replace: props.replace,
preserveScroll: props.preserveScroll,
preserveState: props.preserveState ?? method !== 'get',
only: props.only,
except: props.except,
headers: props.headers,
// @ts-expect-error
onCancelToken: attrs.onCancelToken || (() => ({})),
// @ts-expect-error
onBefore: attrs.onBefore || (() => ({})),
// @ts-expect-error
onStart: attrs.onStart || (() => ({})),
// @ts-expect-error
onProgress: attrs.onProgress || (() => ({})),
// @ts-expect-error
onFinish: attrs.onFinish || (() => ({})),
// @ts-expect-error
onCancel: attrs.onCancel || (() => ({})),
// @ts-expect-error
onSuccess: attrs.onSuccess || (() => ({})),
// @ts-expect-error
onError: attrs.onError || (() => ({})),
})
}
},
},
slots,
)
}
}, I like it. Another idea I had is adding a new prop for this scenario, for example <Link :as="CustomAnchor" href="/" keep-href>Home</Link> However, this add more burden to the user, while yours don't. Also, it feels counter intuitive that without it, the |
This sounds like a good plan! I’m with you on not adding extra burden on the user. Also, I think we might not even need You could try moving the |
Yeah, I just bumped into another problem. In Vue 2, because the Link is a functional component, which is stateless, I cannot use lifecycle hooks.
|
@pedroborges Typescript interface |
Thanks for the clarification, @guizoxxv, and my apologies for the confusion! Functional components wasn't a thing when I last worked with Vue 2, so I wasn’t fully aware of their limitations. That being said, I also forgot to mention that we’re dropping support for Vue 2 in Inertia V2. Since your PR introduces new behavior, we'll be merging it into V2. For now, you can go ahead and make these changes just for the React and Vue 3 adapters. Once the V2 branch is live, we’ll rebase and merge your work. Thanks again for your contribution, and I appreciate your patience! |
This reverts commit 3295dc0 since Inertia 2 won't have support for Vue 2.
No problem. I had more experience with Vue 2 - only started with Vue 3 last year =). It makes sense to drop support for Vue 2 since it reached end of life. I reverted my code to keep the Vue 2 file as it was. I had a bit of a strugle in React due to the ref type, which can be a function. I ended up using this function import { forwardRef, Ref } from "react"
const CustomComponent = forwardRef(({ children, ...props }, ref: Ref<HTMLButtonElement>) => {
return (
<button ref={ref} {...props}>
{children}
</button>
)
})
export default CustomComponent |
Thanks, @guizoxxv! We’ve been focusing on merging bug fixes for 1.3 this week, but I’ll make sure to review your changes next week. Appreciate your patience! |
Would potentially close vuetifyjs/vuetify#11573 as well. |
When will it be merged into the mainline? |
This PR adds the ability to use a custom component in the
as
prop of the Link component. Currently it only accepts a string value. The proposed changes would allow to use the link as:This feature was requested in #1550, #1654, #1668 and #1746.