From 38e0fa6e1e884763c1a8262bd5162121d4dc2f4b Mon Sep 17 00:00:00 2001 From: Francisco Buceta Date: Thu, 7 Jul 2022 19:28:23 +0200 Subject: [PATCH] feat(nuxt3): support `custom` prop for `` (#4249) Co-authored-by: Daniel Roe Co-authored-by: pooya parsa --- .../content/3.api/2.components/4.nuxt-link.md | 1 + examples/routing/nuxt-link/pages/index.vue | 8 +++ examples/routing/universal-router/app.vue | 7 +++ packages/nuxt/src/app/components/nuxt-link.ts | 51 ++++++++++++------- packages/nuxt/src/app/plugins/router.ts | 20 +++++++- packages/nuxt/src/pages/module.ts | 5 +- 6 files changed, 72 insertions(+), 20 deletions(-) diff --git a/docs/content/3.api/2.components/4.nuxt-link.md b/docs/content/3.api/2.components/4.nuxt-link.md index 51e66a694c..95ecbf417e 100644 --- a/docs/content/3.api/2.components/4.nuxt-link.md +++ b/docs/content/3.api/2.components/4.nuxt-link.md @@ -81,6 +81,7 @@ In this example, we use `` 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 - **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 +- **custom**: Whether `` should wrap its content in an `` 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=👉} Defaults can be overwritten, see [overwriting defaults](#overwriting-defaults) if you want to change them. diff --git a/examples/routing/nuxt-link/pages/index.vue b/examples/routing/nuxt-link/pages/index.vue index f12ad59916..230bd2698a 100644 --- a/examples/routing/nuxt-link/pages/index.vue +++ b/examples/routing/nuxt-link/pages/index.vue @@ -3,9 +3,17 @@ About page + + + Nuxt website + + Go to {{ href }} + Nuxt Twitter with a blank target diff --git a/examples/routing/universal-router/app.vue b/examples/routing/universal-router/app.vue index f28d201649..eb7103262a 100644 --- a/examples/routing/universal-router/app.vue +++ b/examples/routing/universal-router/app.vue @@ -23,6 +23,13 @@ const timer = useState('timer', () => 0) Redirect + + + diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index 471dfe67f2..d6d086444c 100644 --- a/packages/nuxt/src/app/components/nuxt-link.ts +++ b/packages/nuxt/src/app/components/nuxt-link.ts @@ -2,37 +2,38 @@ import { defineComponent, h, resolveComponent, PropType, computed, DefineCompone import { RouteLocationRaw, Router } from 'vue-router' import { hasProtocol } from 'ufo' -import { useRouter } from '#app' +import { navigateTo, useRouter } from '#app' const firstNonUndefined = (...args: T[]): T => args.find(arg => arg !== undefined) const DEFAULT_EXTERNAL_REL_ATTRIBUTE = 'noopener noreferrer' export type NuxtLinkOptions = { - componentName?: string; - externalRelAttribute?: string | null; - activeClass?: string; - exactActiveClass?: string; + componentName?: string + externalRelAttribute?: string | null + activeClass?: string + exactActiveClass?: string } export type NuxtLinkProps = { // Routing - to?: string | RouteLocationRaw; - href?: string | RouteLocationRaw; - external?: boolean; + to?: string | RouteLocationRaw + href?: string | RouteLocationRaw + external?: boolean + replace?: boolean + custom?: boolean // Attributes - target?: string; - rel?: string; - noRel?: boolean; + target?: string + rel?: string + noRel?: boolean // Styling - activeClass?: string; - exactActiveClass?: string; + activeClass?: string + exactActiveClass?: string // Vue Router's `` additional props - replace?: boolean; - ariaCurrentValue?: string; + ariaCurrentValue?: string }; export function defineNuxtLink (options: NuxtLinkOptions) { @@ -154,9 +155,9 @@ export function defineNuxtLink (options: NuxtLinkOptions) { activeClass: props.activeClass || options.activeClass, exactActiveClass: props.exactActiveClass || options.exactActiveClass, replace: props.replace, - ariaCurrentValue: props.ariaCurrentValue + ariaCurrentValue: props.ariaCurrentValue, + custom: props.custom }, - // TODO: Slot API slots.default ) } @@ -175,6 +176,22 @@ export function defineNuxtLink (options: NuxtLinkOptions) { // converts `""` to `null` to prevent the attribute from being added as empty (`rel=""`) : firstNonUndefined(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?.()) } } diff --git a/packages/nuxt/src/app/plugins/router.ts b/packages/nuxt/src/app/plugins/router.ts index 09da32b539..50e5d6bae1 100644 --- a/packages/nuxt/src/app/plugins/router.ts +++ b/packages/nuxt/src/app/plugins/router.ts @@ -179,8 +179,24 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => { nuxtApp.vueApp.component('RouterLink', { functional: true, - props: { to: String }, - setup: (props, { slots }) => () => h('a', { href: props.to, onClick: (e) => { e.preventDefault(); router.push(props.to) } }, slots) + props: { + 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) { diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts index c5f35f9000..65518a6a38 100644 --- a/packages/nuxt/src/pages/module.ts +++ b/packages/nuxt/src/pages/module.ts @@ -81,7 +81,10 @@ export default defineNuxtModule({ } 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