Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/1.features.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Shows all the components in your app and where they come from. Search for them a

The Graph view shows the relationships between components and the dependencies of each one. Its dependency edges need the optional [`vite-plugin-inspect`](#inspect) installed.

You can also inspect your app's DOM tree to find which component renders it. Turn the inspector off with `devtools.componentInspector: false`.
You can also inspect your app's DOM tree to find which component renders it. Start it with the **Inspect Vue component** button in the Vite DevTools dock, the button in the Components tab, or :kbd{value="Ctrl"} / :kbd{value="⌘ Cmd"} + :kbd{value="Shift"} + :kbd{value="X"}. The DevTools panel hides while you pick a component and while its details are shown, and comes back once you close them (with any of their actions, a click outside, or :kbd{value="Esc"}). Press :kbd{value="Esc"} or click the dock button again to cancel a pick. Turn the inspector off with `devtools.componentInspector: false`.

### Imports

Expand Down
7 changes: 6 additions & 1 deletion packages/devtools/src/integrations/vue-tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ export function setup({ nuxt, options }: NuxtDevtoolsServerContext) {
if (!options.componentInspector)
return

const plugin = VueTracer()
// `viteDevtools` adds vue-tracer's "Inspect Vue component" dock action.
// Picks are handled by Nuxt's own inspect panel (see `plugins/view/client`)
// instead of opening the editor right away.
const plugin = VueTracer({
viteDevtools: { onPick: 'none' },
})
if (plugin)
addVitePlugin(plugin)
}
94 changes: 81 additions & 13 deletions packages/devtools/src/runtime/plugins/view/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ import { initTimelineMetrics } from '../../function-metrics-helpers'
// used by every Nuxt tab.
const NUXT_DOCK_ANCHOR_ID = 'nuxt:devtools'

// The "Inspect Vue component" dock action registered by vite-plugin-vue-tracer.
// It is an on/off tool: while it is on, the hub hides the DevTools panel, and
// brings it back once the tool is turned off (after a pick, or on Escape).
const VUE_TRACER_DOCK_ID = 'vue-tracer'

function getDevframeContext() {
return getDevframeClientContext() as any
}
Expand Down Expand Up @@ -63,7 +68,7 @@ export async function setupDevToolsClient({
close() {
const ctx = getDevframeContext()
if (ctx)
ctx.panel.session.open = false
closePanel(ctx)
},
open() {
const ctx = getDevframeContext()
Expand Down Expand Up @@ -130,10 +135,57 @@ export async function setupDevToolsClient({
left: '0',
top: '0',
})
// Keep the inspect panel in the browser's top layer, above the DevTools
// dock and panel whatever their z-index: once a pick ends, the hub brings
// the panel back while this one is still showing. It renders nothing
// without a pick, so it can stay there. Browsers without the Popover API
// fall back to the z-index above.
const supportsTopLayer = typeof component.showPopover === 'function'
function raiseToTopLayer() {
if (!supportsTopLayer)
return
if (component.matches(':popover-open'))
component.hidePopover()
component.showPopover()
}
if (supportsTopLayer) {
component.popover = 'manual'
// Undo the UA styles of `[popover]`
Object.assign(component.style, {
inset: 'auto',
margin: '0',
padding: '0',
border: 'none',
background: 'transparent',
overflow: 'visible',
})
raiseToTopLayer()
}
// Keep the DevTools panel hidden while the inspect panel is showing. A
// pick ends vue-tracer's dock tool, and the hub brings the DevTools panel
// back right away; hide it again until the inspect panel is closed.
let reopenPanelOnClose = false
function holdPanelWhileInspecting() {
// After vue-tracer's own `click` listener, which ends the tool
queueMicrotask(() => {
const ctx = getDevframeContext()
if (!props.matched || !ctx?.panel.session.open)
return
ctx.panel.session.open = false
reopenPanelOnClose = true
})
}

component.addEventListener('close', () => {
props.matched = undefined
inspectorState.isEnabled = false
inspectorState.isVisible = false
if (reopenPanelOnClose) {
reopenPanelOnClose = false
const ctx = getDevframeContext()
if (ctx)
ctx.panel.session.open = true
}
})
component.addEventListener('selectParent', () => {
const parent = inspectorState.main?.getParent()
Expand Down Expand Up @@ -172,6 +224,9 @@ export async function setupDevToolsClient({

props.matched = info
props.mouse = { x: e.clientX, y: e.clientY }
// Above anything that entered the top layer since
raiseToTopLayer()
holdPanelWhileInspecting()
})

const isAvailable = ref(inspectorHasData())
Expand All @@ -181,21 +236,24 @@ export async function setupDevToolsClient({
})
}

// Drive the inspector through the dock action when it is there, so the
// hub moves the panel out of the way; fall back to the overlay itself.
function setActive(active: boolean) {
const ctx = getDevframeContext()
if (ctx?.docks.getStateById(VUE_TRACER_DOCK_ID)) {
ctx.docks.setActionActive(VUE_TRACER_DOCK_ID, active)
return
}
inspectorState.isVisible = active
inspectorState.isEnabled = active
}

return inspector = markRaw({
isAvailable,
isEnabled: toRef(inspectorState, 'isVisible'),
enable: () => {
inspectorState.isVisible = true
inspectorState.isEnabled = true
},
disable: () => {
inspectorState.isVisible = false
inspectorState.isEnabled = false
},
toggle: () => {
inspectorState.isEnabled = !inspectorState.isEnabled
inspectorState.isVisible = inspectorState.isEnabled
},
enable: () => setActive(true),
disable: () => setActive(false),
toggle: () => setActive(!inspectorState.isEnabled),
})
}

Expand All @@ -211,6 +269,16 @@ export async function setupDevToolsClient({
})
}

/**
* Close the Devframe panel the way its own close button does: the panel stays
* on screen while an entry is selected, so `session.open` alone only collapses
* the dock bar.
*/
function closePanel(ctx: any) {
ctx.docks.switchEntry(null)
ctx.panel.session.open = false
}

export function useClientColorMode(): Ref<ColorScheme> {
const explicitColor = ref<ColorScheme>()
const systemColor = ref<ColorScheme>()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { NuxtDevToolsInspectorProps } from './Props'
import { onClickOutside, useDraggable } from '@vueuse/core'
import { onClickOutside, onKeyStroke, useDraggable } from '@vueuse/core'
import { computed, nextTick, ref, useTemplateRef, watch } from 'vue'

defineOptions({
Expand Down Expand Up @@ -71,6 +71,11 @@ onClickOutside(el, () => {
})
})

onKeyStroke('Escape', () => {
if (props.matched)
close()
})

function close() {
emit('close')
}
Expand All @@ -85,7 +90,7 @@ async function openInEditor() {
const file = `${props.matched.pos[0]}:${props.matched.pos[1]}:${props.matched.pos[2]}`
// await fetch(`/api/open-in-editor?file=${encodeURI(file)}`)
emit('openInEditor', file)
// close()
close()
}

function generateUniqueSelector(element: Element | undefined): string {
Expand Down Expand Up @@ -226,6 +231,8 @@ async function copyAgentInfo() {
toastTimer = setTimeout(() => {
showToast.value = false
}, 6_000)
// The toast outlives the panel
close()
}
catch (error) {
console.error('Failed to copy agent info:', error)
Expand Down
Loading
Loading