aetherx-ui-inspector 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core.js +40 -1
- package/src/inspector.js +25 -1
- package/src/ui.js +69 -12
package/package.json
CHANGED
package/src/core.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { injectStyles, createCogButton, createOverlay, createTooltip, createPanel, createBoxModelOverlay, updateOverlay, showCopiedFlash, createHoverOverlay, updateHoverOverlay, createSpacingOverlay, hideSpacing, updateSpacing } from './ui.js'
|
|
2
|
-
import { isInspectorElement, getElementStyles, renderTooltip, updateBoxModel, hideBoxModel } from './inspector.js'
|
|
2
|
+
import { isInspectorElement, getElementStyles, getVueComponent, renderTooltip, updateBoxModel, hideBoxModel } from './inspector.js'
|
|
3
3
|
import { renderEditPanel, captureOriginals } from './editor.js'
|
|
4
4
|
import { createChangeTracker, copyToClipboard } from './feedback.js'
|
|
5
5
|
|
|
6
|
+
const isLocalhost = ['localhost', '127.0.0.1', '::1'].includes(location.hostname)
|
|
7
|
+
|| location.hostname.endsWith('.local')
|
|
8
|
+
|| /^\d+\.\d+\.\d+\.\d+$/.test(location.hostname) // any IP (dev servers)
|
|
9
|
+
|
|
6
10
|
export function init() {
|
|
7
11
|
if (document.getElementById('__dev_inspector__')) return
|
|
8
12
|
|
|
@@ -207,6 +211,7 @@ export function init() {
|
|
|
207
211
|
bar.style.display = 'none'
|
|
208
212
|
label.textContent = ''
|
|
209
213
|
updateTabRow(null)
|
|
214
|
+
updateVueBar(null)
|
|
210
215
|
return
|
|
211
216
|
}
|
|
212
217
|
const tag = el.tagName.toLowerCase()
|
|
@@ -219,6 +224,7 @@ export function init() {
|
|
|
219
224
|
label.textContent = `${tag}${id}${cls}`
|
|
220
225
|
bar.style.display = 'flex'
|
|
221
226
|
updateTabRow(el)
|
|
227
|
+
updateVueBar(el)
|
|
222
228
|
}
|
|
223
229
|
|
|
224
230
|
function updateTabRow(el) {
|
|
@@ -235,6 +241,32 @@ export function init() {
|
|
|
235
241
|
classTab?.classList.toggle('active', activeTab === 'class')
|
|
236
242
|
}
|
|
237
243
|
|
|
244
|
+
function updateVueBar(el) {
|
|
245
|
+
const bar = document.getElementById('di-vue-bar')
|
|
246
|
+
const nameEl = document.getElementById('di-vue-comp-name')
|
|
247
|
+
const vscodeBtn = document.getElementById('di-vscode-btn')
|
|
248
|
+
if (!bar) return
|
|
249
|
+
const comp = el ? getVueComponent(el) : null
|
|
250
|
+
if (!comp) { bar.style.display = 'none'; return }
|
|
251
|
+
bar.style.display = 'flex'
|
|
252
|
+
if (nameEl) nameEl.textContent = `<${comp.name}>`
|
|
253
|
+
if (vscodeBtn) {
|
|
254
|
+
// Prefer vite-plugin-vue-inspector annotation (path:line:col) for exact jump
|
|
255
|
+
const vInspector = el.closest?.('[data-v-inspector]')?.dataset?.vInspector
|
|
256
|
+
let fileTarget = null
|
|
257
|
+
if (vInspector) fileTarget = `vscode://file/${vInspector}`
|
|
258
|
+
else if (comp.file) fileTarget = `vscode://file/${comp.file}`
|
|
259
|
+
|
|
260
|
+
if (isLocalhost && fileTarget) {
|
|
261
|
+
vscodeBtn.style.display = 'flex'
|
|
262
|
+
vscodeBtn.dataset.href = fileTarget
|
|
263
|
+
} else {
|
|
264
|
+
vscodeBtn.style.display = 'none'
|
|
265
|
+
delete vscodeBtn.dataset.href
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
238
270
|
function renderPanel(el) {
|
|
239
271
|
textEditCleanup()
|
|
240
272
|
const content = document.getElementById('di-panel-content')
|
|
@@ -360,6 +392,13 @@ export function init() {
|
|
|
360
392
|
}
|
|
361
393
|
})
|
|
362
394
|
|
|
395
|
+
// ── VS Code link ───────────────────────────────────────────────────────────
|
|
396
|
+
document.getElementById('di-vscode-btn')?.addEventListener('click', () => {
|
|
397
|
+
const btn = document.getElementById('di-vscode-btn')
|
|
398
|
+
const href = btn?.dataset?.href
|
|
399
|
+
if (href) window.open(href)
|
|
400
|
+
})
|
|
401
|
+
|
|
363
402
|
// ── Mode tabs ──────────────────────────────────────────────────────────────
|
|
364
403
|
document.getElementById('di-tab-element')?.addEventListener('click', () => {
|
|
365
404
|
if (activeTab === 'element') return
|
package/src/inspector.js
CHANGED
|
@@ -18,6 +18,20 @@ export function isInspectorElement(el) {
|
|
|
18
18
|
return false
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export function getVueComponent(el) {
|
|
22
|
+
let node = el
|
|
23
|
+
while (node && node !== document.documentElement) {
|
|
24
|
+
const instance = node.__vueParentComponent
|
|
25
|
+
if (instance?.type && typeof instance.type === 'object') {
|
|
26
|
+
const name = instance.type.__name || instance.type.name || null
|
|
27
|
+
const file = instance.type.__file || null
|
|
28
|
+
if (name || file) return { name: name || 'Anonymous', file }
|
|
29
|
+
}
|
|
30
|
+
node = node.parentElement
|
|
31
|
+
}
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
export function getElementStyles(el) {
|
|
22
36
|
const cs = window.getComputedStyle(el)
|
|
23
37
|
const rect = el.getBoundingClientRect()
|
|
@@ -43,6 +57,7 @@ export function getElementStyles(el) {
|
|
|
43
57
|
tag,
|
|
44
58
|
isSvg: el instanceof SVGElement,
|
|
45
59
|
textContent,
|
|
60
|
+
vueComponent: getVueComponent(el),
|
|
46
61
|
size: {
|
|
47
62
|
width: Math.round(rect.width) + 'px',
|
|
48
63
|
height: Math.round(rect.height) + 'px',
|
|
@@ -197,7 +212,7 @@ export function updateBoxModel(bm, el) {
|
|
|
197
212
|
}
|
|
198
213
|
|
|
199
214
|
export function renderTooltip(tooltip, styles, x, y) {
|
|
200
|
-
const { label, size, typography, background, spacing, border, layout, textContent, isSvg } = styles
|
|
215
|
+
const { label, size, typography, background, spacing, border, layout, textContent, isSvg, vueComponent } = styles
|
|
201
216
|
|
|
202
217
|
const colorDot = (hex) => hex
|
|
203
218
|
? `<span class="di-swatch" style="background:${hex};"></span>`
|
|
@@ -213,6 +228,15 @@ export function renderTooltip(tooltip, styles, x, y) {
|
|
|
213
228
|
|
|
214
229
|
let html = `<div class="di-tag">${label}</div>`
|
|
215
230
|
|
|
231
|
+
if (vueComponent) {
|
|
232
|
+
html += `<div class="di-section">Vue</div>`
|
|
233
|
+
html += row('component', `<span class="di-vue-name"><${vueComponent.name}></span>`)
|
|
234
|
+
if (vueComponent.file) {
|
|
235
|
+
const fileName = vueComponent.file.replace(/\\/g, '/').split('/').pop()
|
|
236
|
+
html += row('file', fileName)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
216
240
|
if (textContent) {
|
|
217
241
|
html += row('text', `"${textContent.length > 28 ? textContent.slice(0,28)+'…' : textContent}"`)
|
|
218
242
|
}
|
package/src/ui.js
CHANGED
|
@@ -99,6 +99,7 @@ export function injectStyles() {
|
|
|
99
99
|
vertical-align: middle;
|
|
100
100
|
margin-right: 4px;
|
|
101
101
|
}
|
|
102
|
+
#__dev_inspector_tooltip__ .di-vue-name { color: #a78bfa; font-weight: 600; }
|
|
102
103
|
|
|
103
104
|
/* ── Side edit panel ── */
|
|
104
105
|
#__dev_inspector_panel__ {
|
|
@@ -128,6 +129,7 @@ export function injectStyles() {
|
|
|
128
129
|
min-width: 40px;
|
|
129
130
|
}
|
|
130
131
|
#__dev_inspector_panel__.collapsed .di-element-bar,
|
|
132
|
+
#__dev_inspector_panel__.collapsed .di-vue-bar,
|
|
131
133
|
#__dev_inspector_panel__.collapsed .di-tab-row,
|
|
132
134
|
#__dev_inspector_panel__.collapsed #di-panel-content,
|
|
133
135
|
#__dev_inspector_panel__.collapsed .di-panel-footer,
|
|
@@ -242,6 +244,47 @@ export function injectStyles() {
|
|
|
242
244
|
.di-reinspect-btn:hover { border-color: #6366f1; color: #818cf8; background: #1e1e2e; }
|
|
243
245
|
.di-reinspect-btn.active { border-color: #6366f1; color: #818cf8; background: #1e1e2e; }
|
|
244
246
|
|
|
247
|
+
/* ── Vue component bar ── */
|
|
248
|
+
.di-vue-bar {
|
|
249
|
+
padding: 5px 16px;
|
|
250
|
+
border-bottom: 1px solid #27272a;
|
|
251
|
+
display: flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
gap: 8px;
|
|
254
|
+
flex-shrink: 0;
|
|
255
|
+
background: #16162a;
|
|
256
|
+
}
|
|
257
|
+
.di-vue-icon { font-size: 11px; color: #7c3aed; flex-shrink: 0; }
|
|
258
|
+
.di-vue-comp-name {
|
|
259
|
+
flex: 1;
|
|
260
|
+
font-family: 'SF Mono', monospace;
|
|
261
|
+
font-size: 11px;
|
|
262
|
+
color: #a78bfa;
|
|
263
|
+
font-weight: 600;
|
|
264
|
+
overflow: hidden;
|
|
265
|
+
text-overflow: ellipsis;
|
|
266
|
+
white-space: nowrap;
|
|
267
|
+
}
|
|
268
|
+
.di-vscode-btn {
|
|
269
|
+
flex-shrink: 0;
|
|
270
|
+
padding: 3px 8px;
|
|
271
|
+
background: #1e2433;
|
|
272
|
+
border: 1px solid #1e3a5f;
|
|
273
|
+
border-radius: 6px;
|
|
274
|
+
color: #5b9bd5;
|
|
275
|
+
font-size: 10px;
|
|
276
|
+
font-weight: 500;
|
|
277
|
+
cursor: pointer;
|
|
278
|
+
display: flex;
|
|
279
|
+
align-items: center;
|
|
280
|
+
gap: 4px;
|
|
281
|
+
transition: border-color 0.12s, color 0.12s, background 0.12s;
|
|
282
|
+
font-family: system-ui, sans-serif;
|
|
283
|
+
white-space: nowrap;
|
|
284
|
+
}
|
|
285
|
+
.di-vscode-btn:hover { border-color: #007acc; color: #fff; background: #007acc; }
|
|
286
|
+
.di-vscode-btn svg { flex-shrink: 0; }
|
|
287
|
+
|
|
245
288
|
.di-panel-empty {
|
|
246
289
|
flex: 1;
|
|
247
290
|
display: flex;
|
|
@@ -784,6 +827,16 @@ export function createPanel() {
|
|
|
784
827
|
<span class="di-element-label" id="di-element-label"></span>
|
|
785
828
|
<button class="di-reinspect-btn" id="di-reinspect-btn">↖ Pick</button>
|
|
786
829
|
</div>
|
|
830
|
+
<div class="di-vue-bar" id="di-vue-bar" style="display:none;">
|
|
831
|
+
<span class="di-vue-icon">⬡</span>
|
|
832
|
+
<span class="di-vue-comp-name" id="di-vue-comp-name"></span>
|
|
833
|
+
<button class="di-vscode-btn" id="di-vscode-btn" style="display:none;" title="Open in VS Code">
|
|
834
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor">
|
|
835
|
+
<path d="M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z"/>
|
|
836
|
+
</svg>
|
|
837
|
+
Open in VS Code
|
|
838
|
+
</button>
|
|
839
|
+
</div>
|
|
787
840
|
<div class="di-tab-row" id="di-tab-row" style="display:none;">
|
|
788
841
|
<button class="di-tab active" id="di-tab-element">Element</button>
|
|
789
842
|
<button class="di-tab" id="di-tab-class"><span id="di-tab-class-label">.class</span></button>
|
|
@@ -827,6 +880,20 @@ export function createBoxModelOverlay() {
|
|
|
827
880
|
return container
|
|
828
881
|
}
|
|
829
882
|
|
|
883
|
+
function getVueBadgeLabel(el) {
|
|
884
|
+
const instance = el.__vueParentComponent
|
|
885
|
+
if (instance?.type && typeof instance.type === 'object') {
|
|
886
|
+
const name = instance.type.__name || instance.type.name
|
|
887
|
+
if (name) return `<${name}>`
|
|
888
|
+
}
|
|
889
|
+
const tag = el.tagName.toLowerCase()
|
|
890
|
+
const id = el.id ? `#${el.id}` : ''
|
|
891
|
+
const cls = [...el.classList]
|
|
892
|
+
.filter(c => !c.startsWith('di-') && !c.startsWith('__dev'))
|
|
893
|
+
.slice(0, 2).map(c => `.${c}`).join('')
|
|
894
|
+
return `${tag}${id}${cls}`
|
|
895
|
+
}
|
|
896
|
+
|
|
830
897
|
// Fix 1: use raw viewport coords (fixed positioning needs no scrollY offset)
|
|
831
898
|
export function updateOverlay(overlay, el) {
|
|
832
899
|
if (!el) { overlay.style.display = 'none'; return }
|
|
@@ -838,12 +905,7 @@ export function updateOverlay(overlay, el) {
|
|
|
838
905
|
overlay.style.height = rect.height + 'px'
|
|
839
906
|
const badge = overlay.querySelector('.di-overlay-badge')
|
|
840
907
|
if (badge) {
|
|
841
|
-
|
|
842
|
-
const id = el.id ? `#${el.id}` : ''
|
|
843
|
-
const cls = [...el.classList]
|
|
844
|
-
.filter(c => !c.startsWith('di-') && !c.startsWith('__dev'))
|
|
845
|
-
.slice(0, 2).map(c => `.${c}`).join('')
|
|
846
|
-
badge.textContent = `${tag}${id}${cls}`
|
|
908
|
+
badge.textContent = getVueBadgeLabel(el)
|
|
847
909
|
// Selected badge always sits above the element (static position)
|
|
848
910
|
badge.style.top = '-22px'
|
|
849
911
|
badge.style.borderRadius = '4px 4px 0 0'
|
|
@@ -871,12 +933,7 @@ export function updateHoverOverlay(overlay, el) {
|
|
|
871
933
|
overlay.style.height = rect.height + 'px'
|
|
872
934
|
const badge = overlay.querySelector('.di-overlay-badge')
|
|
873
935
|
if (badge) {
|
|
874
|
-
|
|
875
|
-
const id = el.id ? `#${el.id}` : ''
|
|
876
|
-
const cls = [...el.classList]
|
|
877
|
-
.filter(c => !c.startsWith('di-') && !c.startsWith('__dev'))
|
|
878
|
-
.slice(0, 2).map(c => `.${c}`).join('')
|
|
879
|
-
badge.textContent = `${tag}${id}${cls}`
|
|
936
|
+
badge.textContent = getVueBadgeLabel(el)
|
|
880
937
|
// Hover badge always sits below the highlighted element
|
|
881
938
|
badge.style.top = 'calc(100% + 2px)'
|
|
882
939
|
badge.style.borderRadius = '0 0 4px 4px'
|