<li>
<router-link to="/">Home</router-link>
</li>
+ <li>
+ <AppLink to="/">AppLink Home</AppLink>
+ </li>
<li>
<router-link to="/always-redirect">/always-redirect</router-link>
</li>
<script>
import { defineComponent, inject, computed, ref } from 'vue'
import { scrollWaiter } from './scrollWaiter'
-import { useRoute } from '../src'
+import { useLink, useRoute } from '../src'
+import AppLink from './AppLink.vue'
export default defineComponent({
name: 'App',
+ components: { AppLink },
setup() {
const route = useRoute()
const state = inject('state')
const viewName = ref('default')
+ useLink({ to: '/' })
+ useLink({ to: '/documents/hello' })
+ useLink({ to: '/children' })
+
const currentLocation = computed(() => {
const { matched, ...rest } = route
return rest
--- /dev/null
+<template>
+ <a
+ v-if="isExternalLink"
+ v-bind="attrs"
+ class="router-link"
+ :class="classes"
+ target="_blank"
+ rel="noopener noreferrer"
+ :href="to"
+ :tabindex="disabled ? -1 : undefined"
+ :aria-disabled="disabled"
+ >
+ <slot />
+ </a>
+ <a
+ v-else
+ v-bind="attrs"
+ class="router-link"
+ :class="classes"
+ :href="href"
+ :tabindex="disabled ? -1 : undefined"
+ :aria-disabled="disabled"
+ @click="navigate"
+ >
+ <slot />
+ </a>
+</template>
+
+<script>
+import { RouterLinkImpl } from '../src/RouterLink'
+import { computed, defineComponent, toRefs } from 'vue'
+import { START_LOCATION, useLink, useRoute } from '../src'
+
+export default defineComponent({
+ props: {
+ ...RouterLinkImpl.props,
+ disabled: Boolean,
+ },
+
+ setup(props, { attrs }) {
+ const { replace, to, disabled } = toRefs(props)
+ const isExternalLink = computed(
+ () => typeof to.value === 'string' && to.value.startsWith('http')
+ )
+
+ const currentRoute = useRoute()
+
+ const { route, href, isActive, isExactActive, navigate } = useLink({
+ to: computed(() => (isExternalLink.value ? START_LOCATION : to.value)),
+ replace,
+ })
+
+ const classes = computed(() => ({
+ // allow link to be active for unrelated routes
+ 'router-link-active':
+ isActive.value || currentRoute.path.startsWith(route.value.path),
+ 'router-link-exact-active':
+ isExactActive.value || currentRoute.path === route.value.path,
+ }))
+
+ return { attrs, isExternalLink, href, navigate, classes, disabled }
+ },
+})
+</script>
.router-link-exact-active {
color: red;
}
+ .router-link {
+ padding: 2px;
+ display: block;
+ border: 1px solid red;
+ }
.long {
background-color: lightgray;
height: 3000px;
getCurrentInstance,
watchEffect,
} from 'vue'
-import { RouteLocationRaw, VueUseOptions, RouteLocation } from './types'
+import {
+ RouteLocationRaw,
+ VueUseOptions,
+ RouteLocation,
+ RouteLocationNormalized,
+} from './types'
import { isSameRouteLocationParams, isSameRouteRecord } from './location'
import { routerKey, routeLocationKey } from './injectionSymbols'
import { RouteRecord } from './matcher/types'
| 'false'
}
+export interface UseLinkDevtoolsContext {
+ route: RouteLocationNormalized & { href: string }
+ isActive: boolean
+ isExactActive: boolean
+}
+
export type UseLinkOptions = VueUseOptions<RouterLinkOptions>
// TODO: we could allow currentRoute as a prop to expose `isActive` and
return Promise.resolve()
}
+ // devtools only
+ if ((__DEV__ || __FEATURE_PROD_DEVTOOLS__) && isBrowser) {
+ const instance = getCurrentInstance()
+ if (!instance) return
+ const linkContextDevtools: UseLinkDevtoolsContext = {
+ route: route.value,
+ isActive: isActive.value,
+ isExactActive: isExactActive.value,
+ }
+
+ // @ts-expect-error: this is internal
+ instance.__vrl_devtools = instance.__vrl_devtools || []
+ // @ts-expect-error: this is internal
+ instance.__vrl_devtools.push(linkContextDevtools)
+ watchEffect(
+ () => {
+ linkContextDevtools.route = route.value
+ linkContextDevtools.isActive = isActive.value
+ linkContextDevtools.isExactActive = isExactActive.value
+ },
+ { flush: 'post' }
+ )
+ }
+
return {
route,
href: computed(() => route.value.href),
)]: link.isExactActive,
}))
- // devtools only
- if ((__DEV__ || __FEATURE_PROD_DEVTOOLS__) && isBrowser) {
- const instance = getCurrentInstance()
- watchEffect(
- () => {
- if (!instance) return
- ;(instance as any).__vrl_route = link.route
- },
- { flush: 'post' }
- )
- watchEffect(
- () => {
- if (!instance) return
- ;(instance as any).__vrl_active = link.isActive
- ;(instance as any).__vrl_exactActive = link.isExactActive
- },
- { flush: 'post' }
- )
- }
-
return () => {
const children = slots.default && slots.default(link)
return props.custom
import { RouteRecordMatcher } from './matcher/pathMatcher'
import { PathParser } from './matcher/pathParserRanker'
import { Router } from './router'
+import { UseLinkDevtoolsContext } from './RouterLink'
import { RouteLocation, RouteLocationNormalized } from './types'
import { assign } from './utils'
// mark router-link as active
api.on.visitComponentTree(({ treeNode: node, componentInstance }) => {
- if (node.name === 'RouterLink') {
- if (componentInstance.__vrl_route) {
- node.tags.push({
- label: (componentInstance.__vrl_route as RouteLocation).path,
- textColor: 0,
- backgroundColor: ORANGE_400,
- })
- }
-
- if (componentInstance.__vrl_exactActive) {
- node.tags.push({
- label: 'exact',
- textColor: 0,
- backgroundColor: LIME_500,
- })
- }
+ // if multiple useLink are used
+ if (Array.isArray(componentInstance.__vrl_devtools)) {
+ componentInstance.__devtoolsApi = api
+ ;(
+ componentInstance.__vrl_devtools as UseLinkDevtoolsContext[]
+ ).forEach(devtoolsData => {
+ let backgroundColor = ORANGE_400
+ let tooltip: string = ''
+
+ if (devtoolsData.isExactActive) {
+ backgroundColor = LIME_500
+ tooltip = 'This is exactly active'
+ } else if (devtoolsData.isActive) {
+ backgroundColor = BLUE_600
+ tooltip = 'This link is active'
+ }
- if (componentInstance.__vrl_active) {
node.tags.push({
- label: 'active',
+ label: devtoolsData.route.path,
textColor: 0,
- backgroundColor: BLUE_600,
+ tooltip,
+ backgroundColor,
})
- }
+ })
}
})
refreshRoutesView()
api.notifyComponentUpdate()
api.sendInspectorTree(routerInspectorId)
+ api.sendInspectorState(routerInspectorId)
})
const navigationsLayerId = 'router:navigations:' + id