fix(Link): ensure consistency across Nuxt, Vue and Inertia · nuxt/ui@a9ed10d

@@ -29,6 +29,10 @@ export interface LinkProps extends Partial<Omit<RouterLinkProps, 'custom'>>, /**

2929

* A rel attribute value to apply on the link. Defaults to "noopener noreferrer" for external links.

3030

*/

3131

rel?: 'noopener' | 'noreferrer' | 'nofollow' | 'sponsored' | 'ugc' | (string & {}) | null

32+

/**

33+

* If set to true, no rel attribute will be added to the link

34+

*/

35+

noRel?: boolean

3236

/**

3337

* The type of the button when not a link.

3438

* @defaultValue 'button'

@@ -66,6 +70,7 @@ import { hasProtocol } from 'ufo'

6670

import { useRoute, RouterLink } from 'vue-router'

6771

import { useAppConfig } from '#imports'

6872

import { tv } from '../../utils/tv'

73+

import { mergeClasses } from '../../utils'

6974

import { isPartiallyEqual } from '../../utils/link'

7075

import ULinkBase from '../../components/LinkBase.vue'

7176

@@ -75,25 +80,23 @@ const props = withDefaults(defineProps<LinkProps>(), {

7580

as: 'button',

7681

type: 'button',

7782

ariaCurrentValue: 'page',

78-

active: undefined,

79-

activeClass: '',

80-

inactiveClass: ''

83+

active: undefined

8184

})

8285

defineSlots<LinkSlots>()

83868487

const route = useRoute()

85888689

const appConfig = useAppConfig() as Link['AppConfig']

879088-

const routerLinkProps = useForwardProps(reactiveOmit(props, 'as', 'type', 'disabled', 'active', 'exact', 'exactQuery', 'exactHash', 'activeClass', 'inactiveClass', 'to', 'href', 'raw', 'custom', 'class'))

91+

const routerLinkProps = useForwardProps(reactiveOmit(props, 'as', 'type', 'disabled', 'active', 'exact', 'exactQuery', 'exactHash', 'activeClass', 'inactiveClass', 'to', 'href', 'raw', 'custom', 'class', 'noRel'))

89929093

const ui = computed(() => tv({

9194

extend: tv(theme),

9295

...defu({

9396

variants: {

9497

active: {

95-

true: props.activeClass,

96-

false: props.inactiveClass

98+

true: mergeClasses(appConfig.ui?.link?.variants?.active?.true, props.activeClass),

99+

false: mergeClasses(appConfig.ui?.link?.variants?.active?.false, props.inactiveClass)

97100

}

98101

}

99102

}, appConfig.ui?.link || {})

@@ -113,6 +116,27 @@ const isExternal = computed(() => {

113116

return typeof to.value === 'string' && hasProtocol(to.value, { acceptRelative: true })

114117

})

115118119+

const hasTarget = computed(() => !!props.target && props.target !== '_self')

120+121+

const rel = computed(() => {

122+

// If noRel is explicitly set, return null

123+

if (props.noRel) {

124+

return null

125+

}

126+127+

// If rel is explicitly set, use it

128+

if (props.rel !== undefined) {

129+

return props.rel || null

130+

}

131+132+

// Default to "noopener noreferrer" for external links or links with target

133+

if (isExternal.value || hasTarget.value) {

134+

return 'noopener noreferrer'

135+

}

136+137+

return null

138+

})

139+116140

function isLinkActive({ route: linkRoute, isActive, isExactActive }: any) {

117141

if (props.active !== undefined) {

118142

return props.active

@@ -168,6 +192,9 @@ function resolveLinkClass({ route, isActive, isExactActive }: any = {}) {

168192

disabled,

169193

href,

170194

navigate,

195+

rel,

196+

target,

197+

isExternal,

171198

active: isLinkActive({ route: linkRoute, isActive, isExactActive })

172199

}"

173200

/>

@@ -181,7 +208,10 @@ function resolveLinkClass({ route, isActive, isExactActive }: any = {}) {

181208

type,

182209

disabled,

183210

href,

184-

navigate

211+

navigate,

212+

rel,

213+

target,

214+

isExternal

185215

}"

186216

:class="resolveLinkClass({ route: linkRoute, isActive, isExactActive })"

187217

>

@@ -199,7 +229,8 @@ function resolveLinkClass({ route, isActive, isExactActive }: any = {}) {

199229

type,

200230

disabled,

201231

href: to,

202-

target: isExternal ? '_blank' : undefined,

232+

rel,

233+

target,

203234

active,

204235

isExternal

205236

}"

@@ -213,7 +244,8 @@ function resolveLinkClass({ route, isActive, isExactActive }: any = {}) {

213244

type,

214245

disabled,

215246

href: (to as string),

216-

target: isExternal ? '_blank' : undefined,

247+

rel,

248+

target,

217249

isExternal

218250

}"

219251

:class="resolveLinkClass()"