From a18dd20fa257b1255f461eb881bfb528580315c5 Mon Sep 17 00:00:00 2001 From: Francisco Buceta Date: Mon, 11 Apr 2022 10:51:30 +0200 Subject: [PATCH 01/11] fix(nuxt3): nuxt-link custom prop is not being passed to router-link --- packages/nuxt3/src/app/components/nuxt-link.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/nuxt3/src/app/components/nuxt-link.ts b/packages/nuxt3/src/app/components/nuxt-link.ts index 1407289285a..e33e6d3cc5d 100644 --- a/packages/nuxt3/src/app/components/nuxt-link.ts +++ b/packages/nuxt3/src/app/components/nuxt-link.ts @@ -154,7 +154,8 @@ 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,7 +176,9 @@ 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 - return h('a', { href, rel, target }, slots.default()) + return props.custom + ? slots.default && slots.default({ href, rel, target }) + : h('a', { href, rel, target }, slots.default()) } } }) as unknown as DefineComponent From 31f0a585d211b6f6e4c8685acefe68d3ade9e83e Mon Sep 17 00:00:00 2001 From: Francisco Buceta Date: Mon, 11 Apr 2022 11:06:00 +0200 Subject: [PATCH 02/11] chore(examples): add nuxt-link sample with custom prop --- examples/routing/nuxt-link/pages/index.vue | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/routing/nuxt-link/pages/index.vue b/examples/routing/nuxt-link/pages/index.vue index f12ad599164..230bd2698a1 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 From 5d617e74be60cf9842ca3d830dcf773022d4da76 Mon Sep 17 00:00:00 2001 From: Francisco Buceta Date: Mon, 11 Apr 2022 11:07:21 +0200 Subject: [PATCH 03/11] docs: update StackBlitz links on the nuxt-link page --- docs/content/3.api/2.components/4.nuxt-link.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 250642d76e1..0c423093008 100644 --- a/docs/content/3.api/2.components/4.nuxt-link.md +++ b/docs/content/3.api/2.components/4.nuxt-link.md @@ -22,7 +22,7 @@ In this example, we use `` component to link to a website. ``` -:button-link[Open on StackBlitz]{href="https://stackblitz.com/github/nuxt/framework/tree/main/examples/nuxt-link?terminal=dev" blank} +:button-link[Open on StackBlitz]{href="https://stackblitz.com/github/nuxt/framework/tree/main/examples/routing/nuxt-link?terminal=dev" blank} ### Internal routing @@ -37,7 +37,7 @@ In this example, we use `` component to link to another page of the ap ``` -:button-link[Open on StackBlitz]{href="https://stackblitz.com/github/nuxt/framework/tree/main/examples/nuxt-link?terminal=dev" blank} +:button-link[Open on StackBlitz]{href="https://stackblitz.com/github/nuxt/framework/tree/main/examples/routing/nuxt-link?terminal=dev" blank} ### `target` and `rel` attributes @@ -67,7 +67,7 @@ In this example, we use `` with `target`, `rel`, and `noRel` props. ``` -:button-link[Open on StackBlitz]{href="https://stackblitz.com/github/nuxt/framework/tree/main/examples/nuxt-link?terminal=dev" blank} +:button-link[Open on StackBlitz]{href="https://stackblitz.com/github/nuxt/framework/tree/main/examples/routing/nuxt-link?terminal=dev" blank} ## Props @@ -99,7 +99,7 @@ export default defineNuxtLink({ You can then use `` component as usual with your new defaults. -:button-link[Open on StackBlitz]{href="https://stackblitz.com/github/nuxt/framework/tree/main/examples/nuxt-link-pages?terminal=dev" blank} +:button-link[Open on StackBlitz]{href="https://stackblitz.com/github/nuxt/framework/tree/main/examples/routing/nuxt-link?terminal=dev" blank} ### `defineNuxtLink` signature From 35d63b4ffbd49c603a71ebdfd39f5984df5512b6 Mon Sep 17 00:00:00 2001 From: Francisco Buceta Date: Mon, 11 Apr 2022 11:15:16 +0200 Subject: [PATCH 04/11] docs: add custom prop to nuxt-link --- docs/content/3.api/2.components/4.nuxt-link.md | 1 + 1 file changed, 1 insertion(+) 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 0c423093008..e62eabdc05d 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**: Works the same as [Vue Router's `custom` prop](https://router.vuejs.org/api/#custom) ::alert{icon=👉} Defaults can be overwriten, see [overwriting defaults](#overwriting-defaults) if you want to change them. From 0aefbb1750ad758a8b1bd608847ebd48d34cfa2f Mon Sep 17 00:00:00 2001 From: Francisco Buceta Date: Mon, 11 Apr 2022 13:02:04 +0200 Subject: [PATCH 05/11] feat(nuxt3): add useLink composable --- packages/nuxt3/src/app/composables/index.ts | 2 +- packages/nuxt3/src/app/composables/router.ts | 2 ++ packages/nuxt3/src/auto-imports/presets.ts | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/nuxt3/src/app/composables/index.ts b/packages/nuxt3/src/app/composables/index.ts index 2780a248ca6..ca3517b7914 100644 --- a/packages/nuxt3/src/app/composables/index.ts +++ b/packages/nuxt3/src/app/composables/index.ts @@ -9,5 +9,5 @@ export type { FetchResult, UseFetchOptions } from './fetch' export { useCookie } from './cookie' export type { CookieOptions, CookieRef } from './cookie' export { useRequestHeaders, useRequestEvent } from './ssr' -export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, navigateTo, useRoute, useActiveRoute, useRouter } from './router' +export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, navigateTo, useRoute, useActiveRoute, useRouter, useLink } from './router' export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router' diff --git a/packages/nuxt3/src/app/composables/router.ts b/packages/nuxt3/src/app/composables/router.ts index e5b9c7199e5..653346c60ff 100644 --- a/packages/nuxt3/src/app/composables/router.ts +++ b/packages/nuxt3/src/app/composables/router.ts @@ -2,6 +2,8 @@ import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocat import { sendRedirect } from 'h3' import { useNuxtApp } from '#app' +export { useLink } from 'vue-router' + export const useRouter = () => { return useNuxtApp()?.$router as Router } diff --git a/packages/nuxt3/src/auto-imports/presets.ts b/packages/nuxt3/src/auto-imports/presets.ts index cf2fd30dea5..ef8b5cc56d1 100644 --- a/packages/nuxt3/src/auto-imports/presets.ts +++ b/packages/nuxt3/src/auto-imports/presets.ts @@ -45,7 +45,8 @@ export const appPreset = defineUnimportPreset({ 'throwError', 'clearError', 'useError', - 'defineNuxtLink' + 'defineNuxtLink', + 'useLink' ] }) From 9ded9583ee0b3aae54c0d9621b9eabe5b62d4e81 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 9 Jun 2022 15:30:29 +0100 Subject: [PATCH 06/11] fix: add universal routerlink implementation of custom link --- examples/routing/universal-router/app.vue | 7 +++++++ packages/nuxt/src/app/plugins/router.ts | 20 ++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/examples/routing/universal-router/app.vue b/examples/routing/universal-router/app.vue index f28d201649f..eb7103262a2 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/plugins/router.ts b/packages/nuxt/src/app/plugins/router.ts index 641d21c5074..93671e0b0ba 100644 --- a/packages/nuxt/src/app/plugins/router.ts +++ b/packages/nuxt/src/app/plugins/router.ts @@ -177,8 +177,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) { From 3d991c7cecc928835a90f04f64d630c004012444 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 9 Jun 2022 15:30:53 +0100 Subject: [PATCH 07/11] docs: add a brief explanation --- docs/content/3.api/2.components/4.nuxt-link.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8b23be603e8..95ecbf417e3 100644 --- a/docs/content/3.api/2.components/4.nuxt-link.md +++ b/docs/content/3.api/2.components/4.nuxt-link.md @@ -81,7 +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**: Works the same as [Vue Router's `custom` prop](https://router.vuejs.org/api/#custom) +- **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. From 7848865bccdba839f8cd29fdcdc505493a4963c3 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 9 Jun 2022 15:36:53 +0100 Subject: [PATCH 08/11] fix: add `custom` prop to types and pass `navigate` to slot --- packages/nuxt/src/app/components/nuxt-link.ts | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index 41a2d3a5837..757e6cd6c65 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) { @@ -157,7 +158,6 @@ export function defineNuxtLink (options: NuxtLinkOptions) { ariaCurrentValue: props.ariaCurrentValue, custom: props.custom }, - // TODO: Slot API slots.default ) } @@ -176,8 +176,10 @@ 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 }) + return props.custom - ? slots.default && slots.default({ href, rel, target }) + ? slots.default && slots.default({ href, rel, target, navigate, isActive: false, isExactActive: false }) : h('a', { href, rel, target }, slots.default?.()) } } From 93ab8850e5d80b6a287738da8811307b7b050233 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 9 Jun 2022 15:41:20 +0100 Subject: [PATCH 09/11] fix: add `useLink` only with pages ad-hoc module (for now) --- packages/nuxt/src/app/composables/index.ts | 2 +- packages/nuxt/src/app/composables/router.ts | 2 -- packages/nuxt/src/auto-imports/presets.ts | 3 +-- packages/nuxt/src/pages/module.ts | 5 ++++- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/nuxt/src/app/composables/index.ts b/packages/nuxt/src/app/composables/index.ts index ca3517b7914..2780a248ca6 100644 --- a/packages/nuxt/src/app/composables/index.ts +++ b/packages/nuxt/src/app/composables/index.ts @@ -9,5 +9,5 @@ export type { FetchResult, UseFetchOptions } from './fetch' export { useCookie } from './cookie' export type { CookieOptions, CookieRef } from './cookie' export { useRequestHeaders, useRequestEvent } from './ssr' -export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, navigateTo, useRoute, useActiveRoute, useRouter, useLink } from './router' +export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, navigateTo, useRoute, useActiveRoute, useRouter } from './router' export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router' diff --git a/packages/nuxt/src/app/composables/router.ts b/packages/nuxt/src/app/composables/router.ts index 61d279a047f..1ec7e83a62b 100644 --- a/packages/nuxt/src/app/composables/router.ts +++ b/packages/nuxt/src/app/composables/router.ts @@ -3,8 +3,6 @@ import { sendRedirect } from 'h3' import { joinURL } from 'ufo' import { useNuxtApp, useRuntimeConfig } from '#app' -export { useLink } from 'vue-router' - export const useRouter = () => { return useNuxtApp()?.$router as Router } diff --git a/packages/nuxt/src/auto-imports/presets.ts b/packages/nuxt/src/auto-imports/presets.ts index ef8b5cc56d1..cf2fd30dea5 100644 --- a/packages/nuxt/src/auto-imports/presets.ts +++ b/packages/nuxt/src/auto-imports/presets.ts @@ -45,8 +45,7 @@ export const appPreset = defineUnimportPreset({ 'throwError', 'clearError', 'useError', - 'defineNuxtLink', - 'useLink' + 'defineNuxtLink' ] }) diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts index c5c9e38102d..cc41a7e97fb 100644 --- a/packages/nuxt/src/pages/module.ts +++ b/packages/nuxt/src/pages/module.ts @@ -51,7 +51,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 From 8ac1c7dc048fd23877c00c2d223971d267bb1c8e Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 7 Jul 2022 19:22:44 +0200 Subject: [PATCH 10/11] add route polyfill for external custom link --- packages/nuxt/src/app/components/nuxt-link.ts | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index 757e6cd6c65..53614c61b29 100644 --- a/packages/nuxt/src/app/components/nuxt-link.ts +++ b/packages/nuxt/src/app/components/nuxt-link.ts @@ -178,9 +178,31 @@ export function defineNuxtLink (options: NuxtLinkOptions) { const navigate = () => navigateTo(href, { replace: props.replace }) - return props.custom - ? slots.default && slots.default({ href, rel, target, navigate, isActive: false, isExactActive: false }) - : h('a', { href, rel, target }, slots.default?.()) + // https://router.vuejs.org/api/#custom + if (props.custom) { + if (!slots.default) { return null } + const url = new URL(href) + return slots.default({ + href, + navigate, + route: { + path: url.pathname, + fullPath: url.href, + query: Object.fromEntries(url.searchParams.entries()), + hash: url.hash, + params: {}, + matched: [], + meta: {}, + href + }, + rel, + target, + isActive: false, + isExactActive: false + }) + } + + return h('a', { href, rel, target }, slots.default?.()) } } }) as unknown as DefineComponent From b4b2925a82e204effb60b2864f632b7270ec41f3 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 7 Jul 2022 19:27:32 +0200 Subject: [PATCH 11/11] refactor: use `router.resolve` --- packages/nuxt/src/app/components/nuxt-link.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index 53614c61b29..d6d086444c9 100644 --- a/packages/nuxt/src/app/components/nuxt-link.ts +++ b/packages/nuxt/src/app/components/nuxt-link.ts @@ -181,20 +181,10 @@ export function defineNuxtLink (options: NuxtLinkOptions) { // https://router.vuejs.org/api/#custom if (props.custom) { if (!slots.default) { return null } - const url = new URL(href) return slots.default({ href, navigate, - route: { - path: url.pathname, - fullPath: url.href, - query: Object.fromEntries(url.searchParams.entries()), - hash: url.hash, - params: {}, - matched: [], - meta: {}, - href - }, + route: router.resolve(href), rel, target, isActive: false,