diff --git a/docs/1.features.md b/docs/1.features.md index e069ac6eda..e3ab2ca71e 100644 --- a/docs/1.features.md +++ b/docs/1.features.md @@ -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 diff --git a/packages/devtools/src/integrations/vue-tracer.ts b/packages/devtools/src/integrations/vue-tracer.ts index 12abed6025..43ddaea4fc 100644 --- a/packages/devtools/src/integrations/vue-tracer.ts +++ b/packages/devtools/src/integrations/vue-tracer.ts @@ -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) } diff --git a/packages/devtools/src/runtime/plugins/view/client.ts b/packages/devtools/src/runtime/plugins/view/client.ts index db8a313907..04c65f4bcd 100644 --- a/packages/devtools/src/runtime/plugins/view/client.ts +++ b/packages/devtools/src/runtime/plugins/view/client.ts @@ -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 } @@ -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() @@ -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() @@ -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()) @@ -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), }) } @@ -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 { const explicitColor = ref() const systemColor = ref() diff --git a/packages/devtools/src/webcomponents/components/NuxtDevtoolsInspectPanel.vue b/packages/devtools/src/webcomponents/components/NuxtDevtoolsInspectPanel.vue index 598a362894..3f13c133bb 100644 --- a/packages/devtools/src/webcomponents/components/NuxtDevtoolsInspectPanel.vue +++ b/packages/devtools/src/webcomponents/components/NuxtDevtoolsInspectPanel.vue @@ -1,6 +1,6 @@