mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 13:45:18 +00:00
feat(nuxt3): support custom
prop for <nuxt-link>
(#4249)
Co-authored-by: Daniel Roe <daniel@roe.dev> Co-authored-by: pooya parsa <pyapar@gmail.com>
This commit is contained in:
parent
c88e1716ab
commit
38e0fa6e1e
@ -81,6 +81,7 @@ In this example, we use `<NuxtLink>` with `target`, `rel`, and `noRel` props.
|
|||||||
- **replace**: Works the same as [Vue Router's `replace` prop](https://router.vuejs.org/api/#replace) on internal links
|
- **replace**: Works the same as [Vue Router's `replace` prop](https://router.vuejs.org/api/#replace) on internal links
|
||||||
- **ariaCurrentValue**: An `aria-current` attribute value to apply on exact active links. Works the same as [Vue Router's `aria-current-value` prop](https://router.vuejs.org/api/#aria-current-value) on internal links
|
- **ariaCurrentValue**: An `aria-current` attribute value to apply on exact active links. Works the same as [Vue Router's `aria-current-value` prop](https://router.vuejs.org/api/#aria-current-value) on internal links
|
||||||
- **external**: Forces the link to be considered as external (`true`) or internal (`false`). This is helpful to handle edge-cases
|
- **external**: Forces the link to be considered as external (`true`) or internal (`false`). This is helpful to handle edge-cases
|
||||||
|
- **custom**: Whether `<NuxtLink>` should wrap its content in an `<a>` element. It allows taking full control of how a link is rendered and how navigation works when it is clicked. Works the same as [Vue Router's `custom` prop](https://router.vuejs.org/api/#custom)
|
||||||
|
|
||||||
::alert{icon=👉}
|
::alert{icon=👉}
|
||||||
Defaults can be overwritten, see [overwriting defaults](#overwriting-defaults) if you want to change them.
|
Defaults can be overwritten, see [overwriting defaults](#overwriting-defaults) if you want to change them.
|
||||||
|
@ -3,9 +3,17 @@
|
|||||||
<NuxtLink to="/about">
|
<NuxtLink to="/about">
|
||||||
About page
|
About page
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
<NuxtLink v-slot="{ navigate }" to="/about" custom>
|
||||||
|
<button @click="navigate">
|
||||||
|
Custom about page
|
||||||
|
</button>
|
||||||
|
</NuxtLink>
|
||||||
<NuxtLink to="https://nuxtjs.org">
|
<NuxtLink to="https://nuxtjs.org">
|
||||||
Nuxt website
|
Nuxt website
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
<NuxtLink v-slot="{ href, target }" to="https://nuxtjs.org" custom>
|
||||||
|
<a :href="href" :target="target">Go to {{ href }}</a>
|
||||||
|
</NuxtLink>
|
||||||
<NuxtLink to="https://twitter.com/nuxt_js" target="_blank">
|
<NuxtLink to="https://twitter.com/nuxt_js" target="_blank">
|
||||||
Nuxt Twitter with a blank target
|
Nuxt Twitter with a blank target
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
@ -23,6 +23,13 @@ const timer = useState('timer', () => 0)
|
|||||||
<NuxtLink to="/redirect" class="n-link-base">
|
<NuxtLink to="/redirect" class="n-link-base">
|
||||||
Redirect
|
Redirect
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
<NuxtLink custom to="/redirect">
|
||||||
|
<template #default="{ href, navigate }">
|
||||||
|
<button @click="navigate">
|
||||||
|
Custom: {{ href }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</NuxtLink>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -2,37 +2,38 @@ import { defineComponent, h, resolveComponent, PropType, computed, DefineCompone
|
|||||||
import { RouteLocationRaw, Router } from 'vue-router'
|
import { RouteLocationRaw, Router } from 'vue-router'
|
||||||
import { hasProtocol } from 'ufo'
|
import { hasProtocol } from 'ufo'
|
||||||
|
|
||||||
import { useRouter } from '#app'
|
import { navigateTo, useRouter } from '#app'
|
||||||
|
|
||||||
const firstNonUndefined = <T>(...args: T[]): T => args.find(arg => arg !== undefined)
|
const firstNonUndefined = <T>(...args: T[]): T => args.find(arg => arg !== undefined)
|
||||||
|
|
||||||
const DEFAULT_EXTERNAL_REL_ATTRIBUTE = 'noopener noreferrer'
|
const DEFAULT_EXTERNAL_REL_ATTRIBUTE = 'noopener noreferrer'
|
||||||
|
|
||||||
export type NuxtLinkOptions = {
|
export type NuxtLinkOptions = {
|
||||||
componentName?: string;
|
componentName?: string
|
||||||
externalRelAttribute?: string | null;
|
externalRelAttribute?: string | null
|
||||||
activeClass?: string;
|
activeClass?: string
|
||||||
exactActiveClass?: string;
|
exactActiveClass?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NuxtLinkProps = {
|
export type NuxtLinkProps = {
|
||||||
// Routing
|
// Routing
|
||||||
to?: string | RouteLocationRaw;
|
to?: string | RouteLocationRaw
|
||||||
href?: string | RouteLocationRaw;
|
href?: string | RouteLocationRaw
|
||||||
external?: boolean;
|
external?: boolean
|
||||||
|
replace?: boolean
|
||||||
|
custom?: boolean
|
||||||
|
|
||||||
// Attributes
|
// Attributes
|
||||||
target?: string;
|
target?: string
|
||||||
rel?: string;
|
rel?: string
|
||||||
noRel?: boolean;
|
noRel?: boolean
|
||||||
|
|
||||||
// Styling
|
// Styling
|
||||||
activeClass?: string;
|
activeClass?: string
|
||||||
exactActiveClass?: string;
|
exactActiveClass?: string
|
||||||
|
|
||||||
// Vue Router's `<RouterLink>` additional props
|
// Vue Router's `<RouterLink>` additional props
|
||||||
replace?: boolean;
|
ariaCurrentValue?: string
|
||||||
ariaCurrentValue?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function defineNuxtLink (options: NuxtLinkOptions) {
|
export function defineNuxtLink (options: NuxtLinkOptions) {
|
||||||
@ -154,9 +155,9 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
activeClass: props.activeClass || options.activeClass,
|
activeClass: props.activeClass || options.activeClass,
|
||||||
exactActiveClass: props.exactActiveClass || options.exactActiveClass,
|
exactActiveClass: props.exactActiveClass || options.exactActiveClass,
|
||||||
replace: props.replace,
|
replace: props.replace,
|
||||||
ariaCurrentValue: props.ariaCurrentValue
|
ariaCurrentValue: props.ariaCurrentValue,
|
||||||
|
custom: props.custom
|
||||||
},
|
},
|
||||||
// TODO: Slot API
|
|
||||||
slots.default
|
slots.default
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -175,6 +176,22 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
// converts `""` to `null` to prevent the attribute from being added as empty (`rel=""`)
|
// converts `""` to `null` to prevent the attribute from being added as empty (`rel=""`)
|
||||||
: firstNonUndefined<string | null>(props.rel, options.externalRelAttribute, href ? DEFAULT_EXTERNAL_REL_ATTRIBUTE : '') || null
|
: firstNonUndefined<string | null>(props.rel, options.externalRelAttribute, href ? DEFAULT_EXTERNAL_REL_ATTRIBUTE : '') || null
|
||||||
|
|
||||||
|
const navigate = () => navigateTo(href, { replace: props.replace })
|
||||||
|
|
||||||
|
// https://router.vuejs.org/api/#custom
|
||||||
|
if (props.custom) {
|
||||||
|
if (!slots.default) { return null }
|
||||||
|
return slots.default({
|
||||||
|
href,
|
||||||
|
navigate,
|
||||||
|
route: router.resolve(href),
|
||||||
|
rel,
|
||||||
|
target,
|
||||||
|
isActive: false,
|
||||||
|
isExactActive: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return h('a', { href, rel, target }, slots.default?.())
|
return h('a', { href, rel, target }, slots.default?.())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,8 +179,24 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
|
|||||||
|
|
||||||
nuxtApp.vueApp.component('RouterLink', {
|
nuxtApp.vueApp.component('RouterLink', {
|
||||||
functional: true,
|
functional: true,
|
||||||
props: { to: String },
|
props: {
|
||||||
setup: (props, { slots }) => () => h('a', { href: props.to, onClick: (e) => { e.preventDefault(); router.push(props.to) } }, slots)
|
to: String,
|
||||||
|
custom: Boolean,
|
||||||
|
replace: Boolean,
|
||||||
|
// Not implemented
|
||||||
|
activeClass: String,
|
||||||
|
exactActiveClass: String,
|
||||||
|
ariaCurrentValue: String
|
||||||
|
},
|
||||||
|
setup: (props, { slots }) => {
|
||||||
|
const navigate = () => handleNavigation(props.to, props.replace)
|
||||||
|
return () => {
|
||||||
|
const route = router.resolve(props.to)
|
||||||
|
return props.custom
|
||||||
|
? slots.default?.({ href: props.to, navigate, route })
|
||||||
|
: h('a', { href: props.to, onClick: (e) => { e.preventDefault(); return navigate() } }, slots)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (process.client) {
|
if (process.client) {
|
||||||
|
@ -81,7 +81,10 @@ export default defineNuxtModule({
|
|||||||
}
|
}
|
||||||
|
|
||||||
nuxt.hook('autoImports:extend', (autoImports) => {
|
nuxt.hook('autoImports:extend', (autoImports) => {
|
||||||
autoImports.push({ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') })
|
autoImports.push(
|
||||||
|
{ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') },
|
||||||
|
{ name: 'useLink', as: 'useLink', from: 'vue-router' }
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Extract macros from pages
|
// Extract macros from pages
|
||||||
|
Loading…
Reference in New Issue
Block a user