vite-plugin-devtools-vue2 0.1.2 → 0.1.3
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/lib/config.js +7 -7
- package/lib/events.js +21 -12
- package/lib/hook.js +12 -6
- package/lib/inspector.js +11 -5
- package/lib/main.js +3 -1
- package/lib/panel.js +420 -221
- package/lib/picker.js +55 -31
- package/lib/vuex.js +14 -8
- package/lib/walker.js +46 -18
- package/package.json +1 -1
package/lib/panel.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// on every Vue scheduler flush.
|
|
4
4
|
|
|
5
5
|
import { LitElement, css, html } from 'lit';
|
|
6
|
-
import {
|
|
6
|
+
import { DragThreshold, EdgeMargin, PanelEdge, PanelH, PanelW, SectionFilterThreshold, StoreKey } from './config.js';
|
|
7
7
|
import hook from './hook.js';
|
|
8
8
|
import { hide, highlight } from './inspector.js';
|
|
9
9
|
import { isPicking, startPicking, stopPicking } from './picker.js';
|
|
@@ -22,6 +22,7 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
22
22
|
tab: { state: true },
|
|
23
23
|
vuexSelected: { state: true },
|
|
24
24
|
timelineSelected: { state: true },
|
|
25
|
+
theme: { state: true },
|
|
25
26
|
renderCodeText: { state: true }
|
|
26
27
|
};
|
|
27
28
|
|
|
@@ -38,9 +39,12 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
38
39
|
this.tab = ui.tab || 'components';
|
|
39
40
|
this.vuexSelected = 0;
|
|
40
41
|
this.timelineSelected = null;
|
|
42
|
+
this.theme = ui.theme || 'light';
|
|
41
43
|
this.renderCodeText = null;
|
|
42
44
|
this.valueExpanded = new Set();
|
|
43
45
|
this.sectionCollapsed = new Set();
|
|
46
|
+
// Nodes collapsed within search results (default: all shown expanded).
|
|
47
|
+
this._searchCollapsed = new Set();
|
|
44
48
|
// Per-section (props/data/…) key filter text, keyed by section title.
|
|
45
49
|
this._sectionFilter = {};
|
|
46
50
|
// Docked position of the entry/panel. Defaults to the bottom edge near
|
|
@@ -59,14 +63,16 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
59
63
|
// derived from `el.__vue__` anyway.
|
|
60
64
|
this._domObserver = null;
|
|
61
65
|
this._onFlush = () => this._scheduleRefresh();
|
|
62
|
-
this._onKeydown =
|
|
66
|
+
this._onKeydown = event => this._handleKeydown(event);
|
|
63
67
|
// Keep the entry/panel on-screen when the viewport shrinks. resize can
|
|
64
68
|
// fire many times per second while dragging the window edge, and
|
|
65
69
|
// _applyPos reads layout then writes styles — so coalesce to at most one
|
|
66
70
|
// call per frame via rAF to avoid layout thrashing.
|
|
67
71
|
this._resizeRaf = 0;
|
|
68
72
|
this._onResize = () => {
|
|
69
|
-
if (this._resizeRaf)
|
|
73
|
+
if (this._resizeRaf) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
70
76
|
this._resizeRaf = requestAnimationFrame(() => {
|
|
71
77
|
this._resizeRaf = 0;
|
|
72
78
|
this._applyPos();
|
|
@@ -76,8 +82,8 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
76
82
|
|
|
77
83
|
static _loadUiState() {
|
|
78
84
|
try {
|
|
79
|
-
return JSON.parse(localStorage.getItem(
|
|
80
|
-
} catch (
|
|
85
|
+
return JSON.parse(localStorage.getItem(StoreKey)) || {};
|
|
86
|
+
} catch (err) {
|
|
81
87
|
return {};
|
|
82
88
|
}
|
|
83
89
|
}
|
|
@@ -85,8 +91,8 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
85
91
|
_persistUiState() {
|
|
86
92
|
try {
|
|
87
93
|
const pos = this._pos && Number.isFinite(this._pos.along) ? this._pos : undefined;
|
|
88
|
-
localStorage.setItem(
|
|
89
|
-
} catch (
|
|
94
|
+
localStorage.setItem(StoreKey, JSON.stringify({ collapsed: this.collapsed, tab: this.tab, theme: this.theme, pos }));
|
|
95
|
+
} catch (err) {
|
|
90
96
|
/* storage unavailable — ignore */
|
|
91
97
|
}
|
|
92
98
|
}
|
|
@@ -96,97 +102,107 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
96
102
|
// the viewport; clamped to stay fully on-screen.
|
|
97
103
|
_applyPos() {
|
|
98
104
|
const entry = this.renderRoot && this.renderRoot.querySelector('.entry');
|
|
99
|
-
if (!entry)
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
if (!entry) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const pos = this._pos || { edge: 'bottom', along: Number.POSITIVE_INFINITY };
|
|
109
|
+
const margin = EdgeMargin;
|
|
102
110
|
const vw = window.innerWidth;
|
|
103
111
|
const vh = window.innerHeight;
|
|
104
112
|
const er = entry.getBoundingClientRect();
|
|
105
113
|
const ew = er.width || 40;
|
|
106
114
|
const eh = er.height || 40;
|
|
107
|
-
const clamp = (
|
|
115
|
+
const clamp = (value, max) => Math.min(Math.max(margin, value), Math.max(margin, max));
|
|
108
116
|
|
|
109
117
|
// Clamped offset of the entry along its docked edge. We derive the panel
|
|
110
118
|
// position from these numbers directly rather than re-reading the entry's
|
|
111
119
|
// live rect — on a fresh open/refresh that rect can still be stale (reads
|
|
112
120
|
// ~0), which left the entry bottom-right but the panel bottom-left.
|
|
113
|
-
const alongX = clamp(
|
|
114
|
-
const alongY = clamp(
|
|
121
|
+
const alongX = clamp(pos.along, vw - ew - margin);
|
|
122
|
+
const alongY = clamp(pos.along, vh - eh - margin);
|
|
115
123
|
|
|
116
124
|
const es = entry.style;
|
|
117
125
|
es.insetInlineStart = es.insetBlockStart = es.insetInlineEnd = es.insetBlockEnd = 'auto';
|
|
118
|
-
if (
|
|
119
|
-
es['inset' + (
|
|
126
|
+
if (pos.edge === 'right' || pos.edge === 'left') {
|
|
127
|
+
es['inset' + (pos.edge === 'right' ? 'InlineEnd' : 'InlineStart')] = margin + 'px';
|
|
120
128
|
es.insetBlockStart = alongY + 'px';
|
|
121
129
|
} else {
|
|
122
|
-
es['inset' + (
|
|
130
|
+
es['inset' + (pos.edge === 'bottom' ? 'BlockEnd' : 'BlockStart')] = margin + 'px';
|
|
123
131
|
es.insetInlineStart = alongX + 'px';
|
|
124
132
|
}
|
|
125
|
-
this.setAttribute('dock',
|
|
133
|
+
this.setAttribute('dock', pos.edge);
|
|
126
134
|
|
|
127
135
|
const panel = this.renderRoot.querySelector('.panel');
|
|
128
|
-
if (!panel)
|
|
136
|
+
if (!panel) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
129
139
|
const ps = panel.style;
|
|
130
140
|
ps.insetInlineStart = ps.insetBlockStart = ps.insetInlineEnd = ps.insetBlockEnd = 'auto';
|
|
131
141
|
// Entry center along its edge, computed from the clamped offsets above.
|
|
132
142
|
const cx = alongX + ew / 2;
|
|
133
143
|
const cy = alongY + eh / 2;
|
|
134
|
-
if (
|
|
135
|
-
ps.insetInlineEnd =
|
|
136
|
-
ps.insetBlockStart = clamp(cy -
|
|
137
|
-
} else if (
|
|
138
|
-
ps.insetInlineStart =
|
|
139
|
-
ps.insetBlockStart = clamp(cy -
|
|
140
|
-
} else if (
|
|
141
|
-
ps.insetBlockStart =
|
|
142
|
-
ps.insetInlineStart = clamp(cx -
|
|
144
|
+
if (pos.edge === 'right') {
|
|
145
|
+
ps.insetInlineEnd = PanelEdge + 'px';
|
|
146
|
+
ps.insetBlockStart = clamp(cy - PanelH / 2, vh - PanelH - margin) + 'px';
|
|
147
|
+
} else if (pos.edge === 'left') {
|
|
148
|
+
ps.insetInlineStart = PanelEdge + 'px';
|
|
149
|
+
ps.insetBlockStart = clamp(cy - PanelH / 2, vh - PanelH - margin) + 'px';
|
|
150
|
+
} else if (pos.edge === 'top') {
|
|
151
|
+
ps.insetBlockStart = PanelEdge + 'px';
|
|
152
|
+
ps.insetInlineStart = clamp(cx - PanelW / 2, vw - PanelW - margin) + 'px';
|
|
143
153
|
} else {
|
|
144
|
-
ps.insetBlockEnd =
|
|
145
|
-
ps.insetInlineStart = clamp(cx -
|
|
154
|
+
ps.insetBlockEnd = PanelEdge + 'px';
|
|
155
|
+
ps.insetInlineStart = clamp(cx - PanelW / 2, vw - PanelW - margin) + 'px';
|
|
146
156
|
}
|
|
147
157
|
}
|
|
148
158
|
|
|
149
159
|
// Drag the always-visible entry. Movement over a threshold = drag (live snap
|
|
150
160
|
// to nearest edge, panel follows); a plain click toggles the panel.
|
|
151
|
-
_startDrag(
|
|
152
|
-
if (
|
|
153
|
-
|
|
161
|
+
_startDrag(event) {
|
|
162
|
+
if (event.button !== 0) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
event.preventDefault();
|
|
154
166
|
hide(); // clear any hover highlight before dragging
|
|
155
167
|
const entry = this.renderRoot.querySelector('.entry');
|
|
156
168
|
const rect = entry.getBoundingClientRect();
|
|
157
169
|
this._drag = {
|
|
158
|
-
startX:
|
|
159
|
-
startY:
|
|
160
|
-
offX:
|
|
161
|
-
offY:
|
|
170
|
+
startX: event.clientX,
|
|
171
|
+
startY: event.clientY,
|
|
172
|
+
offX: event.clientX - rect.left,
|
|
173
|
+
offY: event.clientY - rect.top,
|
|
162
174
|
moved: false
|
|
163
175
|
};
|
|
164
|
-
this._onDragMove =
|
|
165
|
-
this._onDragUp =
|
|
176
|
+
this._onDragMove = event => this._dragMove(event);
|
|
177
|
+
this._onDragUp = event => this._dragUp(event);
|
|
166
178
|
window.addEventListener('pointermove', this._onDragMove, true);
|
|
167
179
|
window.addEventListener('pointerup', this._onDragUp, true);
|
|
168
180
|
}
|
|
169
181
|
|
|
170
|
-
_onFabPointerDown(
|
|
171
|
-
this._startDrag(
|
|
182
|
+
_onFabPointerDown(event) {
|
|
183
|
+
this._startDrag(event);
|
|
172
184
|
}
|
|
173
185
|
|
|
174
|
-
_dragMove(
|
|
175
|
-
const
|
|
176
|
-
if (!
|
|
177
|
-
|
|
178
|
-
|
|
186
|
+
_dragMove(event) {
|
|
187
|
+
const drag = this._drag;
|
|
188
|
+
if (!drag) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (!drag.moved && Math.abs(event.clientX - drag.startX) + Math.abs(event.clientY - drag.startY) < DragThreshold) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
drag.moved = true;
|
|
179
195
|
// Snap to the nearest edge live during the drag (not on release).
|
|
180
196
|
const vw = window.innerWidth;
|
|
181
197
|
const vh = window.innerHeight;
|
|
182
198
|
const dist = {
|
|
183
|
-
left:
|
|
184
|
-
right: vw -
|
|
185
|
-
top:
|
|
186
|
-
bottom: vh -
|
|
199
|
+
left: event.clientX,
|
|
200
|
+
right: vw - event.clientX,
|
|
201
|
+
top: event.clientY,
|
|
202
|
+
bottom: vh - event.clientY
|
|
187
203
|
};
|
|
188
|
-
const edge = Object.keys(dist).reduce((
|
|
189
|
-
const along = edge === 'left' || edge === 'right' ?
|
|
204
|
+
const edge = Object.keys(dist).reduce((best, current) => (dist[current] < dist[best] ? current : best));
|
|
205
|
+
const along = edge === 'left' || edge === 'right' ? event.clientY - drag.offY : event.clientX - drag.offX;
|
|
190
206
|
this._pos = { edge, along };
|
|
191
207
|
this._applyPos();
|
|
192
208
|
}
|
|
@@ -194,10 +210,12 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
194
210
|
_dragUp() {
|
|
195
211
|
window.removeEventListener('pointermove', this._onDragMove, true);
|
|
196
212
|
window.removeEventListener('pointerup', this._onDragUp, true);
|
|
197
|
-
const
|
|
213
|
+
const drag = this._drag;
|
|
198
214
|
this._drag = null;
|
|
199
|
-
if (!
|
|
200
|
-
|
|
215
|
+
if (!drag) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (!drag.moved) {
|
|
201
219
|
// plain click → toggle the panel
|
|
202
220
|
this.collapsed = !this.collapsed;
|
|
203
221
|
return;
|
|
@@ -236,13 +254,22 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
236
254
|
this._domObserver.disconnect();
|
|
237
255
|
this._domObserver = null;
|
|
238
256
|
}
|
|
239
|
-
if (this._vuexUnsub)
|
|
240
|
-
|
|
257
|
+
if (this._vuexUnsub) {
|
|
258
|
+
this._vuexUnsub();
|
|
259
|
+
}
|
|
260
|
+
if (this._eventsUnsub) {
|
|
261
|
+
this._eventsUnsub();
|
|
262
|
+
}
|
|
241
263
|
stopPicking();
|
|
242
264
|
}
|
|
243
265
|
|
|
244
266
|
firstUpdated() {
|
|
245
267
|
this._applyPos();
|
|
268
|
+
this.setAttribute('theme', this.theme);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
_toggleTheme() {
|
|
272
|
+
this.theme = this.theme === 'dark' ? 'light' : 'dark';
|
|
246
273
|
}
|
|
247
274
|
|
|
248
275
|
_scheduleRefresh() {
|
|
@@ -265,16 +292,23 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
265
292
|
}
|
|
266
293
|
|
|
267
294
|
_toggle(id) {
|
|
268
|
-
if (this.expanded.has(id))
|
|
269
|
-
|
|
295
|
+
if (this.expanded.has(id)) {
|
|
296
|
+
this.expanded.delete(id);
|
|
297
|
+
} else {
|
|
298
|
+
this.expanded.add(id);
|
|
299
|
+
}
|
|
270
300
|
this.requestUpdate();
|
|
271
301
|
}
|
|
272
302
|
|
|
273
303
|
// Highlight the page DOM only while hovering a tree row (vue-devtools style).
|
|
274
304
|
_hoverEnter(id) {
|
|
275
|
-
if (this._drag)
|
|
305
|
+
if (this._drag) {
|
|
306
|
+
return; // don't highlight while dragging the entry/panel
|
|
307
|
+
}
|
|
276
308
|
const vm = getInstance(id);
|
|
277
|
-
if (vm)
|
|
309
|
+
if (vm) {
|
|
310
|
+
highlight(vm);
|
|
311
|
+
}
|
|
278
312
|
}
|
|
279
313
|
|
|
280
314
|
_hoverLeave() {
|
|
@@ -299,7 +333,9 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
299
333
|
_selectVm(vm) {
|
|
300
334
|
this.refresh();
|
|
301
335
|
const path = this._pathToVm(vm);
|
|
302
|
-
if (!path.length)
|
|
336
|
+
if (!path.length) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
303
339
|
for (let i = 0; i < path.length - 1; i++) this.expanded.add(path[i]);
|
|
304
340
|
this._select(path[path.length - 1]);
|
|
305
341
|
this._scrollToSelected = true;
|
|
@@ -314,10 +350,18 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
314
350
|
found = next;
|
|
315
351
|
return true;
|
|
316
352
|
}
|
|
317
|
-
for (const
|
|
353
|
+
for (const child of node.children || []) {
|
|
354
|
+
if (dfs(child, next)) {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
318
358
|
return false;
|
|
319
359
|
};
|
|
320
|
-
for (const
|
|
360
|
+
for (const root of this.tree) {
|
|
361
|
+
if (dfs(root, [])) {
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
321
365
|
return found || [];
|
|
322
366
|
}
|
|
323
367
|
|
|
@@ -327,19 +371,23 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
327
371
|
// match nested inside another match is not promoted (it shows in the subtree).
|
|
328
372
|
_computeFilter() {
|
|
329
373
|
const q = this.query.trim().toLowerCase();
|
|
330
|
-
if (!q)
|
|
374
|
+
if (!q) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
331
377
|
const matched = new Set();
|
|
332
378
|
const mark = node => {
|
|
333
|
-
if (node.name.toLowerCase().includes(q))
|
|
334
|
-
|
|
379
|
+
if (node.name.toLowerCase().includes(q)) {
|
|
380
|
+
matched.add(node.id);
|
|
381
|
+
}
|
|
382
|
+
for (const child of node.children || []) mark(child);
|
|
335
383
|
};
|
|
336
|
-
for (const
|
|
384
|
+
for (const root of this.tree) mark(root);
|
|
337
385
|
|
|
338
386
|
const roots = [];
|
|
339
387
|
const show = new Set();
|
|
340
388
|
const collect = node => {
|
|
341
389
|
show.add(node.id);
|
|
342
|
-
for (const
|
|
390
|
+
for (const child of node.children || []) collect(child);
|
|
343
391
|
};
|
|
344
392
|
const walk = (node, hasMatchedAncestor) => {
|
|
345
393
|
const isMatch = matched.has(node.id);
|
|
@@ -347,9 +395,9 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
347
395
|
roots.push(node);
|
|
348
396
|
collect(node);
|
|
349
397
|
}
|
|
350
|
-
for (const
|
|
398
|
+
for (const child of node.children || []) walk(child, hasMatchedAncestor || isMatch);
|
|
351
399
|
};
|
|
352
|
-
for (const
|
|
400
|
+
for (const root of this.tree) walk(root, false);
|
|
353
401
|
return { roots, show, q };
|
|
354
402
|
}
|
|
355
403
|
|
|
@@ -360,81 +408,130 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
360
408
|
const parent = new Map();
|
|
361
409
|
const node = new Map();
|
|
362
410
|
if (filter) {
|
|
363
|
-
const walk = (
|
|
364
|
-
node.set(
|
|
365
|
-
parent.set(
|
|
366
|
-
order.push(
|
|
367
|
-
|
|
368
|
-
|
|
411
|
+
const walk = (treeNode, parentId) => {
|
|
412
|
+
node.set(treeNode.id, treeNode);
|
|
413
|
+
parent.set(treeNode.id, parentId);
|
|
414
|
+
order.push(treeNode.id);
|
|
415
|
+
if (this._searchCollapsed.has(treeNode.id)) {
|
|
416
|
+
return; // collapsed in search
|
|
417
|
+
}
|
|
418
|
+
for (const child of treeNode.children || []) {
|
|
419
|
+
if (filter.show.has(child.id)) {
|
|
420
|
+
walk(child, treeNode.id);
|
|
421
|
+
}
|
|
369
422
|
}
|
|
370
423
|
};
|
|
371
|
-
for (const
|
|
424
|
+
for (const root of filter.roots) walk(root, null);
|
|
372
425
|
return { order, parent, node };
|
|
373
426
|
}
|
|
374
|
-
const walk = (
|
|
375
|
-
node.set(
|
|
376
|
-
parent.set(
|
|
377
|
-
order.push(
|
|
378
|
-
if (
|
|
379
|
-
for (const
|
|
427
|
+
const walk = (treeNode, parentId) => {
|
|
428
|
+
node.set(treeNode.id, treeNode);
|
|
429
|
+
parent.set(treeNode.id, parentId);
|
|
430
|
+
order.push(treeNode.id);
|
|
431
|
+
if (treeNode.children && treeNode.children.length && this.expanded.has(treeNode.id)) {
|
|
432
|
+
for (const child of treeNode.children) walk(child, treeNode.id);
|
|
380
433
|
}
|
|
381
434
|
};
|
|
382
|
-
for (const
|
|
435
|
+
for (const root of this.tree) walk(root, null);
|
|
383
436
|
return { order, parent, node };
|
|
384
437
|
}
|
|
385
438
|
|
|
386
439
|
// Arrow-key navigation once a component is selected (VS Code / devtools style):
|
|
387
440
|
// ↑/↓ move through visible rows, → step into / expand, ← step out / collapse.
|
|
388
441
|
// During search the subtree is always shown, so →/← just navigate in/out.
|
|
389
|
-
_handleKeydown(
|
|
390
|
-
if (this.collapsed
|
|
391
|
-
|
|
442
|
+
_handleKeydown(event) {
|
|
443
|
+
if (this.collapsed) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
392
449
|
// Don't hijack arrow keys while typing in a field. composedPath sees into
|
|
393
450
|
// our shadow root (the search box) as well as the app's own inputs.
|
|
394
|
-
const path =
|
|
451
|
+
const path = event.composedPath ? event.composedPath() : [];
|
|
395
452
|
const inEditable = path.some(el => el && el.tagName && (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) || el.isContentEditable));
|
|
396
|
-
if (inEditable)
|
|
453
|
+
if (inEditable) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Timeline tab: ↑/↓ move between entries.
|
|
458
|
+
if (this.tab === 'timeline') {
|
|
459
|
+
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const entries = this._timelineEntries();
|
|
463
|
+
if (!entries.length) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const curIdx = entries.findIndex(item => item.id === this.timelineSelected);
|
|
467
|
+
const base = curIdx < 0 ? entries.length - 1 : curIdx;
|
|
468
|
+
const nextIdx = event.key === 'ArrowDown' ? Math.min(entries.length - 1, base + 1) : Math.max(0, base - 1);
|
|
469
|
+
this.timelineSelected = entries[nextIdx].id;
|
|
470
|
+
this._scrollToSelected = true;
|
|
471
|
+
event.preventDefault();
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
397
474
|
|
|
475
|
+
// Components tab: tree navigation (needs a selected node).
|
|
476
|
+
if (this.selectedId == null) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
398
479
|
const filter = this._computeFilter();
|
|
399
480
|
const searching = !!filter;
|
|
400
481
|
const { order, parent, node } = this._index(filter);
|
|
401
482
|
const id = this.selectedId;
|
|
402
483
|
const cur = node.get(id);
|
|
403
|
-
if (!cur)
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const
|
|
484
|
+
if (!cur) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const index = order.indexOf(id);
|
|
488
|
+
if (index < 0) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const shownChildren = (cur.children || []).filter(item => !filter || filter.show.has(item.id));
|
|
407
492
|
const hasChildren = shownChildren.length > 0;
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
const isOpen = searching
|
|
493
|
+
// "open" respects the active mode: normal tree uses `expanded`, search
|
|
494
|
+
// uses `_searchCollapsed` (default open).
|
|
495
|
+
const isOpen = searching ? !this._searchCollapsed.has(id) : this.expanded.has(id);
|
|
411
496
|
let next = null;
|
|
412
497
|
|
|
413
|
-
if (
|
|
414
|
-
next = order[Math.min(order.length - 1,
|
|
415
|
-
} else if (
|
|
416
|
-
next = order[Math.max(0,
|
|
417
|
-
} else if (
|
|
498
|
+
if (event.key === 'ArrowDown') {
|
|
499
|
+
next = order[Math.min(order.length - 1, index + 1)];
|
|
500
|
+
} else if (event.key === 'ArrowUp') {
|
|
501
|
+
next = order[Math.max(0, index - 1)];
|
|
502
|
+
} else if (event.key === 'ArrowRight') {
|
|
418
503
|
// Closed with children -> open it; already open -> step into first child.
|
|
419
|
-
if (hasChildren && !isOpen)
|
|
420
|
-
|
|
421
|
-
|
|
504
|
+
if (hasChildren && !isOpen) {
|
|
505
|
+
searching ? this._searchCollapsed.delete(id) : this.expanded.add(id);
|
|
506
|
+
} else if (hasChildren && isOpen) {
|
|
507
|
+
next = shownChildren[0].id;
|
|
508
|
+
}
|
|
509
|
+
} else if (event.key === 'ArrowLeft') {
|
|
422
510
|
// Check open/closed FIRST: open -> collapse; closed (or leaf) -> parent.
|
|
423
|
-
if (isOpen && hasChildren
|
|
424
|
-
|
|
511
|
+
if (isOpen && hasChildren) {
|
|
512
|
+
searching ? this._searchCollapsed.add(id) : this.expanded.delete(id);
|
|
513
|
+
} else {
|
|
514
|
+
next = parent.get(id);
|
|
515
|
+
}
|
|
425
516
|
}
|
|
426
517
|
|
|
427
|
-
|
|
428
|
-
if (next != null)
|
|
518
|
+
event.preventDefault();
|
|
519
|
+
if (next != null) {
|
|
520
|
+
this._select(next);
|
|
521
|
+
}
|
|
429
522
|
this._scrollToSelected = true;
|
|
430
523
|
this.requestUpdate();
|
|
431
524
|
}
|
|
432
525
|
|
|
433
526
|
updated(changed) {
|
|
434
527
|
// Persist remembered UI bits across reloads.
|
|
435
|
-
if (changed && (changed.has('collapsed') || changed.has('tab'))) {
|
|
528
|
+
if (changed && (changed.has('collapsed') || changed.has('tab') || changed.has('theme'))) {
|
|
436
529
|
this._persistUiState();
|
|
437
530
|
}
|
|
531
|
+
// Reflect the theme onto the host so the CSS variable overrides apply.
|
|
532
|
+
if (changed && changed.has('theme')) {
|
|
533
|
+
this.setAttribute('theme', this.theme);
|
|
534
|
+
}
|
|
438
535
|
// Re-clamp the docked position when switching fab <-> panel (sizes differ).
|
|
439
536
|
if (changed && changed.has('collapsed')) {
|
|
440
537
|
this._applyPos();
|
|
@@ -450,16 +547,20 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
450
547
|
}
|
|
451
548
|
// When the query changes, auto-select the first match (like vue-devtools).
|
|
452
549
|
if (changed && changed.has('query') && this.query.trim()) {
|
|
453
|
-
const
|
|
454
|
-
if (
|
|
455
|
-
this._select(
|
|
550
|
+
const filter = this._computeFilter();
|
|
551
|
+
if (filter && filter.roots.length && !filter.show.has(this.selectedId)) {
|
|
552
|
+
this._select(filter.roots[0].id);
|
|
456
553
|
this._scrollToSelected = true;
|
|
457
554
|
}
|
|
458
555
|
}
|
|
459
|
-
if (!this._scrollToSelected)
|
|
556
|
+
if (!this._scrollToSelected) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
460
559
|
this._scrollToSelected = false;
|
|
461
560
|
const el = this.renderRoot.querySelector('.node.selected');
|
|
462
|
-
if (el)
|
|
561
|
+
if (el) {
|
|
562
|
+
el.scrollIntoView({ block: 'nearest' });
|
|
563
|
+
}
|
|
463
564
|
}
|
|
464
565
|
|
|
465
566
|
_renderNode(node, depth) {
|
|
@@ -477,8 +578,8 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
477
578
|
>
|
|
478
579
|
<span
|
|
479
580
|
class="caret-btn"
|
|
480
|
-
@click=${
|
|
481
|
-
|
|
581
|
+
@click=${event => {
|
|
582
|
+
event.stopPropagation();
|
|
482
583
|
this._toggle(node.id);
|
|
483
584
|
}}
|
|
484
585
|
>
|
|
@@ -486,7 +587,7 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
486
587
|
</span>
|
|
487
588
|
<span class="tag"><${node.name}></span>
|
|
488
589
|
</div>
|
|
489
|
-
${hasChildren && isOpen ? node.children.map(
|
|
590
|
+
${hasChildren && isOpen ? node.children.map(item => this._renderNode(item, depth + 1)) : null}
|
|
490
591
|
</div>
|
|
491
592
|
`;
|
|
492
593
|
}
|
|
@@ -494,8 +595,10 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
494
595
|
// Search result row: matched node as a subtree root, with all descendants
|
|
495
596
|
// shown (always open). Ancestors are omitted. Arrow is non-interactive here.
|
|
496
597
|
_renderSearchNode(node, depth, show) {
|
|
497
|
-
const kids = (node.children || []).filter(
|
|
598
|
+
const kids = (node.children || []).filter(item => show.has(item.id));
|
|
599
|
+
const hasKids = kids.length > 0;
|
|
498
600
|
const isSelected = node.id === this.selectedId;
|
|
601
|
+
const open = !this._searchCollapsed.has(node.id); // default open in search
|
|
499
602
|
return html`
|
|
500
603
|
<div>
|
|
501
604
|
<div
|
|
@@ -505,24 +608,45 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
505
608
|
@mouseenter=${() => this._hoverEnter(node.id)}
|
|
506
609
|
@mouseleave=${() => this._hoverLeave()}
|
|
507
610
|
>
|
|
508
|
-
<span
|
|
611
|
+
<span
|
|
612
|
+
class="caret-btn"
|
|
613
|
+
@click=${event => {
|
|
614
|
+
event.stopPropagation();
|
|
615
|
+
if (hasKids) {
|
|
616
|
+
this._toggleSearchNode(node.id);
|
|
617
|
+
}
|
|
618
|
+
}}
|
|
619
|
+
>
|
|
620
|
+
${this._caret(open && hasKids, !hasKids)}
|
|
621
|
+
</span>
|
|
509
622
|
<span class="tag"><${node.name}></span>
|
|
510
623
|
</div>
|
|
511
|
-
${kids.map(
|
|
624
|
+
${hasKids && open ? kids.map(item => this._renderSearchNode(item, depth + 1, show)) : null}
|
|
512
625
|
</div>
|
|
513
626
|
`;
|
|
514
627
|
}
|
|
515
628
|
|
|
629
|
+
_toggleSearchNode(id) {
|
|
630
|
+
if (this._searchCollapsed.has(id)) {
|
|
631
|
+
this._searchCollapsed.delete(id);
|
|
632
|
+
} else {
|
|
633
|
+
this._searchCollapsed.add(id);
|
|
634
|
+
}
|
|
635
|
+
this.requestUpdate();
|
|
636
|
+
}
|
|
637
|
+
|
|
516
638
|
// Render an object as a titled, collapsible section of expandable value rows.
|
|
517
639
|
// `editable` enables inline editing of primitive leaves (writes back into obj).
|
|
518
640
|
// Sections with many keys get a live filter input in the header.
|
|
519
641
|
_renderKvSection(title, obj, editable) {
|
|
520
642
|
const allKeys = obj ? Object.keys(obj) : [];
|
|
521
|
-
if (!allKeys.length)
|
|
643
|
+
if (!allKeys.length) {
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
522
646
|
const collapsed = this.sectionCollapsed.has(title);
|
|
523
|
-
const showFilter = allKeys.length >
|
|
647
|
+
const showFilter = !collapsed && allKeys.length > SectionFilterThreshold;
|
|
524
648
|
const q = (this._sectionFilter[title] || '').trim().toLowerCase();
|
|
525
|
-
const keys = q ? allKeys.filter(
|
|
649
|
+
const keys = q ? allKeys.filter(item => item.toLowerCase().includes(q)) : allKeys;
|
|
526
650
|
return html`
|
|
527
651
|
<div class="section-title" @click=${() => this._toggleSection(title)}>
|
|
528
652
|
${this._caret(!collapsed, false)}
|
|
@@ -534,16 +658,16 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
534
658
|
type="search"
|
|
535
659
|
placeholder="filter…"
|
|
536
660
|
.value=${this._sectionFilter[title] || ''}
|
|
537
|
-
@click=${
|
|
538
|
-
@keydown=${
|
|
539
|
-
if (
|
|
661
|
+
@click=${event => event.stopPropagation()}
|
|
662
|
+
@keydown=${event => {
|
|
663
|
+
if (event.key === 'Escape') {
|
|
540
664
|
this._sectionFilter[title] = '';
|
|
541
665
|
this.requestUpdate();
|
|
542
666
|
}
|
|
543
|
-
|
|
667
|
+
event.stopPropagation();
|
|
544
668
|
}}
|
|
545
|
-
@input=${
|
|
546
|
-
this._sectionFilter[title] =
|
|
669
|
+
@input=${event => {
|
|
670
|
+
this._sectionFilter[title] = event.target.value;
|
|
547
671
|
this.requestUpdate();
|
|
548
672
|
}}
|
|
549
673
|
/>
|
|
@@ -553,7 +677,7 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
553
677
|
${collapsed
|
|
554
678
|
? null
|
|
555
679
|
: keys.length
|
|
556
|
-
? keys.map(
|
|
680
|
+
? keys.map(item => this._renderValueRow(item, obj[item], `${title}.${item}`, 0, obj, editable))
|
|
557
681
|
: html`
|
|
558
682
|
<div class="empty">No match</div>
|
|
559
683
|
`}
|
|
@@ -561,14 +685,20 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
561
685
|
}
|
|
562
686
|
|
|
563
687
|
_toggleSection(title) {
|
|
564
|
-
if (this.sectionCollapsed.has(title))
|
|
565
|
-
|
|
688
|
+
if (this.sectionCollapsed.has(title)) {
|
|
689
|
+
this.sectionCollapsed.delete(title);
|
|
690
|
+
} else {
|
|
691
|
+
this.sectionCollapsed.add(title);
|
|
692
|
+
}
|
|
566
693
|
this.requestUpdate();
|
|
567
694
|
}
|
|
568
695
|
|
|
569
696
|
_toggleValue(path) {
|
|
570
|
-
if (this.valueExpanded.has(path))
|
|
571
|
-
|
|
697
|
+
if (this.valueExpanded.has(path)) {
|
|
698
|
+
this.valueExpanded.delete(path);
|
|
699
|
+
} else {
|
|
700
|
+
this.valueExpanded.add(path);
|
|
701
|
+
}
|
|
572
702
|
this.requestUpdate();
|
|
573
703
|
}
|
|
574
704
|
|
|
@@ -576,7 +706,7 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
576
706
|
// `editable` is set, primitive leaves can be clicked to edit in place.
|
|
577
707
|
_renderValueRow(keyLabel, value, path, depth, parent, editable) {
|
|
578
708
|
const isObj = value !== null && typeof value === 'object';
|
|
579
|
-
const keys = isObj ? (Array.isArray(value) ? value.map((_,
|
|
709
|
+
const keys = isObj ? (Array.isArray(value) ? value.map((_, index) => index) : Object.keys(value)) : [];
|
|
580
710
|
const expandable = isObj && keys.length > 0;
|
|
581
711
|
const open = this.valueExpanded.has(path);
|
|
582
712
|
const canEdit = editable && !isObj && typeof value !== 'function';
|
|
@@ -594,18 +724,20 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
594
724
|
<input
|
|
595
725
|
class="edit-input"
|
|
596
726
|
.value=${String(value)}
|
|
597
|
-
@click=${
|
|
598
|
-
@keydown=${
|
|
599
|
-
@blur=${
|
|
727
|
+
@click=${event => event.stopPropagation()}
|
|
728
|
+
@keydown=${event => this._onEditKeydown(event, parent, keyLabel, value)}
|
|
729
|
+
@blur=${event => this._commitEdit(parent, keyLabel, event.target.value, value)}
|
|
600
730
|
/>
|
|
601
731
|
`
|
|
602
732
|
: html`
|
|
603
733
|
<span
|
|
604
734
|
class="val ${this._valClass(value)} ${canEdit ? 'editable' : ''}"
|
|
605
735
|
title=${preview}
|
|
606
|
-
@click=${
|
|
607
|
-
if (!canEdit)
|
|
608
|
-
|
|
736
|
+
@click=${event => {
|
|
737
|
+
if (!canEdit) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
event.stopPropagation();
|
|
609
741
|
this._editingPath = path;
|
|
610
742
|
this._focusEdit = true;
|
|
611
743
|
this.requestUpdate();
|
|
@@ -619,8 +751,8 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
619
751
|
<button
|
|
620
752
|
class="copy-btn"
|
|
621
753
|
title="Copy value"
|
|
622
|
-
@click=${
|
|
623
|
-
|
|
754
|
+
@click=${event => {
|
|
755
|
+
event.stopPropagation();
|
|
624
756
|
this._copyValue(value);
|
|
625
757
|
}}
|
|
626
758
|
>
|
|
@@ -629,7 +761,7 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
629
761
|
`
|
|
630
762
|
: null}
|
|
631
763
|
</div>
|
|
632
|
-
${expandable && open ? keys.map(
|
|
764
|
+
${expandable && open ? keys.map(item => this._renderValueRow(item, value[item], `${path}.${item}`, depth + 1, value, editable)) : null}
|
|
633
765
|
`;
|
|
634
766
|
}
|
|
635
767
|
|
|
@@ -638,7 +770,7 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
638
770
|
if (value !== null && typeof value === 'object') {
|
|
639
771
|
try {
|
|
640
772
|
text = JSON.stringify(value, null, 2);
|
|
641
|
-
} catch (
|
|
773
|
+
} catch (err) {
|
|
642
774
|
text = String(value);
|
|
643
775
|
}
|
|
644
776
|
} else {
|
|
@@ -666,17 +798,17 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
666
798
|
ta.select();
|
|
667
799
|
try {
|
|
668
800
|
document.execCommand('copy');
|
|
669
|
-
} catch (
|
|
801
|
+
} catch (err) {
|
|
670
802
|
/* ignore */
|
|
671
803
|
}
|
|
672
804
|
document.body.removeChild(ta);
|
|
673
805
|
}
|
|
674
806
|
|
|
675
|
-
_onEditKeydown(
|
|
676
|
-
|
|
677
|
-
if (
|
|
678
|
-
this._commitEdit(parent, key,
|
|
679
|
-
} else if (
|
|
807
|
+
_onEditKeydown(event, parent, key, oldValue) {
|
|
808
|
+
event.stopPropagation();
|
|
809
|
+
if (event.key === 'Enter') {
|
|
810
|
+
this._commitEdit(parent, key, event.target.value, oldValue);
|
|
811
|
+
} else if (event.key === 'Escape') {
|
|
680
812
|
this._editingPath = null;
|
|
681
813
|
this.requestUpdate();
|
|
682
814
|
}
|
|
@@ -687,34 +819,47 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
687
819
|
_commitEdit(parent, key, rawStr, oldValue) {
|
|
688
820
|
this._editingPath = null;
|
|
689
821
|
let parsed = rawStr;
|
|
690
|
-
const
|
|
691
|
-
if (
|
|
692
|
-
const
|
|
693
|
-
parsed = Number.isNaN(
|
|
694
|
-
} else if (
|
|
822
|
+
const type = typeof oldValue;
|
|
823
|
+
if (type === 'number') {
|
|
824
|
+
const num = Number(rawStr);
|
|
825
|
+
parsed = Number.isNaN(num) ? oldValue : num;
|
|
826
|
+
} else if (type === 'boolean') {
|
|
695
827
|
parsed = rawStr === 'true' || rawStr === '1';
|
|
696
828
|
} else if (oldValue === null || oldValue === undefined) {
|
|
697
829
|
try {
|
|
698
830
|
parsed = JSON.parse(rawStr);
|
|
699
|
-
} catch (
|
|
831
|
+
} catch (err) {
|
|
700
832
|
parsed = rawStr;
|
|
701
833
|
}
|
|
702
834
|
}
|
|
703
835
|
const Vue = hook.Vue;
|
|
704
836
|
if (parent) {
|
|
705
|
-
if (Vue && Vue.set)
|
|
706
|
-
|
|
837
|
+
if (Vue && Vue.set) {
|
|
838
|
+
Vue.set(parent, key, parsed);
|
|
839
|
+
} else {
|
|
840
|
+
parent[key] = parsed;
|
|
841
|
+
}
|
|
707
842
|
}
|
|
708
843
|
this.requestUpdate();
|
|
709
844
|
}
|
|
710
845
|
|
|
711
846
|
_valClass(value) {
|
|
712
|
-
if (value === null || value === undefined)
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
if (
|
|
717
|
-
|
|
847
|
+
if (value === null || value === undefined) {
|
|
848
|
+
return 'v-null';
|
|
849
|
+
}
|
|
850
|
+
const type = typeof value;
|
|
851
|
+
if (type === 'number') {
|
|
852
|
+
return 'v-num';
|
|
853
|
+
}
|
|
854
|
+
if (type === 'boolean') {
|
|
855
|
+
return 'v-bool';
|
|
856
|
+
}
|
|
857
|
+
if (type === 'string') {
|
|
858
|
+
return 'v-str';
|
|
859
|
+
}
|
|
860
|
+
if (type === 'function') {
|
|
861
|
+
return 'v-fn';
|
|
862
|
+
}
|
|
718
863
|
return 'v-obj';
|
|
719
864
|
}
|
|
720
865
|
|
|
@@ -734,20 +879,21 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
734
879
|
`;
|
|
735
880
|
}
|
|
736
881
|
const vm = getInstance(this.selectedId);
|
|
737
|
-
if (!vm)
|
|
882
|
+
if (!vm) {
|
|
738
883
|
return html`
|
|
739
884
|
<div class="empty">Component unmounted</div>
|
|
740
885
|
`;
|
|
886
|
+
}
|
|
741
887
|
|
|
742
888
|
const propsObj = vm._props || {};
|
|
743
889
|
const dataObj = vm._data || vm.$data || {};
|
|
744
890
|
const compDefs = (vm.$options && vm.$options.computed) || {};
|
|
745
891
|
const compObj = {};
|
|
746
|
-
for (const
|
|
892
|
+
for (const key of Object.keys(compDefs)) {
|
|
747
893
|
try {
|
|
748
|
-
compObj[
|
|
894
|
+
compObj[key] = vm[key];
|
|
749
895
|
} catch (err) {
|
|
750
|
-
compObj[
|
|
896
|
+
compObj[key] = `⚠ ${err && err.message}`;
|
|
751
897
|
}
|
|
752
898
|
}
|
|
753
899
|
const attrsObj = vm.$attrs || {};
|
|
@@ -790,27 +936,34 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
790
936
|
}
|
|
791
937
|
|
|
792
938
|
_vmName(vm) {
|
|
793
|
-
const
|
|
794
|
-
let
|
|
795
|
-
if (!
|
|
796
|
-
|
|
939
|
+
const options = vm.$options || {};
|
|
940
|
+
let name = options.name || options._componentTag;
|
|
941
|
+
if (!name && options.__file) {
|
|
942
|
+
name = String(options.__file)
|
|
797
943
|
.split(/[\\/]/)
|
|
798
944
|
.pop()
|
|
799
945
|
.replace(/\.vue$/, '');
|
|
800
|
-
|
|
801
|
-
|
|
946
|
+
}
|
|
947
|
+
if (!name && vm.$root === vm) {
|
|
948
|
+
name = 'Root';
|
|
949
|
+
}
|
|
950
|
+
return name || 'Anonymous';
|
|
802
951
|
}
|
|
803
952
|
|
|
804
953
|
// Ask the Vite dev server to open the component's source file in the editor.
|
|
805
954
|
_openInEditor(file) {
|
|
806
|
-
if (!file)
|
|
955
|
+
if (!file) {
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
807
958
|
fetch('/__open-in-editor?file=' + encodeURIComponent(file)).catch(() => {});
|
|
808
959
|
}
|
|
809
960
|
|
|
810
961
|
// Scroll the component's root DOM element into view and flash the highlight.
|
|
811
962
|
_scrollToComponent(vm) {
|
|
812
963
|
const el = vm && vm.$el;
|
|
813
|
-
if (!el || !el.scrollIntoView)
|
|
964
|
+
if (!el || !el.scrollIntoView) {
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
814
967
|
el.scrollIntoView({
|
|
815
968
|
behavior: 'smooth',
|
|
816
969
|
block: 'center',
|
|
@@ -833,17 +986,23 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
833
986
|
const lines = code.split('\n');
|
|
834
987
|
let min = Infinity;
|
|
835
988
|
for (let i = 1; i < lines.length; i++) {
|
|
836
|
-
if (!lines[i].trim())
|
|
989
|
+
if (!lines[i].trim()) {
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
837
992
|
const indent = lines[i].match(/^[ \t]*/)[0].length;
|
|
838
|
-
if (indent < min)
|
|
993
|
+
if (indent < min) {
|
|
994
|
+
min = indent;
|
|
995
|
+
}
|
|
839
996
|
}
|
|
840
|
-
if (!isFinite(min) || min === 0)
|
|
841
|
-
|
|
997
|
+
if (!isFinite(min) || min === 0) {
|
|
998
|
+
return code;
|
|
999
|
+
}
|
|
1000
|
+
return lines.map((item, index) => (index === 0 ? item : item.slice(min))).join('\n');
|
|
842
1001
|
}
|
|
843
1002
|
|
|
844
1003
|
render() {
|
|
845
1004
|
return html`
|
|
846
|
-
<div class="entry" @pointerdown=${
|
|
1005
|
+
<div class="entry" @pointerdown=${event => this._onFabPointerDown(event)}>
|
|
847
1006
|
<span class="fab-icon">${this._vueLogo()}</span>
|
|
848
1007
|
</div>
|
|
849
1008
|
${this.collapsed ? null : this._renderPanel()}
|
|
@@ -876,6 +1035,10 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
876
1035
|
</button>
|
|
877
1036
|
`
|
|
878
1037
|
: null}
|
|
1038
|
+
<button class="side-tab" @click=${() => this._toggleTheme()}>
|
|
1039
|
+
${this._icon(this.theme === 'dark' ? 'sun' : 'moon')}
|
|
1040
|
+
<span class="tip">${this.theme === 'dark' ? 'Light theme' : 'Dark theme'}</span>
|
|
1041
|
+
</button>
|
|
879
1042
|
<button class="side-tab side-tab--min" @click=${() => (this.collapsed = true)}>
|
|
880
1043
|
${this._icon('min')}
|
|
881
1044
|
<span class="tip">Minimize</span>
|
|
@@ -909,11 +1072,11 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
909
1072
|
type="search"
|
|
910
1073
|
placeholder="Search components…"
|
|
911
1074
|
.value=${this.query}
|
|
912
|
-
@input=${
|
|
913
|
-
@keydown=${
|
|
914
|
-
if (
|
|
1075
|
+
@input=${event => (this.query = event.target.value)}
|
|
1076
|
+
@keydown=${event => {
|
|
1077
|
+
if (event.key === 'Escape') {
|
|
915
1078
|
this.query = '';
|
|
916
|
-
|
|
1079
|
+
event.stopPropagation();
|
|
917
1080
|
}
|
|
918
1081
|
}}
|
|
919
1082
|
/>
|
|
@@ -929,8 +1092,8 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
929
1092
|
<div class="empty">No component matches</div>
|
|
930
1093
|
`
|
|
931
1094
|
: filter
|
|
932
|
-
? filter.roots.map(
|
|
933
|
-
: this.tree.map(
|
|
1095
|
+
? filter.roots.map(item => this._renderSearchNode(item, 0, filter.show))
|
|
1096
|
+
: this.tree.map(item => this._renderNode(item, 0))}
|
|
934
1097
|
</div>
|
|
935
1098
|
<div class="detail">${this._renderDetail()}</div>
|
|
936
1099
|
</div>
|
|
@@ -964,10 +1127,10 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
964
1127
|
</button>
|
|
965
1128
|
</div>
|
|
966
1129
|
${snaps.map(
|
|
967
|
-
(
|
|
968
|
-
<div class="node ${
|
|
969
|
-
<span class="mut-index">${
|
|
970
|
-
<span class="tag">${
|
|
1130
|
+
(item, index) => html`
|
|
1131
|
+
<div class="node ${index === sel ? 'selected' : ''}" @click=${() => (this.vuexSelected = index)}>
|
|
1132
|
+
<span class="mut-index">${item.base ? '' : index}</span>
|
|
1133
|
+
<span class="tag">${item.base ? 'Base State' : item.type}</span>
|
|
971
1134
|
</div>
|
|
972
1135
|
`
|
|
973
1136
|
)}
|
|
@@ -995,14 +1158,16 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
995
1158
|
// (like the vue-devtools v7 Timeline).
|
|
996
1159
|
_timelineEntries() {
|
|
997
1160
|
const entries = [];
|
|
998
|
-
for (const
|
|
999
|
-
entries.push({ id: `e${
|
|
1161
|
+
for (const event of getEvents()) {
|
|
1162
|
+
entries.push({ id: `e${event.id}`, kind: 'event', time: event.time, title: event.name, sub: `<${event.component}>`, event: event });
|
|
1000
1163
|
}
|
|
1001
|
-
getSnapshots().forEach((
|
|
1002
|
-
if (
|
|
1003
|
-
|
|
1164
|
+
getSnapshots().forEach((item, index) => {
|
|
1165
|
+
if (item.base) {
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
entries.push({ id: `m${index}`, kind: 'mutation', time: item.time || 0, title: item.type, sub: 'vuex', snap: item, index });
|
|
1004
1169
|
});
|
|
1005
|
-
entries.sort((
|
|
1170
|
+
entries.sort((first, second) => first.time - second.time);
|
|
1006
1171
|
return entries;
|
|
1007
1172
|
}
|
|
1008
1173
|
|
|
@@ -1013,7 +1178,7 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
1013
1178
|
<div class="body"><div class="empty">No timeline activity yet</div></div>
|
|
1014
1179
|
`;
|
|
1015
1180
|
}
|
|
1016
|
-
const sel = entries.find(
|
|
1181
|
+
const sel = entries.find(item => item.id === this.timelineSelected) || entries[entries.length - 1];
|
|
1017
1182
|
return html`
|
|
1018
1183
|
<div class="body">
|
|
1019
1184
|
<div class="tree">
|
|
@@ -1030,12 +1195,11 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
1030
1195
|
</button>
|
|
1031
1196
|
</div>
|
|
1032
1197
|
${entries.map(
|
|
1033
|
-
|
|
1034
|
-
<div class="node ${
|
|
1035
|
-
<span class="
|
|
1036
|
-
<span class="
|
|
1037
|
-
<span class="
|
|
1038
|
-
<span class="ev-comp">${e.sub}</span>
|
|
1198
|
+
item => html`
|
|
1199
|
+
<div class="node ${item.id === sel.id ? 'selected' : ''}" @click=${() => (this.timelineSelected = item.id)}>
|
|
1200
|
+
<span class="ev-time">${this._formatTime(item.time)}</span>
|
|
1201
|
+
<span class="tag">${item.title}</span>
|
|
1202
|
+
<span class="ev-comp">${item.sub}</span>
|
|
1039
1203
|
</div>
|
|
1040
1204
|
`
|
|
1041
1205
|
)}
|
|
@@ -1046,44 +1210,46 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
1046
1210
|
}
|
|
1047
1211
|
|
|
1048
1212
|
_renderTimelineDetail(entry) {
|
|
1049
|
-
if (!entry)
|
|
1213
|
+
if (!entry) {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1050
1216
|
if (entry.kind === 'event') {
|
|
1051
|
-
const
|
|
1217
|
+
const event = entry.event;
|
|
1052
1218
|
const argsObj = {};
|
|
1053
|
-
(
|
|
1054
|
-
argsObj[
|
|
1219
|
+
(event.args || []).forEach((item, index) => {
|
|
1220
|
+
argsObj[index] = item;
|
|
1055
1221
|
});
|
|
1056
1222
|
return html`
|
|
1057
|
-
${this._renderKvSection('event', { name:
|
|
1058
|
-
${
|
|
1223
|
+
${this._renderKvSection('event', { name: event.name, from: event.component, time: new Date(event.time).toLocaleTimeString() }, false)}
|
|
1224
|
+
${event.args && event.args.length
|
|
1059
1225
|
? this._renderKvSection('payload', argsObj, false)
|
|
1060
1226
|
: html`
|
|
1061
1227
|
<div class="empty">No payload</div>
|
|
1062
1228
|
`}
|
|
1063
1229
|
`;
|
|
1064
1230
|
}
|
|
1065
|
-
const
|
|
1231
|
+
const snap = entry.snap;
|
|
1066
1232
|
const store = getStore();
|
|
1067
|
-
const payloadObj =
|
|
1233
|
+
const payloadObj = snap.payload === undefined ? null : { payload: snap.payload };
|
|
1068
1234
|
return html`
|
|
1069
1235
|
<button class="btn on time-travel" @click=${() => travelTo(entry.index)}>⏱ Time Travel</button>
|
|
1070
|
-
${this._renderKvSection('mutation', { type:
|
|
1236
|
+
${this._renderKvSection('mutation', { type: snap.type, time: new Date(snap.time).toLocaleTimeString() }, false)}
|
|
1071
1237
|
${payloadObj ? this._renderKvSection('payload', payloadObj, false) : null}
|
|
1072
|
-
${this._renderKvSection('state',
|
|
1238
|
+
${this._renderKvSection('state', snap.state || {}, false)}
|
|
1073
1239
|
${store ? this._renderKvSection('getters (live)', store.getters || {}, false) : null}
|
|
1074
1240
|
`;
|
|
1075
1241
|
}
|
|
1076
1242
|
|
|
1077
|
-
_formatTime(
|
|
1078
|
-
const
|
|
1079
|
-
const
|
|
1080
|
-
return `${
|
|
1243
|
+
_formatTime(time) {
|
|
1244
|
+
const date = new Date(time);
|
|
1245
|
+
const pad = (num, length = 2) => String(num).padStart(length, '0');
|
|
1246
|
+
return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`;
|
|
1081
1247
|
}
|
|
1082
1248
|
|
|
1083
1249
|
// Plugin logo
|
|
1084
1250
|
_vueLogo() {
|
|
1085
1251
|
return html`
|
|
1086
|
-
<svg fill-rule="evenodd" viewBox="64 64 896 896" fill="
|
|
1252
|
+
<svg fill-rule="evenodd" viewBox="64 64 896 896" fill="var(--logo-fill)" aria-hidden="true">
|
|
1087
1253
|
<path
|
|
1088
1254
|
d="M250.02 547.04c92.37-19.8 79.77-130.07 76.95-154.18-4.56-37.2-48.26-102.16-107.63-97.02-74.7 6.7-85.65 114.58-85.65 114.58-10.04 49.88 24.2 156.43 116.33 136.62m84.7 214.14c10.28 38.7 43.95 40.43 43.95 40.43H427V683.55h-51.74c-23.22 6.96-34.5 25.1-36.98 32.8-2.74 7.8-8.71 27.6-3.57 44.83m169.07-531.1c0-72.42-41.13-131.08-92.2-131.08-50.92 0-92.21 58.66-92.21 131.07 0 72.5 41.3 131.16 92.2 131.16 51.08 0 92.21-58.66 92.21-131.16m248.1 9.1c8.86-54.92-35.08-118.88-83.34-129.82-48.34-11.1-108.7 66.28-114.18 116.74-6.55 61.72 8.79 123.28 76.86 132.06 68.16 8.87 112.03-63.87 120.65-118.97m46.35 433.02s-105.47-81.53-167-169.6c-83.4-129.91-201.98-77.05-241.62-11.02-39.47 66.03-101 107.87-109.7 118.9-8.87 10.93-127.36 74.8-101.07 191.55 26.28 116.65 118.73 114.5 118.73 114.5s68.08 6.7 147.1-10.94C523.7 888.03 591.7 910 591.7 910s184.57 61.72 235.07-57.18c50.41-118.97-28.53-180.61-28.53-180.61M362.42 849.17c-51.83-10.36-72.47-45.65-75.13-51.7-2.57-6.13-17.24-34.55-9.45-82.85 22.39-72.41 86.23-77.63 86.23-77.63h63.85v-78.46l54.4.82.08 289.82zm205.38-.83c-53.56-13.75-56.05-51.78-56.05-51.78V643.95l56.05-.92v137.12c3.4 14.59 21.65 17.32 21.65 17.32h56.88V643.95h59.62v204.39zm323.84-397.72c0-26.35-21.89-105.72-103.15-105.72-81.43 0-92.29 74.9-92.29 127.84 0 50.54 4.31 121.13 105.4 118.8 101.15-2.15 90.04-114.41 90.04-140.92"
|
|
1089
1255
|
/>
|
|
@@ -1094,6 +1260,19 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
1094
1260
|
// Inline stroked icons (currentColor) for the sidebar / toolbar.
|
|
1095
1261
|
_icon(name) {
|
|
1096
1262
|
switch (name) {
|
|
1263
|
+
case 'moon':
|
|
1264
|
+
return html`
|
|
1265
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
1266
|
+
<path d="M21 12.8A9 9 0 1111.2 3 7 7 0 0021 12.8z" />
|
|
1267
|
+
</svg>
|
|
1268
|
+
`;
|
|
1269
|
+
case 'sun':
|
|
1270
|
+
return html`
|
|
1271
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
1272
|
+
<circle cx="12" cy="12" r="4" />
|
|
1273
|
+
<path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
|
|
1274
|
+
</svg>
|
|
1275
|
+
`;
|
|
1097
1276
|
case 'components':
|
|
1098
1277
|
return html`
|
|
1099
1278
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -1199,6 +1378,7 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
1199
1378
|
--c-null: #b45309;
|
|
1200
1379
|
--c-fn: #2563eb;
|
|
1201
1380
|
--c-obj: #475467;
|
|
1381
|
+
--logo-fill: #2932e1;
|
|
1202
1382
|
|
|
1203
1383
|
position: fixed;
|
|
1204
1384
|
inset: 0;
|
|
@@ -1208,6 +1388,25 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
1208
1388
|
font-size: 12px;
|
|
1209
1389
|
color: var(--text);
|
|
1210
1390
|
}
|
|
1391
|
+
:host([theme='dark']) {
|
|
1392
|
+
--bg: #1e1e20;
|
|
1393
|
+
--surface: #26262a;
|
|
1394
|
+
--border: #34343a;
|
|
1395
|
+
--border-strong: #3a3a42;
|
|
1396
|
+
--field-border: #3a3a42;
|
|
1397
|
+
--text: #e4e4e7;
|
|
1398
|
+
--text-strong: #f4f4f5;
|
|
1399
|
+
--muted: #8b8b93;
|
|
1400
|
+
--muted-2: #a1a1aa;
|
|
1401
|
+
--c-key: #80cbc4;
|
|
1402
|
+
--c-num: #ffcb6b;
|
|
1403
|
+
--c-bool: #c792ea;
|
|
1404
|
+
--c-str: #c3e88d;
|
|
1405
|
+
--c-null: #f78c6c;
|
|
1406
|
+
--c-fn: #82aaff;
|
|
1407
|
+
--c-obj: #b0bec5;
|
|
1408
|
+
--logo-fill: #ffffff;
|
|
1409
|
+
}
|
|
1211
1410
|
.entry {
|
|
1212
1411
|
position: fixed;
|
|
1213
1412
|
pointer-events: auto;
|
|
@@ -1571,7 +1770,7 @@ export class VueDevToolsPanel extends LitElement {
|
|
|
1571
1770
|
cursor: pointer;
|
|
1572
1771
|
|
|
1573
1772
|
&:hover {
|
|
1574
|
-
color:
|
|
1773
|
+
color: var(--text);
|
|
1575
1774
|
}
|
|
1576
1775
|
& .section-filter {
|
|
1577
1776
|
margin-inline-start: 6px;
|