]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat(devtools): display components using `useLink()`
authorEduardo San Martin Morote <posva13@gmail.com>
Fri, 18 Jun 2021 12:27:46 +0000 (14:27 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Fri, 18 Jun 2021 12:28:34 +0000 (14:28 +0200)
Close #1003

playground/App.vue
playground/AppLink.vue [new file with mode: 0644]
playground/index.html
src/RouterLink.ts
src/devtools.ts

index 20e7de8bb8afd5009a838e8718457531a12be65f..1e25a4d864a3442c22f3144c480adbcbf036330f 100644 (file)
@@ -66,6 +66,9 @@
       <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
diff --git a/playground/AppLink.vue b/playground/AppLink.vue
new file mode 100644 (file)
index 0000000..b28476a
--- /dev/null
@@ -0,0 +1,64 @@
+<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>
index 396abe5d461f44afa136aba3874b045fa38908d2..824c042acf07ff7ed462fd721696b092dfaad747 100644 (file)
       .router-link-exact-active {
         color: red;
       }
+      .router-link {
+        padding: 2px;
+        display: block;
+        border: 1px solid red;
+      }
       .long {
         background-color: lightgray;
         height: 3000px;
index 0dbef7140729a06172ceb95425d417e78343aac4..6be888bcd2c5e6c545cebc28d26a2ff271568ca7 100644 (file)
@@ -12,7 +12,12 @@ import {
   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'
@@ -58,6 +63,12 @@ export interface RouterLinkProps extends RouterLinkOptions {
     | '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
@@ -122,6 +133,30 @@ export function useLink(props: UseLinkOptions) {
     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),
@@ -173,26 +208,6 @@ export const RouterLinkImpl = /*#__PURE__*/ defineComponent({
       )]: 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
index c54b5e102c21e2d99eb59a5dea0d5cc0d9313a77..31b63d7b154c18ed57ad7db68f7ebb78544b12bf 100644 (file)
@@ -14,6 +14,7 @@ import { RouterMatcher } from './matcher'
 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'
 
@@ -87,30 +88,30 @@ export function addDevtools(app: App, router: Router, matcher: RouterMatcher) {
 
       // 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,
             })
-          }
+          })
         }
       })
 
@@ -119,6 +120,7 @@ export function addDevtools(app: App, router: Router, matcher: RouterMatcher) {
         refreshRoutesView()
         api.notifyComponentUpdate()
         api.sendInspectorTree(routerInspectorId)
+        api.sendInspectorState(routerInspectorId)
       })
 
       const navigationsLayerId = 'router:navigations:' + id