@json-render/devtools 0.18.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/LICENSE +201 -0
- package/README.md +46 -0
- package/dist/index.d.mts +509 -0
- package/dist/index.d.ts +509 -0
- package/dist/index.js +3311 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3261 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEVTOOLS_KEY_ATTR: () => DEVTOOLS_KEY_ATTR,
|
|
24
|
+
actionsTab: () => actionsTab,
|
|
25
|
+
buildCatalogDisplayData: () => buildCatalogDisplayData,
|
|
26
|
+
catalogTab: () => catalogTab,
|
|
27
|
+
createEventStore: () => createEventStore,
|
|
28
|
+
createPanel: () => createPanel,
|
|
29
|
+
createSelectionBus: () => createSelectionBus,
|
|
30
|
+
createStubTab: () => createStubTab,
|
|
31
|
+
extractSpecFromParts: () => extractSpecFromParts,
|
|
32
|
+
findAllElementsByKey: () => findAllElementsByKey,
|
|
33
|
+
findElementByKey: () => findElementByKey,
|
|
34
|
+
formatTime: () => formatTime,
|
|
35
|
+
h: () => h,
|
|
36
|
+
highlightElement: () => highlightElement,
|
|
37
|
+
isProduction: () => isProduction,
|
|
38
|
+
jsonPreview: () => jsonPreview,
|
|
39
|
+
pickerTab: () => pickerTab,
|
|
40
|
+
recordEvent: () => recordEvent,
|
|
41
|
+
recordUsage: () => recordUsage,
|
|
42
|
+
replaceChildren: () => replaceChildren,
|
|
43
|
+
scanMessageParts: () => scanMessageParts,
|
|
44
|
+
setHoverHighlight: () => setHoverHighlight,
|
|
45
|
+
specTab: () => specTab,
|
|
46
|
+
startPicker: () => startPicker,
|
|
47
|
+
stateTab: () => stateTab,
|
|
48
|
+
streamTab: () => streamTab,
|
|
49
|
+
tapJsonRenderStream: () => tapJsonRenderStream,
|
|
50
|
+
tapYamlStream: () => tapYamlStream
|
|
51
|
+
});
|
|
52
|
+
module.exports = __toCommonJS(index_exports);
|
|
53
|
+
|
|
54
|
+
// src/event-store.ts
|
|
55
|
+
function createEventStore(options = {}) {
|
|
56
|
+
const bufferSize = Math.max(1, options.bufferSize ?? 500);
|
|
57
|
+
const events = [];
|
|
58
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
59
|
+
function notify() {
|
|
60
|
+
for (const listener of listeners) {
|
|
61
|
+
try {
|
|
62
|
+
listener();
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (process.env.NODE_ENV !== "production") {
|
|
65
|
+
console.error("[json-render devtools] listener threw:", err);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
push(event) {
|
|
72
|
+
events.push(event);
|
|
73
|
+
if (events.length > bufferSize) {
|
|
74
|
+
events.splice(0, events.length - bufferSize);
|
|
75
|
+
}
|
|
76
|
+
notify();
|
|
77
|
+
},
|
|
78
|
+
snapshot() {
|
|
79
|
+
return events.slice();
|
|
80
|
+
},
|
|
81
|
+
subscribe(listener) {
|
|
82
|
+
listeners.add(listener);
|
|
83
|
+
return () => {
|
|
84
|
+
listeners.delete(listener);
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
clear() {
|
|
88
|
+
if (events.length === 0) return;
|
|
89
|
+
events.length = 0;
|
|
90
|
+
notify();
|
|
91
|
+
},
|
|
92
|
+
size() {
|
|
93
|
+
return events.length;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/prod-guard.ts
|
|
99
|
+
function isProduction() {
|
|
100
|
+
if (typeof process !== "undefined" && process.env) {
|
|
101
|
+
return process.env.NODE_ENV === "production";
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/panel/dom.ts
|
|
107
|
+
function appendChild(parent, child) {
|
|
108
|
+
if (child === null || child === void 0 || child === false) return;
|
|
109
|
+
if (Array.isArray(child)) {
|
|
110
|
+
for (const c of child) appendChild(parent, c);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (child instanceof Node) {
|
|
114
|
+
parent.appendChild(child);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
parent.appendChild(document.createTextNode(String(child)));
|
|
118
|
+
}
|
|
119
|
+
function h(tag, attrs, ...children) {
|
|
120
|
+
const el = document.createElement(tag);
|
|
121
|
+
if (attrs) {
|
|
122
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
123
|
+
if (value === null || value === void 0 || value === false) continue;
|
|
124
|
+
if (key === "className") {
|
|
125
|
+
el.className = String(value);
|
|
126
|
+
} else if (key === "style" && typeof value === "object") {
|
|
127
|
+
Object.assign(el.style, value);
|
|
128
|
+
} else if (key.startsWith("on") && typeof value === "function") {
|
|
129
|
+
el.addEventListener(key.slice(2).toLowerCase(), value);
|
|
130
|
+
} else if (key === "dataset" && typeof value === "object") {
|
|
131
|
+
for (const [dk, dv] of Object.entries(
|
|
132
|
+
value
|
|
133
|
+
)) {
|
|
134
|
+
el.dataset[dk] = String(dv);
|
|
135
|
+
}
|
|
136
|
+
} else if (typeof value === "boolean") {
|
|
137
|
+
if (value) el.setAttribute(key, "");
|
|
138
|
+
} else {
|
|
139
|
+
el.setAttribute(key, String(value));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
for (const child of children) appendChild(el, child);
|
|
144
|
+
return el;
|
|
145
|
+
}
|
|
146
|
+
function replaceChildren(parent, ...next) {
|
|
147
|
+
while (parent.firstChild) parent.removeChild(parent.firstChild);
|
|
148
|
+
for (const n of next) appendChild(parent, n);
|
|
149
|
+
}
|
|
150
|
+
function jsonPreview(value, max = 140) {
|
|
151
|
+
let text;
|
|
152
|
+
try {
|
|
153
|
+
text = JSON.stringify(value);
|
|
154
|
+
} catch {
|
|
155
|
+
text = String(value);
|
|
156
|
+
}
|
|
157
|
+
if (text === void 0) return "undefined";
|
|
158
|
+
if (text.length > max) return text.slice(0, max - 1) + "\u2026";
|
|
159
|
+
return text;
|
|
160
|
+
}
|
|
161
|
+
function formatTime(at) {
|
|
162
|
+
const d = new Date(at);
|
|
163
|
+
const hh = String(d.getHours()).padStart(2, "0");
|
|
164
|
+
const mm = String(d.getMinutes()).padStart(2, "0");
|
|
165
|
+
const ss = String(d.getSeconds()).padStart(2, "0");
|
|
166
|
+
const ms = String(d.getMilliseconds()).padStart(3, "0");
|
|
167
|
+
return `${hh}:${mm}:${ss}.${ms}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/picker.ts
|
|
171
|
+
var DEVTOOLS_KEY_ATTR = "data-jr-key";
|
|
172
|
+
var PICKER_OVERLAY_ID = "__jr_devtools_picker_overlay";
|
|
173
|
+
var PICKER_LABEL_ID = "__jr_devtools_picker_label";
|
|
174
|
+
var HOVER_OVERLAY_ID = "__jr_devtools_hover_overlay";
|
|
175
|
+
var HOVER_LABEL_ID = "__jr_devtools_hover_label";
|
|
176
|
+
function computeBounds(el) {
|
|
177
|
+
const own = el.getBoundingClientRect();
|
|
178
|
+
if (own.width > 0 && own.height > 0) return own;
|
|
179
|
+
let left = Infinity;
|
|
180
|
+
let top = Infinity;
|
|
181
|
+
let right = -Infinity;
|
|
182
|
+
let bottom = -Infinity;
|
|
183
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_ELEMENT);
|
|
184
|
+
let node = walker.nextNode();
|
|
185
|
+
while (node) {
|
|
186
|
+
const r = node.getBoundingClientRect();
|
|
187
|
+
if (r.width > 0 && r.height > 0) {
|
|
188
|
+
if (r.left < left) left = r.left;
|
|
189
|
+
if (r.top < top) top = r.top;
|
|
190
|
+
if (r.right > right) right = r.right;
|
|
191
|
+
if (r.bottom > bottom) bottom = r.bottom;
|
|
192
|
+
}
|
|
193
|
+
node = walker.nextNode();
|
|
194
|
+
}
|
|
195
|
+
if (!Number.isFinite(left) || !Number.isFinite(top)) return null;
|
|
196
|
+
return new DOMRect(left, top, right - left, bottom - top);
|
|
197
|
+
}
|
|
198
|
+
function paintOverlay(rect, label, ids) {
|
|
199
|
+
let ov = document.getElementById(ids.overlay);
|
|
200
|
+
if (!ov) {
|
|
201
|
+
ov = document.createElement("div");
|
|
202
|
+
ov.id = ids.overlay;
|
|
203
|
+
document.body.appendChild(ov);
|
|
204
|
+
}
|
|
205
|
+
ov.style.display = "block";
|
|
206
|
+
ov.style.left = `${rect.left}px`;
|
|
207
|
+
ov.style.top = `${rect.top}px`;
|
|
208
|
+
ov.style.width = `${rect.width}px`;
|
|
209
|
+
ov.style.height = `${rect.height}px`;
|
|
210
|
+
let lb = document.getElementById(ids.label);
|
|
211
|
+
if (!lb) {
|
|
212
|
+
lb = document.createElement("div");
|
|
213
|
+
lb.id = ids.label;
|
|
214
|
+
document.body.appendChild(lb);
|
|
215
|
+
}
|
|
216
|
+
lb.textContent = label;
|
|
217
|
+
lb.style.display = "block";
|
|
218
|
+
const labelLeft = Math.max(4, rect.left);
|
|
219
|
+
const labelTop = rect.top >= 24 ? rect.top - 22 : rect.top + rect.height + 4;
|
|
220
|
+
lb.style.left = `${labelLeft}px`;
|
|
221
|
+
lb.style.top = `${labelTop}px`;
|
|
222
|
+
return { overlay: ov, label: lb };
|
|
223
|
+
}
|
|
224
|
+
function hideOverlayPair(ids) {
|
|
225
|
+
const ov = document.getElementById(ids.overlay);
|
|
226
|
+
const lb = document.getElementById(ids.label);
|
|
227
|
+
if (ov) ov.style.display = "none";
|
|
228
|
+
if (lb) lb.style.display = "none";
|
|
229
|
+
}
|
|
230
|
+
function removeOverlayPair(ids) {
|
|
231
|
+
const ov = document.getElementById(ids.overlay);
|
|
232
|
+
const lb = document.getElementById(ids.label);
|
|
233
|
+
if (ov) ov.remove();
|
|
234
|
+
if (lb) lb.remove();
|
|
235
|
+
}
|
|
236
|
+
function startPicker(options) {
|
|
237
|
+
if (typeof document === "undefined") return null;
|
|
238
|
+
const IDS = { overlay: PICKER_OVERLAY_ID, label: PICKER_LABEL_ID };
|
|
239
|
+
let currentHover = null;
|
|
240
|
+
function paint(el) {
|
|
241
|
+
const rect = computeBounds(el);
|
|
242
|
+
if (!rect) {
|
|
243
|
+
hideOverlayPair(IDS);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const key = el.getAttribute(DEVTOOLS_KEY_ATTR) ?? "";
|
|
247
|
+
paintOverlay(
|
|
248
|
+
rect,
|
|
249
|
+
`${key} ${Math.round(rect.width)} \xD7 ${Math.round(rect.height)}`,
|
|
250
|
+
IDS
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const onMove = (ev) => {
|
|
254
|
+
const target = ev.target;
|
|
255
|
+
if (!target || !target.closest) return;
|
|
256
|
+
const host = target.closest(`[${DEVTOOLS_KEY_ATTR}]`);
|
|
257
|
+
if (host !== currentHover) currentHover = host;
|
|
258
|
+
if (host) paint(host);
|
|
259
|
+
else hideOverlayPair(IDS);
|
|
260
|
+
};
|
|
261
|
+
const onClick = (ev) => {
|
|
262
|
+
ev.preventDefault();
|
|
263
|
+
ev.stopPropagation();
|
|
264
|
+
const target = ev.target;
|
|
265
|
+
const host = target?.closest?.(`[${DEVTOOLS_KEY_ATTR}]`);
|
|
266
|
+
if (host) {
|
|
267
|
+
const key = host.getAttribute(DEVTOOLS_KEY_ATTR) ?? "";
|
|
268
|
+
stop();
|
|
269
|
+
options.onPick(key);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
const onKey = (ev) => {
|
|
273
|
+
if (ev.key === "Escape") {
|
|
274
|
+
ev.preventDefault();
|
|
275
|
+
stop();
|
|
276
|
+
options.onCancel?.();
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
const onReposition = () => {
|
|
280
|
+
if (currentHover && currentHover.isConnected) {
|
|
281
|
+
paint(currentHover);
|
|
282
|
+
} else if (currentHover) {
|
|
283
|
+
currentHover = null;
|
|
284
|
+
hideOverlayPair(IDS);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
function stop() {
|
|
288
|
+
removeOverlayPair(IDS);
|
|
289
|
+
currentHover = null;
|
|
290
|
+
document.documentElement.removeAttribute("data-jr-devtools-picking");
|
|
291
|
+
document.removeEventListener("mousemove", onMove, true);
|
|
292
|
+
document.removeEventListener("click", onClick, true);
|
|
293
|
+
document.removeEventListener("keydown", onKey, true);
|
|
294
|
+
window.removeEventListener("scroll", onReposition, true);
|
|
295
|
+
window.removeEventListener("resize", onReposition);
|
|
296
|
+
}
|
|
297
|
+
document.documentElement.setAttribute("data-jr-devtools-picking", "true");
|
|
298
|
+
document.addEventListener("mousemove", onMove, true);
|
|
299
|
+
document.addEventListener("click", onClick, true);
|
|
300
|
+
document.addEventListener("keydown", onKey, true);
|
|
301
|
+
window.addEventListener("scroll", onReposition, true);
|
|
302
|
+
window.addEventListener("resize", onReposition);
|
|
303
|
+
return { stop };
|
|
304
|
+
}
|
|
305
|
+
var HOVER_IDS = { overlay: HOVER_OVERLAY_ID, label: HOVER_LABEL_ID };
|
|
306
|
+
var HOVER_EXTRA_CLASS = "__jr_devtools_hover_extra";
|
|
307
|
+
var hoverKey = null;
|
|
308
|
+
var hoverListenersAttached = false;
|
|
309
|
+
var hoverExtraOverlays = [];
|
|
310
|
+
function ensureExtraOverlay(index) {
|
|
311
|
+
let ov = hoverExtraOverlays[index];
|
|
312
|
+
if (!ov) {
|
|
313
|
+
ov = document.createElement("div");
|
|
314
|
+
ov.className = HOVER_EXTRA_CLASS;
|
|
315
|
+
document.body.appendChild(ov);
|
|
316
|
+
hoverExtraOverlays[index] = ov;
|
|
317
|
+
}
|
|
318
|
+
return ov;
|
|
319
|
+
}
|
|
320
|
+
function hideExtrasFrom(index) {
|
|
321
|
+
for (let i = index; i < hoverExtraOverlays.length; i++) {
|
|
322
|
+
const ov = hoverExtraOverlays[i];
|
|
323
|
+
if (ov) ov.style.display = "none";
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function removeAllExtras() {
|
|
327
|
+
for (const ov of hoverExtraOverlays) ov.remove();
|
|
328
|
+
hoverExtraOverlays = [];
|
|
329
|
+
}
|
|
330
|
+
function repositionHover() {
|
|
331
|
+
if (!hoverKey) return;
|
|
332
|
+
const matches = findAllElementsByKey(hoverKey);
|
|
333
|
+
const first = matches[0];
|
|
334
|
+
if (!first) {
|
|
335
|
+
hideOverlayPair(HOVER_IDS);
|
|
336
|
+
hideExtrasFrom(0);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const firstRect = computeBounds(first);
|
|
340
|
+
if (!firstRect) {
|
|
341
|
+
hideOverlayPair(HOVER_IDS);
|
|
342
|
+
hideExtrasFrom(0);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const labelSuffix = matches.length > 1 ? ` (${matches.length} instances)` : "";
|
|
346
|
+
paintOverlay(
|
|
347
|
+
firstRect,
|
|
348
|
+
`${hoverKey} ${Math.round(firstRect.width)} \xD7 ${Math.round(firstRect.height)}${labelSuffix}`,
|
|
349
|
+
HOVER_IDS
|
|
350
|
+
);
|
|
351
|
+
for (let i = 1; i < matches.length; i++) {
|
|
352
|
+
const el = matches[i];
|
|
353
|
+
if (!el) continue;
|
|
354
|
+
const rect = computeBounds(el);
|
|
355
|
+
const ov = ensureExtraOverlay(i - 1);
|
|
356
|
+
if (!rect) {
|
|
357
|
+
ov.style.display = "none";
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
ov.style.display = "block";
|
|
361
|
+
ov.style.left = `${rect.left}px`;
|
|
362
|
+
ov.style.top = `${rect.top}px`;
|
|
363
|
+
ov.style.width = `${rect.width}px`;
|
|
364
|
+
ov.style.height = `${rect.height}px`;
|
|
365
|
+
}
|
|
366
|
+
hideExtrasFrom(Math.max(0, matches.length - 1));
|
|
367
|
+
}
|
|
368
|
+
function attachHoverListeners() {
|
|
369
|
+
if (hoverListenersAttached || typeof window === "undefined") return;
|
|
370
|
+
hoverListenersAttached = true;
|
|
371
|
+
window.addEventListener("scroll", repositionHover, true);
|
|
372
|
+
window.addEventListener("resize", repositionHover);
|
|
373
|
+
}
|
|
374
|
+
function detachHoverListeners() {
|
|
375
|
+
if (!hoverListenersAttached || typeof window === "undefined") return;
|
|
376
|
+
hoverListenersAttached = false;
|
|
377
|
+
window.removeEventListener("scroll", repositionHover, true);
|
|
378
|
+
window.removeEventListener("resize", repositionHover);
|
|
379
|
+
}
|
|
380
|
+
function setHoverHighlight(key) {
|
|
381
|
+
if (typeof document === "undefined") return;
|
|
382
|
+
if (!key) {
|
|
383
|
+
hoverKey = null;
|
|
384
|
+
removeOverlayPair(HOVER_IDS);
|
|
385
|
+
removeAllExtras();
|
|
386
|
+
detachHoverListeners();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
hoverKey = key;
|
|
390
|
+
repositionHover();
|
|
391
|
+
attachHoverListeners();
|
|
392
|
+
}
|
|
393
|
+
function findElementByKey(key) {
|
|
394
|
+
if (typeof document === "undefined") return null;
|
|
395
|
+
return document.querySelector(`[${DEVTOOLS_KEY_ATTR}="${cssEscape(key)}"]`);
|
|
396
|
+
}
|
|
397
|
+
function findAllElementsByKey(key) {
|
|
398
|
+
if (typeof document === "undefined") return [];
|
|
399
|
+
const selector = `[${DEVTOOLS_KEY_ATTR}="${cssEscape(key)}"]`;
|
|
400
|
+
return Array.from(document.querySelectorAll(selector));
|
|
401
|
+
}
|
|
402
|
+
function highlightElement(key, durationMs = 1200) {
|
|
403
|
+
if (typeof document === "undefined") return;
|
|
404
|
+
const el = findElementByKey(key);
|
|
405
|
+
if (!el) return;
|
|
406
|
+
const rect = computeBounds(el);
|
|
407
|
+
if (!rect) return;
|
|
408
|
+
const overlay = document.createElement("div");
|
|
409
|
+
overlay.className = "__jr_devtools_highlight";
|
|
410
|
+
overlay.style.cssText = `
|
|
411
|
+
position: fixed;
|
|
412
|
+
left: ${rect.left}px;
|
|
413
|
+
top: ${rect.top}px;
|
|
414
|
+
width: ${rect.width}px;
|
|
415
|
+
height: ${rect.height}px;
|
|
416
|
+
outline: 2px solid #60a5fa;
|
|
417
|
+
outline-offset: 2px;
|
|
418
|
+
pointer-events: none;
|
|
419
|
+
z-index: 2147483645;
|
|
420
|
+
transition: opacity 0.25s ease-out;
|
|
421
|
+
opacity: 1;
|
|
422
|
+
`;
|
|
423
|
+
document.body.appendChild(overlay);
|
|
424
|
+
setTimeout(
|
|
425
|
+
() => {
|
|
426
|
+
overlay.style.opacity = "0";
|
|
427
|
+
},
|
|
428
|
+
Math.max(0, durationMs - 250)
|
|
429
|
+
);
|
|
430
|
+
setTimeout(() => {
|
|
431
|
+
overlay.remove();
|
|
432
|
+
}, durationMs);
|
|
433
|
+
}
|
|
434
|
+
function cssEscape(value) {
|
|
435
|
+
if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
|
|
436
|
+
return CSS.escape(value);
|
|
437
|
+
}
|
|
438
|
+
return value.replace(/["\\]/g, "\\$&");
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// src/panel/styles.ts
|
|
442
|
+
var PANEL_CSS = `
|
|
443
|
+
:host {
|
|
444
|
+
/* Light theme \u2014 default. Overridden by the dark-mode media query below
|
|
445
|
+
so the panel follows the host OS's \`prefers-color-scheme\`. */
|
|
446
|
+
--jr-bg: #ffffff;
|
|
447
|
+
--jr-bg-muted: #f4f4f5;
|
|
448
|
+
--jr-bg-soft: #fafafa;
|
|
449
|
+
--jr-fg: #18181b;
|
|
450
|
+
--jr-fg-muted: #52525b;
|
|
451
|
+
--jr-fg-dim: #71717a;
|
|
452
|
+
--jr-border: #e4e4e7;
|
|
453
|
+
--jr-border-strong: #a1a1aa;
|
|
454
|
+
--jr-accent: #2563eb;
|
|
455
|
+
--jr-green: #16a34a;
|
|
456
|
+
--jr-red: #dc2626;
|
|
457
|
+
--jr-amber: #d97706;
|
|
458
|
+
--jr-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
459
|
+
--jr-font-ui: system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
460
|
+
all: initial;
|
|
461
|
+
color-scheme: light dark;
|
|
462
|
+
font-family: var(--jr-font-ui);
|
|
463
|
+
color: var(--jr-fg);
|
|
464
|
+
position: fixed;
|
|
465
|
+
z-index: 2147483647;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
@media (prefers-color-scheme: dark) {
|
|
469
|
+
:host {
|
|
470
|
+
--jr-bg: #09090b;
|
|
471
|
+
--jr-bg-muted: #111113;
|
|
472
|
+
--jr-bg-soft: #17171a;
|
|
473
|
+
--jr-fg: #fafafa;
|
|
474
|
+
--jr-fg-muted: #a1a1aa;
|
|
475
|
+
--jr-fg-dim: #71717a;
|
|
476
|
+
--jr-border: #27272a;
|
|
477
|
+
--jr-border-strong: #3f3f46;
|
|
478
|
+
--jr-accent: #60a5fa;
|
|
479
|
+
--jr-green: #4ade80;
|
|
480
|
+
--jr-red: #f87171;
|
|
481
|
+
--jr-amber: #fbbf24;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
* {
|
|
486
|
+
box-sizing: border-box;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
button {
|
|
490
|
+
font: inherit;
|
|
491
|
+
color: inherit;
|
|
492
|
+
background: transparent;
|
|
493
|
+
border: 0;
|
|
494
|
+
cursor: pointer;
|
|
495
|
+
padding: 0;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/* Floating toggle \u2014 frosted dark-glass badge inspired by Next.js Dev Tools.
|
|
499
|
+
Always dark regardless of OS \`prefers-color-scheme\` so the affordance
|
|
500
|
+
reads the same on both light and dark hosts. */
|
|
501
|
+
.jr-toggle {
|
|
502
|
+
position: fixed;
|
|
503
|
+
bottom: 20px;
|
|
504
|
+
right: 20px;
|
|
505
|
+
width: 36px;
|
|
506
|
+
height: 36px;
|
|
507
|
+
border-radius: 50%;
|
|
508
|
+
border: 0;
|
|
509
|
+
padding: 0;
|
|
510
|
+
background: rgba(0, 0, 0, 0.8);
|
|
511
|
+
-webkit-backdrop-filter: blur(48px);
|
|
512
|
+
backdrop-filter: blur(48px);
|
|
513
|
+
color: #ffffff;
|
|
514
|
+
/* Triple layer: outer hard ring, inner subtle highlight, drop shadow. */
|
|
515
|
+
box-shadow:
|
|
516
|
+
0 0 0 1px #171717,
|
|
517
|
+
inset 0 0 0 1px hsla(0, 0%, 100%, 0.14),
|
|
518
|
+
0 16px 32px -8px rgba(0, 0, 0, 0.24);
|
|
519
|
+
display: flex;
|
|
520
|
+
align-items: center;
|
|
521
|
+
justify-content: center;
|
|
522
|
+
will-change: scale, background;
|
|
523
|
+
transition:
|
|
524
|
+
scale 150ms cubic-bezier(0.23, 0.88, 0.26, 0.92),
|
|
525
|
+
background 150ms ease;
|
|
526
|
+
}
|
|
527
|
+
.jr-toggle[data-position="bottom-left"] { right: auto; left: 20px; }
|
|
528
|
+
.jr-toggle[data-position="right"] { bottom: auto; top: 20px; }
|
|
529
|
+
.jr-toggle:hover {
|
|
530
|
+
background: rgba(20, 20, 20, 0.88);
|
|
531
|
+
}
|
|
532
|
+
.jr-toggle:active {
|
|
533
|
+
scale: 0.95;
|
|
534
|
+
}
|
|
535
|
+
.jr-toggle[data-open="true"] {
|
|
536
|
+
box-shadow:
|
|
537
|
+
0 0 0 1px var(--jr-accent),
|
|
538
|
+
inset 0 0 0 1px hsla(0, 0%, 100%, 0.14),
|
|
539
|
+
0 16px 32px -8px rgba(0, 0, 0, 0.24);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.jr-panel {
|
|
543
|
+
position: fixed;
|
|
544
|
+
background: var(--jr-bg);
|
|
545
|
+
color: var(--jr-fg);
|
|
546
|
+
display: flex;
|
|
547
|
+
flex-direction: column;
|
|
548
|
+
overflow: hidden;
|
|
549
|
+
font-family: var(--jr-font-ui);
|
|
550
|
+
font-size: 12px;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.jr-panel[data-dock="bottom"] {
|
|
554
|
+
left: 0;
|
|
555
|
+
right: 0;
|
|
556
|
+
bottom: 0;
|
|
557
|
+
height: var(--jr-panel-height, 380px);
|
|
558
|
+
min-height: 180px;
|
|
559
|
+
max-height: 90vh;
|
|
560
|
+
border-top: 1px solid var(--jr-border);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.jr-panel[data-dock="right"] {
|
|
564
|
+
top: 0;
|
|
565
|
+
right: 0;
|
|
566
|
+
bottom: 0;
|
|
567
|
+
width: var(--jr-panel-width, 440px);
|
|
568
|
+
min-width: 260px;
|
|
569
|
+
max-width: 90vw;
|
|
570
|
+
border-left: 1px solid var(--jr-border);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.jr-panel[data-hidden="true"] {
|
|
574
|
+
display: none;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.jr-resize {
|
|
578
|
+
position: absolute;
|
|
579
|
+
z-index: 1;
|
|
580
|
+
}
|
|
581
|
+
.jr-panel[data-dock="bottom"] .jr-resize {
|
|
582
|
+
top: -3px;
|
|
583
|
+
left: 0;
|
|
584
|
+
right: 0;
|
|
585
|
+
height: 6px;
|
|
586
|
+
cursor: ns-resize;
|
|
587
|
+
}
|
|
588
|
+
.jr-panel[data-dock="right"] .jr-resize {
|
|
589
|
+
left: -3px;
|
|
590
|
+
top: 0;
|
|
591
|
+
bottom: 0;
|
|
592
|
+
width: 6px;
|
|
593
|
+
cursor: ew-resize;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.jr-header {
|
|
597
|
+
display: flex;
|
|
598
|
+
align-items: stretch;
|
|
599
|
+
height: 36px;
|
|
600
|
+
border-bottom: 1px solid var(--jr-border);
|
|
601
|
+
flex-shrink: 0;
|
|
602
|
+
background: var(--jr-bg);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.jr-title {
|
|
606
|
+
display: flex;
|
|
607
|
+
align-items: center;
|
|
608
|
+
padding: 0 12px;
|
|
609
|
+
font-family: var(--jr-font-mono);
|
|
610
|
+
font-size: 11px;
|
|
611
|
+
font-weight: 600;
|
|
612
|
+
color: var(--jr-fg);
|
|
613
|
+
border-right: 1px solid var(--jr-border);
|
|
614
|
+
letter-spacing: 0.02em;
|
|
615
|
+
}
|
|
616
|
+
.jr-tabs {
|
|
617
|
+
display: flex;
|
|
618
|
+
flex: 1;
|
|
619
|
+
min-width: 0;
|
|
620
|
+
overflow-x: auto;
|
|
621
|
+
}
|
|
622
|
+
.jr-tab {
|
|
623
|
+
display: inline-flex;
|
|
624
|
+
align-items: center;
|
|
625
|
+
gap: 6px;
|
|
626
|
+
padding: 0 12px;
|
|
627
|
+
font-family: var(--jr-font-mono);
|
|
628
|
+
font-size: 11px;
|
|
629
|
+
color: var(--jr-fg-muted);
|
|
630
|
+
background: transparent;
|
|
631
|
+
border-right: 1px solid var(--jr-border);
|
|
632
|
+
white-space: nowrap;
|
|
633
|
+
transition: color 0.1s ease, background 0.1s ease;
|
|
634
|
+
}
|
|
635
|
+
.jr-tab:hover {
|
|
636
|
+
color: var(--jr-fg);
|
|
637
|
+
background: var(--jr-bg-muted);
|
|
638
|
+
}
|
|
639
|
+
.jr-tab[data-active="true"] {
|
|
640
|
+
color: var(--jr-fg);
|
|
641
|
+
background: var(--jr-bg-muted);
|
|
642
|
+
box-shadow: inset 0 -2px 0 var(--jr-accent);
|
|
643
|
+
}
|
|
644
|
+
.jr-tab-count {
|
|
645
|
+
font-size: 10px;
|
|
646
|
+
color: var(--jr-fg-dim);
|
|
647
|
+
background: var(--jr-bg-soft);
|
|
648
|
+
border-radius: 999px;
|
|
649
|
+
padding: 1px 6px;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.jr-actions {
|
|
653
|
+
display: flex;
|
|
654
|
+
align-items: center;
|
|
655
|
+
padding: 0 8px;
|
|
656
|
+
gap: 4px;
|
|
657
|
+
border-left: 1px solid var(--jr-border);
|
|
658
|
+
}
|
|
659
|
+
.jr-icon-btn {
|
|
660
|
+
width: 26px;
|
|
661
|
+
height: 26px;
|
|
662
|
+
border-radius: 6px;
|
|
663
|
+
color: var(--jr-fg-muted);
|
|
664
|
+
display: inline-flex;
|
|
665
|
+
align-items: center;
|
|
666
|
+
justify-content: center;
|
|
667
|
+
font-size: 14px;
|
|
668
|
+
}
|
|
669
|
+
.jr-icon-btn:hover {
|
|
670
|
+
color: var(--jr-fg);
|
|
671
|
+
background: var(--jr-bg-muted);
|
|
672
|
+
}
|
|
673
|
+
.jr-icon-btn[data-active="true"] {
|
|
674
|
+
color: var(--jr-accent);
|
|
675
|
+
background: var(--jr-bg-muted);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.jr-body {
|
|
679
|
+
flex: 1;
|
|
680
|
+
min-height: 0;
|
|
681
|
+
display: flex;
|
|
682
|
+
overflow: hidden;
|
|
683
|
+
}
|
|
684
|
+
.jr-body > .jr-tab-panel {
|
|
685
|
+
flex: 1;
|
|
686
|
+
min-width: 0;
|
|
687
|
+
min-height: 0;
|
|
688
|
+
overflow: auto;
|
|
689
|
+
padding: 0;
|
|
690
|
+
display: flex;
|
|
691
|
+
flex-direction: column;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.jr-empty {
|
|
695
|
+
flex: 1;
|
|
696
|
+
min-height: 80px;
|
|
697
|
+
padding: 24px;
|
|
698
|
+
color: var(--jr-fg-dim);
|
|
699
|
+
font-size: 12px;
|
|
700
|
+
text-align: center;
|
|
701
|
+
display: flex;
|
|
702
|
+
align-items: center;
|
|
703
|
+
justify-content: center;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/* --- Rows / list primitives used by tabs --- */
|
|
707
|
+
|
|
708
|
+
.jr-row {
|
|
709
|
+
display: grid;
|
|
710
|
+
grid-template-columns: auto 1fr auto;
|
|
711
|
+
gap: 8px;
|
|
712
|
+
align-items: center;
|
|
713
|
+
padding: 4px 12px;
|
|
714
|
+
border-bottom: 1px solid var(--jr-border);
|
|
715
|
+
font-family: var(--jr-font-mono);
|
|
716
|
+
font-size: 11px;
|
|
717
|
+
cursor: pointer;
|
|
718
|
+
}
|
|
719
|
+
.jr-row:hover {
|
|
720
|
+
background: var(--jr-bg-muted);
|
|
721
|
+
}
|
|
722
|
+
.jr-row-time {
|
|
723
|
+
color: var(--jr-fg-dim);
|
|
724
|
+
font-size: 10px;
|
|
725
|
+
white-space: nowrap;
|
|
726
|
+
}
|
|
727
|
+
.jr-row-main {
|
|
728
|
+
color: var(--jr-fg);
|
|
729
|
+
overflow: hidden;
|
|
730
|
+
text-overflow: ellipsis;
|
|
731
|
+
white-space: nowrap;
|
|
732
|
+
}
|
|
733
|
+
.jr-row-meta {
|
|
734
|
+
color: var(--jr-fg-muted);
|
|
735
|
+
font-size: 10px;
|
|
736
|
+
white-space: nowrap;
|
|
737
|
+
}
|
|
738
|
+
.jr-row-expanded {
|
|
739
|
+
grid-column: 1 / -1;
|
|
740
|
+
padding: 8px 12px 10px 12px;
|
|
741
|
+
color: var(--jr-fg-muted);
|
|
742
|
+
white-space: pre-wrap;
|
|
743
|
+
word-break: break-all;
|
|
744
|
+
background: var(--jr-bg-soft);
|
|
745
|
+
border-top: 1px solid var(--jr-border);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
.jr-badge {
|
|
749
|
+
display: inline-flex;
|
|
750
|
+
align-items: center;
|
|
751
|
+
padding: 1px 6px;
|
|
752
|
+
border-radius: 4px;
|
|
753
|
+
font-family: var(--jr-font-mono);
|
|
754
|
+
font-size: 10px;
|
|
755
|
+
color: var(--jr-fg-muted);
|
|
756
|
+
background: var(--jr-bg-soft);
|
|
757
|
+
border: 1px solid var(--jr-border);
|
|
758
|
+
}
|
|
759
|
+
.jr-badge[data-tone="green"] { color: var(--jr-green); border-color: rgba(74, 222, 128, 0.25); }
|
|
760
|
+
.jr-badge[data-tone="red"] { color: var(--jr-red); border-color: rgba(248, 113, 113, 0.25); }
|
|
761
|
+
.jr-badge[data-tone="amber"] { color: var(--jr-amber); border-color: rgba(251, 191, 36, 0.25); }
|
|
762
|
+
.jr-badge[data-tone="accent"] { color: var(--jr-accent); border-color: rgba(96, 165, 250, 0.25); }
|
|
763
|
+
|
|
764
|
+
.jr-toolbar {
|
|
765
|
+
display: flex;
|
|
766
|
+
align-items: center;
|
|
767
|
+
gap: 8px;
|
|
768
|
+
padding: 6px 12px;
|
|
769
|
+
border-bottom: 1px solid var(--jr-border);
|
|
770
|
+
background: var(--jr-bg);
|
|
771
|
+
font-family: var(--jr-font-mono);
|
|
772
|
+
font-size: 10px;
|
|
773
|
+
color: var(--jr-fg-muted);
|
|
774
|
+
position: sticky;
|
|
775
|
+
top: 0;
|
|
776
|
+
z-index: 1;
|
|
777
|
+
}
|
|
778
|
+
.jr-toolbar .jr-icon-btn { width: auto; padding: 0 6px; font-size: 10px; }
|
|
779
|
+
.jr-search {
|
|
780
|
+
flex: 1;
|
|
781
|
+
background: var(--jr-bg-soft);
|
|
782
|
+
border: 1px solid var(--jr-border);
|
|
783
|
+
border-radius: 4px;
|
|
784
|
+
padding: 3px 6px;
|
|
785
|
+
color: var(--jr-fg);
|
|
786
|
+
font-family: var(--jr-font-mono);
|
|
787
|
+
font-size: 10px;
|
|
788
|
+
outline: none;
|
|
789
|
+
}
|
|
790
|
+
.jr-search:focus {
|
|
791
|
+
border-color: var(--jr-accent);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/* --- Spec tree --- */
|
|
795
|
+
|
|
796
|
+
.jr-tree-row {
|
|
797
|
+
display: flex;
|
|
798
|
+
align-items: center;
|
|
799
|
+
gap: 6px;
|
|
800
|
+
padding: 2px 12px;
|
|
801
|
+
font-family: var(--jr-font-mono);
|
|
802
|
+
font-size: 11px;
|
|
803
|
+
cursor: pointer;
|
|
804
|
+
white-space: nowrap;
|
|
805
|
+
color: var(--jr-fg);
|
|
806
|
+
}
|
|
807
|
+
.jr-tree-row:hover {
|
|
808
|
+
background: var(--jr-bg-muted);
|
|
809
|
+
}
|
|
810
|
+
.jr-tree-row[data-selected="true"] {
|
|
811
|
+
background: var(--jr-bg-muted);
|
|
812
|
+
box-shadow: inset 2px 0 0 var(--jr-accent);
|
|
813
|
+
}
|
|
814
|
+
.jr-tree-toggle {
|
|
815
|
+
width: 12px;
|
|
816
|
+
color: var(--jr-fg-dim);
|
|
817
|
+
display: inline-block;
|
|
818
|
+
text-align: center;
|
|
819
|
+
}
|
|
820
|
+
.jr-tree-type {
|
|
821
|
+
color: var(--jr-accent);
|
|
822
|
+
font-weight: 500;
|
|
823
|
+
}
|
|
824
|
+
.jr-tree-key {
|
|
825
|
+
color: var(--jr-fg-dim);
|
|
826
|
+
font-size: 10px;
|
|
827
|
+
}
|
|
828
|
+
.jr-tree-summary {
|
|
829
|
+
color: var(--jr-fg-muted);
|
|
830
|
+
font-size: 10px;
|
|
831
|
+
overflow: hidden;
|
|
832
|
+
text-overflow: ellipsis;
|
|
833
|
+
}
|
|
834
|
+
.jr-tree-hidden {
|
|
835
|
+
opacity: 0.45;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
.jr-split {
|
|
839
|
+
display: flex;
|
|
840
|
+
flex: 1;
|
|
841
|
+
min-height: 0;
|
|
842
|
+
width: 100%;
|
|
843
|
+
}
|
|
844
|
+
.jr-split-left {
|
|
845
|
+
flex: 1;
|
|
846
|
+
min-width: 0;
|
|
847
|
+
min-height: 0;
|
|
848
|
+
/* Pane itself no longer scrolls; the inner .jr-tree-rows container does,
|
|
849
|
+
so the breadcrumb bar above stays pinned. */
|
|
850
|
+
overflow: hidden;
|
|
851
|
+
border-right: 1px solid var(--jr-border);
|
|
852
|
+
display: flex;
|
|
853
|
+
flex-direction: column;
|
|
854
|
+
/* Selected row already has its own accent indicator, so no focus ring
|
|
855
|
+
on the container itself. */
|
|
856
|
+
outline: none;
|
|
857
|
+
}
|
|
858
|
+
.jr-tree-rows {
|
|
859
|
+
flex: 1;
|
|
860
|
+
min-height: 0;
|
|
861
|
+
overflow: auto;
|
|
862
|
+
/* Flex-column so .jr-empty inside can stretch + center vertically. */
|
|
863
|
+
display: flex;
|
|
864
|
+
flex-direction: column;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/* Generation switcher \u2014 only rendered when >1 specs (e.g. AI chat with
|
|
868
|
+
multiple assistant replies). Sits above the breadcrumbs. */
|
|
869
|
+
.jr-generations {
|
|
870
|
+
flex-shrink: 0;
|
|
871
|
+
display: flex;
|
|
872
|
+
align-items: center;
|
|
873
|
+
gap: 8px;
|
|
874
|
+
padding: 4px 8px;
|
|
875
|
+
min-height: 28px;
|
|
876
|
+
border-bottom: 1px solid var(--jr-border);
|
|
877
|
+
background: var(--jr-bg);
|
|
878
|
+
font-family: var(--jr-font-mono);
|
|
879
|
+
font-size: 11px;
|
|
880
|
+
}
|
|
881
|
+
.jr-generations:empty {
|
|
882
|
+
display: none;
|
|
883
|
+
}
|
|
884
|
+
.jr-generation-select {
|
|
885
|
+
background: var(--jr-bg-soft);
|
|
886
|
+
color: var(--jr-fg);
|
|
887
|
+
border: 1px solid var(--jr-border);
|
|
888
|
+
border-radius: 4px;
|
|
889
|
+
padding: 2px 6px;
|
|
890
|
+
font-family: inherit;
|
|
891
|
+
font-size: 11px;
|
|
892
|
+
outline: none;
|
|
893
|
+
cursor: pointer;
|
|
894
|
+
}
|
|
895
|
+
.jr-generation-select:focus-visible {
|
|
896
|
+
border-color: var(--jr-accent);
|
|
897
|
+
}
|
|
898
|
+
.jr-generations-count {
|
|
899
|
+
color: var(--jr-fg-dim);
|
|
900
|
+
font-size: 10px;
|
|
901
|
+
}
|
|
902
|
+
.jr-breadcrumbs {
|
|
903
|
+
flex-shrink: 0;
|
|
904
|
+
display: flex;
|
|
905
|
+
align-items: center;
|
|
906
|
+
gap: 4px;
|
|
907
|
+
padding: 4px 8px;
|
|
908
|
+
min-height: 28px;
|
|
909
|
+
border-bottom: 1px solid var(--jr-border);
|
|
910
|
+
background: var(--jr-bg);
|
|
911
|
+
white-space: nowrap;
|
|
912
|
+
font-family: var(--jr-font-mono);
|
|
913
|
+
font-size: 11px;
|
|
914
|
+
}
|
|
915
|
+
.jr-breadcrumbs-trail {
|
|
916
|
+
flex: 1;
|
|
917
|
+
min-width: 0;
|
|
918
|
+
display: flex;
|
|
919
|
+
align-items: center;
|
|
920
|
+
gap: 2px;
|
|
921
|
+
overflow-x: auto;
|
|
922
|
+
scrollbar-width: thin;
|
|
923
|
+
}
|
|
924
|
+
.jr-breadcrumbs-tools {
|
|
925
|
+
flex-shrink: 0;
|
|
926
|
+
display: flex;
|
|
927
|
+
align-items: center;
|
|
928
|
+
gap: 2px;
|
|
929
|
+
padding-left: 4px;
|
|
930
|
+
border-left: 1px solid var(--jr-border);
|
|
931
|
+
margin-left: 4px;
|
|
932
|
+
}
|
|
933
|
+
.jr-view-btn {
|
|
934
|
+
width: 24px;
|
|
935
|
+
height: 22px;
|
|
936
|
+
border-radius: 4px;
|
|
937
|
+
border: 0;
|
|
938
|
+
background: transparent;
|
|
939
|
+
color: var(--jr-fg-muted);
|
|
940
|
+
cursor: pointer;
|
|
941
|
+
display: inline-flex;
|
|
942
|
+
align-items: center;
|
|
943
|
+
justify-content: center;
|
|
944
|
+
}
|
|
945
|
+
.jr-view-btn:hover {
|
|
946
|
+
color: var(--jr-fg);
|
|
947
|
+
background: var(--jr-bg-muted);
|
|
948
|
+
}
|
|
949
|
+
.jr-view-btn[data-active="true"] {
|
|
950
|
+
color: var(--jr-accent);
|
|
951
|
+
background: var(--jr-bg-muted);
|
|
952
|
+
}
|
|
953
|
+
.jr-breadcrumb {
|
|
954
|
+
padding: 2px 6px;
|
|
955
|
+
border-radius: 4px;
|
|
956
|
+
color: var(--jr-fg-muted);
|
|
957
|
+
cursor: pointer;
|
|
958
|
+
background: transparent;
|
|
959
|
+
border: 0;
|
|
960
|
+
font: inherit;
|
|
961
|
+
white-space: nowrap;
|
|
962
|
+
}
|
|
963
|
+
.jr-breadcrumb:hover {
|
|
964
|
+
color: var(--jr-fg);
|
|
965
|
+
background: var(--jr-bg-muted);
|
|
966
|
+
}
|
|
967
|
+
.jr-breadcrumb[data-current="true"] {
|
|
968
|
+
color: var(--jr-accent);
|
|
969
|
+
background: var(--jr-bg-muted);
|
|
970
|
+
}
|
|
971
|
+
.jr-breadcrumb-sep {
|
|
972
|
+
color: var(--jr-fg-dim);
|
|
973
|
+
font-size: 10px;
|
|
974
|
+
padding: 0 1px;
|
|
975
|
+
user-select: none;
|
|
976
|
+
}
|
|
977
|
+
.jr-breadcrumbs-empty {
|
|
978
|
+
color: var(--jr-fg-dim);
|
|
979
|
+
padding: 2px 6px;
|
|
980
|
+
font-style: italic;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/* Raw JSON view \u2014 spec pretty-printed, selectable for copy/paste. */
|
|
984
|
+
.jr-raw {
|
|
985
|
+
margin: 0;
|
|
986
|
+
padding: 10px 12px;
|
|
987
|
+
flex: 1;
|
|
988
|
+
min-height: 0;
|
|
989
|
+
font-family: var(--jr-font-mono);
|
|
990
|
+
font-size: 11px;
|
|
991
|
+
line-height: 1.5;
|
|
992
|
+
color: var(--jr-fg);
|
|
993
|
+
white-space: pre;
|
|
994
|
+
overflow: auto;
|
|
995
|
+
-moz-tab-size: 2;
|
|
996
|
+
tab-size: 2;
|
|
997
|
+
}
|
|
998
|
+
.jr-split-right {
|
|
999
|
+
width: 40%;
|
|
1000
|
+
min-width: 240px;
|
|
1001
|
+
max-width: 60%;
|
|
1002
|
+
min-height: 0;
|
|
1003
|
+
overflow: auto;
|
|
1004
|
+
background: var(--jr-bg);
|
|
1005
|
+
display: flex;
|
|
1006
|
+
flex-direction: column;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
.jr-detail {
|
|
1010
|
+
padding: 10px 12px;
|
|
1011
|
+
font-family: var(--jr-font-mono);
|
|
1012
|
+
font-size: 11px;
|
|
1013
|
+
color: var(--jr-fg);
|
|
1014
|
+
}
|
|
1015
|
+
.jr-detail h4 {
|
|
1016
|
+
margin: 0 0 6px 0;
|
|
1017
|
+
font-family: var(--jr-font-mono);
|
|
1018
|
+
font-size: 10px;
|
|
1019
|
+
font-weight: 600;
|
|
1020
|
+
text-transform: uppercase;
|
|
1021
|
+
letter-spacing: 0.06em;
|
|
1022
|
+
color: var(--jr-fg-dim);
|
|
1023
|
+
}
|
|
1024
|
+
.jr-detail pre {
|
|
1025
|
+
margin: 0 0 14px 0;
|
|
1026
|
+
padding: 8px;
|
|
1027
|
+
background: var(--jr-bg-soft);
|
|
1028
|
+
border: 1px solid var(--jr-border);
|
|
1029
|
+
border-radius: 4px;
|
|
1030
|
+
white-space: pre-wrap;
|
|
1031
|
+
word-break: break-all;
|
|
1032
|
+
color: var(--jr-fg);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/* --- State editor --- */
|
|
1036
|
+
.jr-state-row {
|
|
1037
|
+
display: grid;
|
|
1038
|
+
grid-template-columns: 1fr 2fr;
|
|
1039
|
+
gap: 8px;
|
|
1040
|
+
padding: 3px 12px;
|
|
1041
|
+
border-bottom: 1px solid var(--jr-border);
|
|
1042
|
+
font-family: var(--jr-font-mono);
|
|
1043
|
+
font-size: 11px;
|
|
1044
|
+
align-items: center;
|
|
1045
|
+
}
|
|
1046
|
+
.jr-state-path {
|
|
1047
|
+
color: var(--jr-fg-muted);
|
|
1048
|
+
overflow: hidden;
|
|
1049
|
+
text-overflow: ellipsis;
|
|
1050
|
+
white-space: nowrap;
|
|
1051
|
+
}
|
|
1052
|
+
.jr-state-value {
|
|
1053
|
+
background: transparent;
|
|
1054
|
+
color: var(--jr-fg);
|
|
1055
|
+
border: 1px solid transparent;
|
|
1056
|
+
border-radius: 3px;
|
|
1057
|
+
padding: 2px 4px;
|
|
1058
|
+
font-family: var(--jr-font-mono);
|
|
1059
|
+
font-size: 11px;
|
|
1060
|
+
outline: none;
|
|
1061
|
+
width: 100%;
|
|
1062
|
+
min-width: 0;
|
|
1063
|
+
}
|
|
1064
|
+
.jr-state-value:hover {
|
|
1065
|
+
border-color: var(--jr-border);
|
|
1066
|
+
}
|
|
1067
|
+
.jr-state-value:focus {
|
|
1068
|
+
border-color: var(--jr-accent);
|
|
1069
|
+
background: var(--jr-bg-soft);
|
|
1070
|
+
}
|
|
1071
|
+
.jr-state-value[data-dirty="true"] {
|
|
1072
|
+
border-color: var(--jr-amber);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/* --- Catalog chips --- */
|
|
1076
|
+
.jr-chips {
|
|
1077
|
+
display: flex;
|
|
1078
|
+
flex-wrap: wrap;
|
|
1079
|
+
gap: 4px;
|
|
1080
|
+
margin-top: 4px;
|
|
1081
|
+
}
|
|
1082
|
+
.jr-chip {
|
|
1083
|
+
padding: 1px 5px;
|
|
1084
|
+
border-radius: 3px;
|
|
1085
|
+
font-family: var(--jr-font-mono);
|
|
1086
|
+
font-size: 10px;
|
|
1087
|
+
background: var(--jr-bg-soft);
|
|
1088
|
+
color: var(--jr-fg-muted);
|
|
1089
|
+
border: 1px solid var(--jr-border);
|
|
1090
|
+
}
|
|
1091
|
+
.jr-chip[data-tone="prop"] { color: var(--jr-green); border-color: rgba(74, 222, 128, 0.25); }
|
|
1092
|
+
.jr-chip[data-tone="event"] { color: var(--jr-accent); border-color: rgba(96, 165, 250, 0.25); }
|
|
1093
|
+
.jr-chip[data-tone="param"] { color: var(--jr-amber); border-color: rgba(251, 191, 36, 0.25); }
|
|
1094
|
+
|
|
1095
|
+
.jr-catalog-item {
|
|
1096
|
+
padding: 8px 12px;
|
|
1097
|
+
border-bottom: 1px solid var(--jr-border);
|
|
1098
|
+
}
|
|
1099
|
+
.jr-catalog-name {
|
|
1100
|
+
font-family: var(--jr-font-mono);
|
|
1101
|
+
font-size: 11px;
|
|
1102
|
+
color: var(--jr-fg);
|
|
1103
|
+
font-weight: 600;
|
|
1104
|
+
}
|
|
1105
|
+
.jr-catalog-desc {
|
|
1106
|
+
font-size: 11px;
|
|
1107
|
+
color: var(--jr-fg-muted);
|
|
1108
|
+
margin-top: 2px;
|
|
1109
|
+
}
|
|
1110
|
+
`;
|
|
1111
|
+
var HOST_CSS = `
|
|
1112
|
+
:root {
|
|
1113
|
+
--jr-devtools-offset-bottom: 0px;
|
|
1114
|
+
--jr-devtools-offset-right: 0px;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
html[data-jr-devtools-reserve="true"] body {
|
|
1118
|
+
box-sizing: border-box;
|
|
1119
|
+
padding-bottom: var(--jr-devtools-offset-bottom, 0);
|
|
1120
|
+
padding-right: var(--jr-devtools-offset-right, 0);
|
|
1121
|
+
transition: padding-bottom 0.12s ease-out, padding-right 0.12s ease-out;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
html[data-jr-devtools-picking="true"],
|
|
1125
|
+
html[data-jr-devtools-picking="true"] * {
|
|
1126
|
+
cursor: crosshair !important;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/* Floating overlays + labels used by two features:
|
|
1130
|
+
1. Picker \u2014 while the toolbar button is active and the user hovers the page
|
|
1131
|
+
2. Tree hover \u2014 while the user hovers a row in the Spec tab tree
|
|
1132
|
+
Both use the same visual language. Not rendered with a CSS \`outline\` on the
|
|
1133
|
+
target because the json-render element wrappers use \`display: contents\`
|
|
1134
|
+
(no box, no outline). */
|
|
1135
|
+
#__jr_devtools_picker_overlay,
|
|
1136
|
+
#__jr_devtools_hover_overlay {
|
|
1137
|
+
position: fixed;
|
|
1138
|
+
z-index: 2147483646;
|
|
1139
|
+
pointer-events: none;
|
|
1140
|
+
background: rgba(96, 165, 250, 0.15);
|
|
1141
|
+
outline: 2px solid #60a5fa;
|
|
1142
|
+
outline-offset: 0;
|
|
1143
|
+
border-radius: 2px;
|
|
1144
|
+
box-shadow: 0 0 0 1px rgba(9, 9, 11, 0.4);
|
|
1145
|
+
display: none;
|
|
1146
|
+
}
|
|
1147
|
+
#__jr_devtools_picker_label,
|
|
1148
|
+
#__jr_devtools_hover_label {
|
|
1149
|
+
position: fixed;
|
|
1150
|
+
z-index: 2147483646;
|
|
1151
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
1152
|
+
font-size: 11px;
|
|
1153
|
+
padding: 3px 7px;
|
|
1154
|
+
border-radius: 4px;
|
|
1155
|
+
background: #60a5fa;
|
|
1156
|
+
color: #09090b;
|
|
1157
|
+
pointer-events: none;
|
|
1158
|
+
white-space: nowrap;
|
|
1159
|
+
display: none;
|
|
1160
|
+
}
|
|
1161
|
+
/* Secondary overlays painted for the *other* instances of a repeated spec
|
|
1162
|
+
element \u2014 amber + dashed so they read as "siblings of the hovered one". */
|
|
1163
|
+
.__jr_devtools_hover_extra {
|
|
1164
|
+
position: fixed;
|
|
1165
|
+
z-index: 2147483646;
|
|
1166
|
+
pointer-events: none;
|
|
1167
|
+
background: rgba(251, 191, 36, 0.08);
|
|
1168
|
+
outline: 2px dashed #fbbf24;
|
|
1169
|
+
outline-offset: 0;
|
|
1170
|
+
border-radius: 2px;
|
|
1171
|
+
box-shadow: 0 0 0 1px rgba(9, 9, 11, 0.4);
|
|
1172
|
+
display: none;
|
|
1173
|
+
}
|
|
1174
|
+
`;
|
|
1175
|
+
|
|
1176
|
+
// src/panel/shell.ts
|
|
1177
|
+
var HOST_ATTR = "data-jr-devtools-host";
|
|
1178
|
+
var HOST_STYLE_ID = "__jr_devtools_host_styles";
|
|
1179
|
+
var RESERVE_ATTR = "data-jr-devtools-reserve";
|
|
1180
|
+
var OFFSET_VAR_BOTTOM = "--jr-devtools-offset-bottom";
|
|
1181
|
+
var OFFSET_VAR_RIGHT = "--jr-devtools-offset-right";
|
|
1182
|
+
var OFFSET_VAR_ALIAS = "--jr-devtools-offset";
|
|
1183
|
+
function ensureHostStyles() {
|
|
1184
|
+
if (document.getElementById(HOST_STYLE_ID)) return;
|
|
1185
|
+
const style = document.createElement("style");
|
|
1186
|
+
style.id = HOST_STYLE_ID;
|
|
1187
|
+
style.textContent = HOST_CSS;
|
|
1188
|
+
document.head.appendChild(style);
|
|
1189
|
+
}
|
|
1190
|
+
function setOffset(dock, px) {
|
|
1191
|
+
const root = document.documentElement;
|
|
1192
|
+
const bottom = dock === "bottom" ? px : 0;
|
|
1193
|
+
const right = dock === "right" ? px : 0;
|
|
1194
|
+
if (bottom > 0) root.style.setProperty(OFFSET_VAR_BOTTOM, `${bottom}px`);
|
|
1195
|
+
else root.style.removeProperty(OFFSET_VAR_BOTTOM);
|
|
1196
|
+
if (right > 0) root.style.setProperty(OFFSET_VAR_RIGHT, `${right}px`);
|
|
1197
|
+
else root.style.removeProperty(OFFSET_VAR_RIGHT);
|
|
1198
|
+
if (px > 0) root.style.setProperty(OFFSET_VAR_ALIAS, `${px}px`);
|
|
1199
|
+
else root.style.removeProperty(OFFSET_VAR_ALIAS);
|
|
1200
|
+
}
|
|
1201
|
+
function dockFromPosition(position) {
|
|
1202
|
+
return position === "right" ? "right" : "bottom";
|
|
1203
|
+
}
|
|
1204
|
+
var STORAGE_DOCK = "__jr_devtools_dock";
|
|
1205
|
+
var STORAGE_HEIGHT = "__jr_devtools_height";
|
|
1206
|
+
var STORAGE_WIDTH = "__jr_devtools_width";
|
|
1207
|
+
function readStoredDock() {
|
|
1208
|
+
if (typeof window === "undefined") return null;
|
|
1209
|
+
try {
|
|
1210
|
+
const raw = window.localStorage?.getItem(STORAGE_DOCK);
|
|
1211
|
+
return raw === "bottom" || raw === "right" ? raw : null;
|
|
1212
|
+
} catch {
|
|
1213
|
+
return null;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
function writeStoredDock(dock) {
|
|
1217
|
+
if (typeof window === "undefined") return;
|
|
1218
|
+
try {
|
|
1219
|
+
window.localStorage?.setItem(STORAGE_DOCK, dock);
|
|
1220
|
+
} catch {
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
function dockSpec(dock) {
|
|
1224
|
+
return dock === "right" ? {
|
|
1225
|
+
key: STORAGE_WIDTH,
|
|
1226
|
+
defaultSize: 440,
|
|
1227
|
+
minSize: 260,
|
|
1228
|
+
cssVar: "--jr-panel-width"
|
|
1229
|
+
} : {
|
|
1230
|
+
key: STORAGE_HEIGHT,
|
|
1231
|
+
defaultSize: 380,
|
|
1232
|
+
minSize: 180,
|
|
1233
|
+
cssVar: "--jr-panel-height"
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
var PICK_ICON_SVG = `
|
|
1237
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
1238
|
+
<path d="M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.944l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.944.033z"/>
|
|
1239
|
+
<path d="M21 11V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6"/>
|
|
1240
|
+
</svg>`;
|
|
1241
|
+
var TOGGLE_BRACES_SVG = `
|
|
1242
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" aria-hidden="true">
|
|
1243
|
+
<rect x="2" y="0" width="4" height="2"/>
|
|
1244
|
+
<rect x="2" y="2" width="2" height="4"/>
|
|
1245
|
+
<rect x="0" y="6" width="2" height="2"/>
|
|
1246
|
+
<rect x="2" y="8" width="2" height="4"/>
|
|
1247
|
+
<rect x="2" y="12" width="4" height="2"/>
|
|
1248
|
+
<rect x="8" y="0" width="4" height="2"/>
|
|
1249
|
+
<rect x="10" y="2" width="2" height="4"/>
|
|
1250
|
+
<rect x="12" y="6" width="2" height="2"/>
|
|
1251
|
+
<rect x="10" y="8" width="2" height="4"/>
|
|
1252
|
+
<rect x="8" y="12" width="4" height="2"/>
|
|
1253
|
+
</svg>`;
|
|
1254
|
+
var PANEL_BOTTOM_SVG = `
|
|
1255
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
1256
|
+
<rect width="18" height="18" x="3" y="3" rx="2"/>
|
|
1257
|
+
<path d="M3 15h18"/>
|
|
1258
|
+
</svg>`;
|
|
1259
|
+
var PANEL_RIGHT_SVG = `
|
|
1260
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
1261
|
+
<rect width="18" height="18" x="3" y="3" rx="2"/>
|
|
1262
|
+
<path d="M15 3v18"/>
|
|
1263
|
+
</svg>`;
|
|
1264
|
+
function matchHotkey(event, hotkey) {
|
|
1265
|
+
const parts = hotkey.toLowerCase().split("+").map((p) => p.trim());
|
|
1266
|
+
const isMac = typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.platform || navigator.userAgent);
|
|
1267
|
+
let needsMod = false;
|
|
1268
|
+
let needsShift = false;
|
|
1269
|
+
let needsAlt = false;
|
|
1270
|
+
let key = "";
|
|
1271
|
+
for (const p of parts) {
|
|
1272
|
+
if (p === "mod") needsMod = true;
|
|
1273
|
+
else if (p === "ctrl") needsMod = true;
|
|
1274
|
+
else if (p === "meta" || p === "cmd") needsMod = true;
|
|
1275
|
+
else if (p === "shift") needsShift = true;
|
|
1276
|
+
else if (p === "alt" || p === "option") needsAlt = true;
|
|
1277
|
+
else key = p;
|
|
1278
|
+
}
|
|
1279
|
+
const modOk = needsMod ? isMac ? event.metaKey : event.ctrlKey : true;
|
|
1280
|
+
const shiftOk = needsShift ? event.shiftKey : true;
|
|
1281
|
+
const altOk = needsAlt ? event.altKey : true;
|
|
1282
|
+
const keyOk = key ? event.key.toLowerCase() === key : true;
|
|
1283
|
+
return modOk && shiftOk && altOk && keyOk;
|
|
1284
|
+
}
|
|
1285
|
+
function createPanel(options) {
|
|
1286
|
+
const host = options.host ?? (typeof document !== "undefined" ? document.body : null);
|
|
1287
|
+
if (!host) {
|
|
1288
|
+
return {
|
|
1289
|
+
open() {
|
|
1290
|
+
},
|
|
1291
|
+
close() {
|
|
1292
|
+
},
|
|
1293
|
+
toggle() {
|
|
1294
|
+
},
|
|
1295
|
+
isOpen: () => false,
|
|
1296
|
+
refresh() {
|
|
1297
|
+
},
|
|
1298
|
+
destroy() {
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
const existing = host.querySelector(`[${HOST_ATTR}]`);
|
|
1303
|
+
if (existing && existing.__jrHandle) {
|
|
1304
|
+
return existing.__jrHandle;
|
|
1305
|
+
}
|
|
1306
|
+
ensureHostStyles();
|
|
1307
|
+
const rootHost = document.createElement("div");
|
|
1308
|
+
rootHost.setAttribute(HOST_ATTR, "");
|
|
1309
|
+
host.appendChild(rootHost);
|
|
1310
|
+
const shadow = rootHost.attachShadow({ mode: "open" });
|
|
1311
|
+
const styleEl = document.createElement("style");
|
|
1312
|
+
styleEl.textContent = PANEL_CSS;
|
|
1313
|
+
shadow.appendChild(styleEl);
|
|
1314
|
+
const position = options.position ?? "bottom-right";
|
|
1315
|
+
const allowDockToggle = options.allowDockToggle ?? true;
|
|
1316
|
+
const initialDock = (allowDockToggle ? readStoredDock() : null) ?? dockFromPosition(position);
|
|
1317
|
+
let dock = initialDock;
|
|
1318
|
+
const reserveSpace = options.reserveSpace ?? true;
|
|
1319
|
+
if (reserveSpace) {
|
|
1320
|
+
document.documentElement.setAttribute(RESERVE_ATTR, "true");
|
|
1321
|
+
}
|
|
1322
|
+
let open = options.initialOpen ?? false;
|
|
1323
|
+
let activeTabId = options.tabs[0]?.id ?? "";
|
|
1324
|
+
const ctx = options.context;
|
|
1325
|
+
const tabs = options.tabs;
|
|
1326
|
+
const tabInstances = /* @__PURE__ */ new Map();
|
|
1327
|
+
const toggleBtn = h("button", {
|
|
1328
|
+
className: "jr-toggle",
|
|
1329
|
+
"data-position": position,
|
|
1330
|
+
"data-open": String(open),
|
|
1331
|
+
title: "json-render devtools",
|
|
1332
|
+
"aria-label": "Toggle json-render devtools",
|
|
1333
|
+
onClick: () => toggle()
|
|
1334
|
+
});
|
|
1335
|
+
toggleBtn.innerHTML = TOGGLE_BRACES_SVG;
|
|
1336
|
+
shadow.appendChild(toggleBtn);
|
|
1337
|
+
const tabsStrip = h("div", { className: "jr-tabs" });
|
|
1338
|
+
const body = h("div", { className: "jr-body" });
|
|
1339
|
+
const pickBtn = h("button", {
|
|
1340
|
+
className: "jr-icon-btn jr-pick-btn",
|
|
1341
|
+
title: "Select an element in the page (Esc to cancel)",
|
|
1342
|
+
"aria-label": "Select an element in the page",
|
|
1343
|
+
onClick: () => togglePicker()
|
|
1344
|
+
});
|
|
1345
|
+
pickBtn.innerHTML = PICK_ICON_SVG;
|
|
1346
|
+
const dockBtn = h("button", {
|
|
1347
|
+
className: "jr-icon-btn jr-dock-btn",
|
|
1348
|
+
"aria-label": "Toggle devtools dock",
|
|
1349
|
+
onClick: () => toggleDock()
|
|
1350
|
+
});
|
|
1351
|
+
function updateDockButton() {
|
|
1352
|
+
if (dock === "bottom") {
|
|
1353
|
+
dockBtn.innerHTML = PANEL_RIGHT_SVG;
|
|
1354
|
+
dockBtn.title = "Dock to right";
|
|
1355
|
+
} else {
|
|
1356
|
+
dockBtn.innerHTML = PANEL_BOTTOM_SVG;
|
|
1357
|
+
dockBtn.title = "Dock to bottom";
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
updateDockButton();
|
|
1361
|
+
const closeBtn = h(
|
|
1362
|
+
"button",
|
|
1363
|
+
{
|
|
1364
|
+
className: "jr-icon-btn",
|
|
1365
|
+
title: "Close devtools",
|
|
1366
|
+
"aria-label": "Close",
|
|
1367
|
+
onClick: () => close()
|
|
1368
|
+
},
|
|
1369
|
+
"\xD7"
|
|
1370
|
+
);
|
|
1371
|
+
const header = h(
|
|
1372
|
+
"header",
|
|
1373
|
+
{ className: "jr-header" },
|
|
1374
|
+
h("div", { className: "jr-title" }, "json-render devtools"),
|
|
1375
|
+
tabsStrip,
|
|
1376
|
+
h(
|
|
1377
|
+
"div",
|
|
1378
|
+
{ className: "jr-actions" },
|
|
1379
|
+
pickBtn,
|
|
1380
|
+
allowDockToggle ? dockBtn : null,
|
|
1381
|
+
closeBtn
|
|
1382
|
+
)
|
|
1383
|
+
);
|
|
1384
|
+
const resizeHandle = h("div", {
|
|
1385
|
+
className: "jr-resize",
|
|
1386
|
+
"aria-label": "Resize devtools"
|
|
1387
|
+
});
|
|
1388
|
+
const panel = h(
|
|
1389
|
+
"section",
|
|
1390
|
+
{
|
|
1391
|
+
className: "jr-panel",
|
|
1392
|
+
"data-hidden": String(!open),
|
|
1393
|
+
"data-dock": dock,
|
|
1394
|
+
role: "complementary"
|
|
1395
|
+
},
|
|
1396
|
+
resizeHandle,
|
|
1397
|
+
header,
|
|
1398
|
+
body
|
|
1399
|
+
);
|
|
1400
|
+
shadow.appendChild(panel);
|
|
1401
|
+
let panelSize = loadSizeFor(dock);
|
|
1402
|
+
applyPanelSize(dock, panelSize);
|
|
1403
|
+
function loadSizeFor(d) {
|
|
1404
|
+
const spec = dockSpec(d);
|
|
1405
|
+
const stored = typeof window !== "undefined" ? window.localStorage?.getItem(spec.key) : null;
|
|
1406
|
+
let size = stored ? parseInt(stored, 10) : spec.defaultSize;
|
|
1407
|
+
if (!Number.isFinite(size) || size < spec.minSize) {
|
|
1408
|
+
size = spec.defaultSize;
|
|
1409
|
+
}
|
|
1410
|
+
return size;
|
|
1411
|
+
}
|
|
1412
|
+
function applyPanelSize(d, size) {
|
|
1413
|
+
const spec = dockSpec(d);
|
|
1414
|
+
const other = d === "bottom" ? "--jr-panel-width" : "--jr-panel-height";
|
|
1415
|
+
panel.style.removeProperty(other);
|
|
1416
|
+
panel.style.setProperty(spec.cssVar, `${size}px`);
|
|
1417
|
+
}
|
|
1418
|
+
resizeHandle.addEventListener("mousedown", (ev) => {
|
|
1419
|
+
ev.preventDefault();
|
|
1420
|
+
const d = dock;
|
|
1421
|
+
const startCoord = d === "right" ? ev.clientX : ev.clientY;
|
|
1422
|
+
const startSize = panelSize;
|
|
1423
|
+
const spec = dockSpec(d);
|
|
1424
|
+
const onMove = (e) => {
|
|
1425
|
+
const current = d === "right" ? e.clientX : e.clientY;
|
|
1426
|
+
const delta = startCoord - current;
|
|
1427
|
+
const viewport = d === "right" ? window.innerWidth : window.innerHeight;
|
|
1428
|
+
panelSize = Math.max(
|
|
1429
|
+
spec.minSize,
|
|
1430
|
+
Math.min(viewport * 0.9, startSize + delta)
|
|
1431
|
+
);
|
|
1432
|
+
applyPanelSize(d, panelSize);
|
|
1433
|
+
if (open && reserveSpace) setOffset(d, panelSize);
|
|
1434
|
+
};
|
|
1435
|
+
const onUp = () => {
|
|
1436
|
+
document.removeEventListener("mousemove", onMove);
|
|
1437
|
+
document.removeEventListener("mouseup", onUp);
|
|
1438
|
+
try {
|
|
1439
|
+
window.localStorage?.setItem(spec.key, String(panelSize));
|
|
1440
|
+
} catch {
|
|
1441
|
+
}
|
|
1442
|
+
};
|
|
1443
|
+
document.addEventListener("mousemove", onMove);
|
|
1444
|
+
document.addEventListener("mouseup", onUp);
|
|
1445
|
+
});
|
|
1446
|
+
function setDock(next, persist) {
|
|
1447
|
+
if (next === dock) return;
|
|
1448
|
+
setOffset(dock, 0);
|
|
1449
|
+
dock = next;
|
|
1450
|
+
panel.dataset.dock = next;
|
|
1451
|
+
panelSize = loadSizeFor(next);
|
|
1452
|
+
applyPanelSize(next, panelSize);
|
|
1453
|
+
if (open && reserveSpace) setOffset(next, panelSize);
|
|
1454
|
+
if (persist) writeStoredDock(next);
|
|
1455
|
+
updateDockButton();
|
|
1456
|
+
}
|
|
1457
|
+
function toggleDock() {
|
|
1458
|
+
if (!allowDockToggle) return;
|
|
1459
|
+
setDock(dock === "bottom" ? "right" : "bottom", true);
|
|
1460
|
+
}
|
|
1461
|
+
let pickerStop = null;
|
|
1462
|
+
function updatePickButton() {
|
|
1463
|
+
const active = !!pickerStop;
|
|
1464
|
+
pickBtn.dataset.active = String(active);
|
|
1465
|
+
pickBtn.title = active ? "Stop picking (Esc)" : "Select an element in the page";
|
|
1466
|
+
pickBtn.disabled = typeof document === "undefined" || !ctx.startPicker;
|
|
1467
|
+
}
|
|
1468
|
+
function stopPicker() {
|
|
1469
|
+
if (pickerStop) pickerStop();
|
|
1470
|
+
pickerStop = null;
|
|
1471
|
+
updatePickButton();
|
|
1472
|
+
}
|
|
1473
|
+
function startPickerSession() {
|
|
1474
|
+
if (pickerStop || !ctx.startPicker) return;
|
|
1475
|
+
const session = ctx.startPicker({
|
|
1476
|
+
onPick: (key) => {
|
|
1477
|
+
pickerStop = null;
|
|
1478
|
+
updatePickButton();
|
|
1479
|
+
ctx.selection.set(key);
|
|
1480
|
+
if (tabs.some((t) => t.id === "spec")) {
|
|
1481
|
+
ctx.activateTab("spec");
|
|
1482
|
+
} else {
|
|
1483
|
+
openPanel();
|
|
1484
|
+
}
|
|
1485
|
+
},
|
|
1486
|
+
onCancel: () => {
|
|
1487
|
+
pickerStop = null;
|
|
1488
|
+
updatePickButton();
|
|
1489
|
+
}
|
|
1490
|
+
});
|
|
1491
|
+
if (!session) return;
|
|
1492
|
+
pickerStop = session.stop;
|
|
1493
|
+
updatePickButton();
|
|
1494
|
+
}
|
|
1495
|
+
function togglePicker() {
|
|
1496
|
+
if (pickerStop) stopPicker();
|
|
1497
|
+
else startPickerSession();
|
|
1498
|
+
}
|
|
1499
|
+
updatePickButton();
|
|
1500
|
+
const tabButtons = /* @__PURE__ */ new Map();
|
|
1501
|
+
function renderTabs() {
|
|
1502
|
+
replaceChildren(
|
|
1503
|
+
tabsStrip,
|
|
1504
|
+
tabs.map((tab) => {
|
|
1505
|
+
const badgeValue = tab.badge?.(ctx);
|
|
1506
|
+
const btn = h(
|
|
1507
|
+
"button",
|
|
1508
|
+
{
|
|
1509
|
+
className: "jr-tab",
|
|
1510
|
+
"data-active": String(tab.id === activeTabId),
|
|
1511
|
+
"data-tab": tab.id,
|
|
1512
|
+
onClick: () => setActiveTab(tab.id)
|
|
1513
|
+
},
|
|
1514
|
+
tab.label,
|
|
1515
|
+
badgeValue !== void 0 && badgeValue !== "" && badgeValue !== 0 ? h("span", { className: "jr-tab-count" }, String(badgeValue)) : null
|
|
1516
|
+
);
|
|
1517
|
+
tabButtons.set(tab.id, btn);
|
|
1518
|
+
return btn;
|
|
1519
|
+
})
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
function activateTab(id) {
|
|
1523
|
+
const tab = tabs.find((t) => t.id === id);
|
|
1524
|
+
if (!tab) return;
|
|
1525
|
+
body.textContent = "";
|
|
1526
|
+
const panelEl = h("div", { className: "jr-tab-panel", "data-tab": id });
|
|
1527
|
+
body.appendChild(panelEl);
|
|
1528
|
+
for (const [prevId, inst] of tabInstances) {
|
|
1529
|
+
if (prevId !== id) {
|
|
1530
|
+
try {
|
|
1531
|
+
inst.destroy?.();
|
|
1532
|
+
} catch (err) {
|
|
1533
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1534
|
+
console.error("[json-render devtools] tab destroy threw:", err);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
tabInstances.delete(prevId);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
const instance = tab.mount(panelEl, ctx);
|
|
1541
|
+
tabInstances.set(id, instance);
|
|
1542
|
+
}
|
|
1543
|
+
function setActiveTab(id) {
|
|
1544
|
+
if (activeTabId === id) return;
|
|
1545
|
+
activeTabId = id;
|
|
1546
|
+
renderTabs();
|
|
1547
|
+
activateTab(id);
|
|
1548
|
+
}
|
|
1549
|
+
ctx.activateTab = (id) => {
|
|
1550
|
+
setActiveTab(id);
|
|
1551
|
+
openPanel();
|
|
1552
|
+
};
|
|
1553
|
+
renderTabs();
|
|
1554
|
+
if (activeTabId) activateTab(activeTabId);
|
|
1555
|
+
let scheduled = false;
|
|
1556
|
+
const runRefresh = () => {
|
|
1557
|
+
scheduled = false;
|
|
1558
|
+
renderTabs();
|
|
1559
|
+
const inst = tabInstances.get(activeTabId);
|
|
1560
|
+
inst?.update();
|
|
1561
|
+
};
|
|
1562
|
+
const scheduleRefresh = () => {
|
|
1563
|
+
if (scheduled) return;
|
|
1564
|
+
scheduled = true;
|
|
1565
|
+
if (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function") {
|
|
1566
|
+
window.requestAnimationFrame(runRefresh);
|
|
1567
|
+
} else {
|
|
1568
|
+
setTimeout(runRefresh, 16);
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
const unsubEvents = ctx.events.subscribe(scheduleRefresh);
|
|
1572
|
+
const hotkey = options.hotkey === void 0 ? "mod+shift+j" : options.hotkey;
|
|
1573
|
+
const onKey = hotkey ? (e) => {
|
|
1574
|
+
if (matchHotkey(e, hotkey)) {
|
|
1575
|
+
e.preventDefault();
|
|
1576
|
+
toggle();
|
|
1577
|
+
}
|
|
1578
|
+
} : null;
|
|
1579
|
+
if (onKey) document.addEventListener("keydown", onKey);
|
|
1580
|
+
function setOpen(next) {
|
|
1581
|
+
open = next;
|
|
1582
|
+
toggleBtn.dataset.open = String(open);
|
|
1583
|
+
panel.dataset.hidden = String(!open);
|
|
1584
|
+
setOffset(dock, open ? panelSize : 0);
|
|
1585
|
+
if (open) {
|
|
1586
|
+
const inst = tabInstances.get(activeTabId);
|
|
1587
|
+
inst?.update();
|
|
1588
|
+
} else {
|
|
1589
|
+
stopPicker();
|
|
1590
|
+
setHoverHighlight(null);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
function openPanel() {
|
|
1594
|
+
setOpen(true);
|
|
1595
|
+
}
|
|
1596
|
+
function close() {
|
|
1597
|
+
setOpen(false);
|
|
1598
|
+
}
|
|
1599
|
+
function toggle() {
|
|
1600
|
+
setOpen(!open);
|
|
1601
|
+
}
|
|
1602
|
+
setOffset(dock, open ? panelSize : 0);
|
|
1603
|
+
const handle = {
|
|
1604
|
+
open: openPanel,
|
|
1605
|
+
close,
|
|
1606
|
+
toggle,
|
|
1607
|
+
isOpen: () => open,
|
|
1608
|
+
refresh: scheduleRefresh,
|
|
1609
|
+
destroy() {
|
|
1610
|
+
unsubEvents();
|
|
1611
|
+
if (onKey) document.removeEventListener("keydown", onKey);
|
|
1612
|
+
stopPicker();
|
|
1613
|
+
setHoverHighlight(null);
|
|
1614
|
+
setOffset(dock, 0);
|
|
1615
|
+
for (const inst of tabInstances.values()) {
|
|
1616
|
+
try {
|
|
1617
|
+
inst.destroy?.();
|
|
1618
|
+
} catch {
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
tabInstances.clear();
|
|
1622
|
+
rootHost.remove();
|
|
1623
|
+
if (!document.querySelector(`[${HOST_ATTR}]`)) {
|
|
1624
|
+
document.documentElement.removeAttribute(RESERVE_ATTR);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
};
|
|
1628
|
+
rootHost.__jrHandle = handle;
|
|
1629
|
+
return handle;
|
|
1630
|
+
}
|
|
1631
|
+
function createStubTab(id, label) {
|
|
1632
|
+
return {
|
|
1633
|
+
id,
|
|
1634
|
+
label,
|
|
1635
|
+
mount(root) {
|
|
1636
|
+
const empty = h("div", { className: "jr-empty" }, `${label} (stub)`);
|
|
1637
|
+
root.appendChild(empty);
|
|
1638
|
+
return { update() {
|
|
1639
|
+
} };
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
// src/panel/types.ts
|
|
1645
|
+
function createSelectionBus() {
|
|
1646
|
+
let value = null;
|
|
1647
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1648
|
+
return {
|
|
1649
|
+
get: () => value,
|
|
1650
|
+
set(next) {
|
|
1651
|
+
if (value === next) return;
|
|
1652
|
+
value = next;
|
|
1653
|
+
for (const l of listeners) l(next);
|
|
1654
|
+
},
|
|
1655
|
+
subscribe(listener) {
|
|
1656
|
+
listeners.add(listener);
|
|
1657
|
+
return () => {
|
|
1658
|
+
listeners.delete(listener);
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
// src/panel/tabs/spec.ts
|
|
1665
|
+
var import_core = require("@json-render/core");
|
|
1666
|
+
var TAB_ID = "spec";
|
|
1667
|
+
var VIEW_TREE_SVG = `
|
|
1668
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
1669
|
+
<path d="M21 12h-8"/>
|
|
1670
|
+
<path d="M21 6H8"/>
|
|
1671
|
+
<path d="M21 18h-8"/>
|
|
1672
|
+
<path d="M3 6v4c0 1.1.9 2 2 2h3"/>
|
|
1673
|
+
<path d="M3 10v6c0 1.1.9 2 2 2h3"/>
|
|
1674
|
+
</svg>`;
|
|
1675
|
+
var VIEW_RAW_SVG = `
|
|
1676
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
1677
|
+
<path d="M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5a2 2 0 0 0 2 2h1"/>
|
|
1678
|
+
<path d="M16 21h1a2 2 0 0 0 2-2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1"/>
|
|
1679
|
+
</svg>`;
|
|
1680
|
+
function specTab() {
|
|
1681
|
+
return {
|
|
1682
|
+
id: TAB_ID,
|
|
1683
|
+
label: "Spec",
|
|
1684
|
+
badge(ctx) {
|
|
1685
|
+
const spec = ctx.getSpec();
|
|
1686
|
+
if (!spec) return void 0;
|
|
1687
|
+
return Object.keys(spec.elements).length || void 0;
|
|
1688
|
+
},
|
|
1689
|
+
mount(root, ctx) {
|
|
1690
|
+
return mountSpecTab(root, ctx);
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
function mountSpecTab(root, ctx) {
|
|
1695
|
+
const expanded = /* @__PURE__ */ new Set();
|
|
1696
|
+
const treePane = h("div", {
|
|
1697
|
+
className: "jr-split-left",
|
|
1698
|
+
role: "tree",
|
|
1699
|
+
tabindex: "0"
|
|
1700
|
+
});
|
|
1701
|
+
const generationBar = h("div", {
|
|
1702
|
+
className: "jr-generations",
|
|
1703
|
+
"aria-label": "Spec generation"
|
|
1704
|
+
});
|
|
1705
|
+
let selectedSpecId = null;
|
|
1706
|
+
let lastSeenLatestId = null;
|
|
1707
|
+
const breadcrumbs = h("nav", {
|
|
1708
|
+
className: "jr-breadcrumbs",
|
|
1709
|
+
"aria-label": "Selected element ancestors"
|
|
1710
|
+
});
|
|
1711
|
+
const crumbTrail = h("div", { className: "jr-breadcrumbs-trail" });
|
|
1712
|
+
const viewTools = h("div", {
|
|
1713
|
+
className: "jr-breadcrumbs-tools",
|
|
1714
|
+
role: "group",
|
|
1715
|
+
"aria-label": "Spec view"
|
|
1716
|
+
});
|
|
1717
|
+
const treeViewBtn = h("button", {
|
|
1718
|
+
className: "jr-view-btn",
|
|
1719
|
+
type: "button",
|
|
1720
|
+
title: "Tree view",
|
|
1721
|
+
"aria-label": "Tree view",
|
|
1722
|
+
"data-active": "true",
|
|
1723
|
+
onClick: () => setView("tree")
|
|
1724
|
+
});
|
|
1725
|
+
treeViewBtn.innerHTML = VIEW_TREE_SVG;
|
|
1726
|
+
const rawViewBtn = h("button", {
|
|
1727
|
+
className: "jr-view-btn",
|
|
1728
|
+
type: "button",
|
|
1729
|
+
title: "Raw JSON view",
|
|
1730
|
+
"aria-label": "Raw JSON view",
|
|
1731
|
+
"data-active": "false",
|
|
1732
|
+
onClick: () => setView("raw")
|
|
1733
|
+
});
|
|
1734
|
+
rawViewBtn.innerHTML = VIEW_RAW_SVG;
|
|
1735
|
+
viewTools.append(treeViewBtn, rawViewBtn);
|
|
1736
|
+
breadcrumbs.append(crumbTrail, viewTools);
|
|
1737
|
+
const treeRows = h("div", { className: "jr-tree-rows" });
|
|
1738
|
+
treePane.append(generationBar, breadcrumbs, treeRows);
|
|
1739
|
+
const detailPane = h("div", { className: "jr-split-right" });
|
|
1740
|
+
root.appendChild(h("div", { className: "jr-split" }, treePane, detailPane));
|
|
1741
|
+
let view = "tree";
|
|
1742
|
+
function setView(next) {
|
|
1743
|
+
if (view === next) return;
|
|
1744
|
+
view = next;
|
|
1745
|
+
treeViewBtn.dataset.active = String(view === "tree");
|
|
1746
|
+
rawViewBtn.dataset.active = String(view === "raw");
|
|
1747
|
+
render();
|
|
1748
|
+
}
|
|
1749
|
+
const unsubSelection = ctx.selection.subscribe(() => {
|
|
1750
|
+
const key = ctx.selection.get();
|
|
1751
|
+
if (key) {
|
|
1752
|
+
const { spec, generations } = resolveCurrentSpec();
|
|
1753
|
+
if (spec && !spec.elements[key] && generations.length > 1) {
|
|
1754
|
+
const match = generations.find((g) => g.spec.elements[key]);
|
|
1755
|
+
if (match) selectedSpecId = match.id;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
render();
|
|
1759
|
+
});
|
|
1760
|
+
const onTreeLeave = () => setHoverHighlight(null);
|
|
1761
|
+
treePane.addEventListener("mouseleave", onTreeLeave);
|
|
1762
|
+
let visibleKeys = [];
|
|
1763
|
+
function toggleExpanded(key) {
|
|
1764
|
+
if (expanded.has(key)) expanded.delete(key);
|
|
1765
|
+
else expanded.add(key);
|
|
1766
|
+
render();
|
|
1767
|
+
}
|
|
1768
|
+
function scrollSelectedIntoView() {
|
|
1769
|
+
const el = treePane.querySelector(
|
|
1770
|
+
'.jr-tree-row[data-selected="true"]'
|
|
1771
|
+
);
|
|
1772
|
+
el?.scrollIntoView({ block: "nearest" });
|
|
1773
|
+
}
|
|
1774
|
+
function moveSelection(nextKey) {
|
|
1775
|
+
if (!nextKey) return;
|
|
1776
|
+
ctx.selection.set(nextKey);
|
|
1777
|
+
setHoverHighlight(nextKey);
|
|
1778
|
+
scrollSelectedIntoView();
|
|
1779
|
+
}
|
|
1780
|
+
function onKeyDown(ev) {
|
|
1781
|
+
if (view === "raw") return;
|
|
1782
|
+
if (ev.metaKey || ev.ctrlKey || ev.altKey) return;
|
|
1783
|
+
const key = ev.key;
|
|
1784
|
+
if (key !== "ArrowUp" && key !== "ArrowDown" && key !== "ArrowLeft" && key !== "ArrowRight" && key !== "Enter" && key !== " " && key !== "Home" && key !== "End") {
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
const { spec } = resolveCurrentSpec();
|
|
1788
|
+
if (!spec || visibleKeys.length === 0) return;
|
|
1789
|
+
const current = ctx.selection.get();
|
|
1790
|
+
const idx = current ? visibleKeys.indexOf(current) : -1;
|
|
1791
|
+
ev.preventDefault();
|
|
1792
|
+
if (key === "ArrowDown") {
|
|
1793
|
+
if (idx < 0) moveSelection(visibleKeys[0]);
|
|
1794
|
+
else
|
|
1795
|
+
moveSelection(visibleKeys[Math.min(idx + 1, visibleKeys.length - 1)]);
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
if (key === "ArrowUp") {
|
|
1799
|
+
if (idx < 0) moveSelection(visibleKeys[0]);
|
|
1800
|
+
else moveSelection(visibleKeys[Math.max(idx - 1, 0)]);
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
if (key === "Home") {
|
|
1804
|
+
moveSelection(visibleKeys[0]);
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
if (key === "End") {
|
|
1808
|
+
moveSelection(visibleKeys[visibleKeys.length - 1]);
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
if (key === "ArrowRight") {
|
|
1812
|
+
if (!current) {
|
|
1813
|
+
moveSelection(visibleKeys[0]);
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
const el2 = spec.elements[current];
|
|
1817
|
+
const hasChildren = !!el2?.children?.length;
|
|
1818
|
+
if (!hasChildren) return;
|
|
1819
|
+
if (!expanded.has(current)) {
|
|
1820
|
+
expanded.add(current);
|
|
1821
|
+
render();
|
|
1822
|
+
scrollSelectedIntoView();
|
|
1823
|
+
} else {
|
|
1824
|
+
moveSelection(el2.children[0]);
|
|
1825
|
+
}
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1828
|
+
if (key === "ArrowLeft") {
|
|
1829
|
+
if (!current) return;
|
|
1830
|
+
const el2 = spec.elements[current];
|
|
1831
|
+
const hasChildren = !!el2?.children?.length;
|
|
1832
|
+
if (hasChildren && expanded.has(current)) {
|
|
1833
|
+
expanded.delete(current);
|
|
1834
|
+
render();
|
|
1835
|
+
scrollSelectedIntoView();
|
|
1836
|
+
} else {
|
|
1837
|
+
const parent = findParent(spec, current);
|
|
1838
|
+
if (parent) moveSelection(parent);
|
|
1839
|
+
}
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
if (!current) {
|
|
1843
|
+
moveSelection(visibleKeys[0]);
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
const el = spec.elements[current];
|
|
1847
|
+
if (el?.children?.length) {
|
|
1848
|
+
toggleExpanded(current);
|
|
1849
|
+
scrollSelectedIntoView();
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
const onFocusIn = () => {
|
|
1853
|
+
const current = ctx.selection.get();
|
|
1854
|
+
if (current) setHoverHighlight(current);
|
|
1855
|
+
};
|
|
1856
|
+
const onFocusOut = (ev) => {
|
|
1857
|
+
const next = ev.relatedTarget;
|
|
1858
|
+
if (!next || !treePane.contains(next)) {
|
|
1859
|
+
setHoverHighlight(null);
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
const onMouseDown = () => {
|
|
1863
|
+
try {
|
|
1864
|
+
treePane.focus({ preventScroll: true });
|
|
1865
|
+
} catch {
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
treePane.addEventListener("mousedown", onMouseDown);
|
|
1869
|
+
treePane.addEventListener("keydown", onKeyDown);
|
|
1870
|
+
treePane.addEventListener("focusin", onFocusIn);
|
|
1871
|
+
treePane.addEventListener("focusout", onFocusOut);
|
|
1872
|
+
function resolveCurrentSpec() {
|
|
1873
|
+
const generations = ctx.getSpecs?.() ?? [];
|
|
1874
|
+
if (generations.length === 0) {
|
|
1875
|
+
return { spec: ctx.getSpec(), generations };
|
|
1876
|
+
}
|
|
1877
|
+
const latest = generations[generations.length - 1];
|
|
1878
|
+
if (selectedSpecId == null || !generations.some((g) => g.id === selectedSpecId)) {
|
|
1879
|
+
selectedSpecId = latest.id;
|
|
1880
|
+
} else if (lastSeenLatestId != null && selectedSpecId === lastSeenLatestId && lastSeenLatestId !== latest.id) {
|
|
1881
|
+
selectedSpecId = latest.id;
|
|
1882
|
+
}
|
|
1883
|
+
lastSeenLatestId = latest.id;
|
|
1884
|
+
const picked = generations.find((g) => g.id === selectedSpecId);
|
|
1885
|
+
return { spec: picked.spec, generations };
|
|
1886
|
+
}
|
|
1887
|
+
function renderGenerationBar(generations) {
|
|
1888
|
+
if (generations.length < 2) {
|
|
1889
|
+
replaceChildren(generationBar, null);
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
const latestId = generations[generations.length - 1].id;
|
|
1893
|
+
const select = h(
|
|
1894
|
+
"select",
|
|
1895
|
+
{
|
|
1896
|
+
className: "jr-generation-select",
|
|
1897
|
+
"aria-label": "Inspect generation",
|
|
1898
|
+
onChange: (ev) => {
|
|
1899
|
+
selectedSpecId = ev.target.value;
|
|
1900
|
+
render();
|
|
1901
|
+
}
|
|
1902
|
+
},
|
|
1903
|
+
...generations.map(
|
|
1904
|
+
(g, i) => h(
|
|
1905
|
+
"option",
|
|
1906
|
+
{
|
|
1907
|
+
value: g.id,
|
|
1908
|
+
selected: g.id === selectedSpecId ? "true" : void 0
|
|
1909
|
+
},
|
|
1910
|
+
g.id === latestId ? `Gen ${i + 1} (latest)` : `Gen ${i + 1}`
|
|
1911
|
+
)
|
|
1912
|
+
)
|
|
1913
|
+
);
|
|
1914
|
+
const count = h(
|
|
1915
|
+
"span",
|
|
1916
|
+
{ className: "jr-generations-count" },
|
|
1917
|
+
`${generations.length} generation${generations.length === 1 ? "" : "s"}`
|
|
1918
|
+
);
|
|
1919
|
+
replaceChildren(generationBar, select, count);
|
|
1920
|
+
}
|
|
1921
|
+
function render() {
|
|
1922
|
+
const { spec, generations } = resolveCurrentSpec();
|
|
1923
|
+
renderGenerationBar(generations);
|
|
1924
|
+
if (!spec || !spec.root || Object.keys(spec.elements).length === 0) {
|
|
1925
|
+
visibleKeys = [];
|
|
1926
|
+
renderBreadcrumbs(null, null);
|
|
1927
|
+
replaceChildren(
|
|
1928
|
+
treeRows,
|
|
1929
|
+
h("div", { className: "jr-empty" }, "No spec attached.")
|
|
1930
|
+
);
|
|
1931
|
+
replaceChildren(detailPane, null);
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
const issues = collectIssues(spec);
|
|
1935
|
+
const selected = ctx.selection.get();
|
|
1936
|
+
const pathLeafFirst = selected ? findPath(spec, selected) : [];
|
|
1937
|
+
for (const k of pathLeafFirst) expanded.add(k);
|
|
1938
|
+
visibleKeys = collectVisibleKeys(spec, expanded);
|
|
1939
|
+
renderBreadcrumbs(spec, pathLeafFirst);
|
|
1940
|
+
if (view === "raw") {
|
|
1941
|
+
renderRaw(spec);
|
|
1942
|
+
} else {
|
|
1943
|
+
replaceChildren(
|
|
1944
|
+
treeRows,
|
|
1945
|
+
renderNode(
|
|
1946
|
+
spec,
|
|
1947
|
+
spec.root,
|
|
1948
|
+
0,
|
|
1949
|
+
expanded,
|
|
1950
|
+
selected,
|
|
1951
|
+
issues,
|
|
1952
|
+
ctx,
|
|
1953
|
+
toggleExpanded,
|
|
1954
|
+
null
|
|
1955
|
+
)
|
|
1956
|
+
);
|
|
1957
|
+
}
|
|
1958
|
+
if (selected && spec.elements[selected]) {
|
|
1959
|
+
renderDetail(detailPane, spec, selected, issues);
|
|
1960
|
+
} else {
|
|
1961
|
+
replaceChildren(
|
|
1962
|
+
detailPane,
|
|
1963
|
+
h("div", { className: "jr-empty" }, "Select an element to inspect.")
|
|
1964
|
+
);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
function renderRaw(spec) {
|
|
1968
|
+
let text;
|
|
1969
|
+
try {
|
|
1970
|
+
text = JSON.stringify(spec, null, 2);
|
|
1971
|
+
} catch {
|
|
1972
|
+
text = String(spec);
|
|
1973
|
+
}
|
|
1974
|
+
replaceChildren(treeRows, h("pre", { className: "jr-raw" }, text));
|
|
1975
|
+
}
|
|
1976
|
+
function renderBreadcrumbs(spec, pathLeafFirst) {
|
|
1977
|
+
if (!spec || !pathLeafFirst || pathLeafFirst.length === 0) {
|
|
1978
|
+
replaceChildren(
|
|
1979
|
+
crumbTrail,
|
|
1980
|
+
h("span", { className: "jr-breadcrumbs-empty" }, "No element selected")
|
|
1981
|
+
);
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
const path = [...pathLeafFirst].reverse();
|
|
1985
|
+
const parts = [];
|
|
1986
|
+
path.forEach((key, i) => {
|
|
1987
|
+
const el = spec.elements[key];
|
|
1988
|
+
const label = el?.type ?? "<missing>";
|
|
1989
|
+
const isCurrent = i === path.length - 1;
|
|
1990
|
+
const crumb = h(
|
|
1991
|
+
"button",
|
|
1992
|
+
{
|
|
1993
|
+
className: "jr-breadcrumb",
|
|
1994
|
+
type: "button",
|
|
1995
|
+
"data-current": String(isCurrent),
|
|
1996
|
+
title: `#${key}`,
|
|
1997
|
+
onClick: (ev) => {
|
|
1998
|
+
ev.stopPropagation();
|
|
1999
|
+
ctx.selection.set(key);
|
|
2000
|
+
setHoverHighlight(key);
|
|
2001
|
+
},
|
|
2002
|
+
onMouseEnter: () => setHoverHighlight(key),
|
|
2003
|
+
onMouseLeave: () => setHoverHighlight(null)
|
|
2004
|
+
},
|
|
2005
|
+
label
|
|
2006
|
+
);
|
|
2007
|
+
parts.push(crumb);
|
|
2008
|
+
if (!isCurrent) {
|
|
2009
|
+
parts.push(h("span", { className: "jr-breadcrumb-sep" }, "\u25B8"));
|
|
2010
|
+
}
|
|
2011
|
+
});
|
|
2012
|
+
replaceChildren(crumbTrail, parts);
|
|
2013
|
+
}
|
|
2014
|
+
render();
|
|
2015
|
+
return {
|
|
2016
|
+
update: render,
|
|
2017
|
+
destroy() {
|
|
2018
|
+
unsubSelection();
|
|
2019
|
+
treePane.removeEventListener("mouseleave", onTreeLeave);
|
|
2020
|
+
treePane.removeEventListener("mousedown", onMouseDown);
|
|
2021
|
+
treePane.removeEventListener("keydown", onKeyDown);
|
|
2022
|
+
treePane.removeEventListener("focusin", onFocusIn);
|
|
2023
|
+
treePane.removeEventListener("focusout", onFocusOut);
|
|
2024
|
+
setHoverHighlight(null);
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
function collectVisibleKeys(spec, expanded) {
|
|
2029
|
+
const list = [];
|
|
2030
|
+
function walk(key) {
|
|
2031
|
+
list.push(key);
|
|
2032
|
+
const el = spec.elements[key];
|
|
2033
|
+
if (!el?.children || el.children.length === 0) return;
|
|
2034
|
+
if (!expanded.has(key)) return;
|
|
2035
|
+
for (const child of el.children) walk(child);
|
|
2036
|
+
}
|
|
2037
|
+
walk(spec.root);
|
|
2038
|
+
return list;
|
|
2039
|
+
}
|
|
2040
|
+
function findParent(spec, key) {
|
|
2041
|
+
for (const [parentKey, el] of Object.entries(spec.elements)) {
|
|
2042
|
+
if (el.children?.includes(key)) return parentKey;
|
|
2043
|
+
}
|
|
2044
|
+
return null;
|
|
2045
|
+
}
|
|
2046
|
+
function collectIssues(spec) {
|
|
2047
|
+
const result = (0, import_core.validateSpec)(spec, { checkOrphans: true });
|
|
2048
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2049
|
+
for (const issue of result.issues) {
|
|
2050
|
+
const k = issue.elementKey ?? "__spec__";
|
|
2051
|
+
const arr = byKey.get(k) ?? [];
|
|
2052
|
+
arr.push(issue);
|
|
2053
|
+
byKey.set(k, arr);
|
|
2054
|
+
}
|
|
2055
|
+
return { all: result.issues, byKey };
|
|
2056
|
+
}
|
|
2057
|
+
function findPath(spec, key) {
|
|
2058
|
+
const path = [];
|
|
2059
|
+
function walk(current) {
|
|
2060
|
+
if (current === key) {
|
|
2061
|
+
path.push(current);
|
|
2062
|
+
return true;
|
|
2063
|
+
}
|
|
2064
|
+
const el = spec.elements[current];
|
|
2065
|
+
if (!el?.children) return false;
|
|
2066
|
+
for (const child of el.children) {
|
|
2067
|
+
if (walk(child)) {
|
|
2068
|
+
path.push(current);
|
|
2069
|
+
return true;
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
return false;
|
|
2073
|
+
}
|
|
2074
|
+
walk(spec.root);
|
|
2075
|
+
return path;
|
|
2076
|
+
}
|
|
2077
|
+
function renderNode(spec, key, depth, expanded, selected, issues, ctx, onToggle, parentHidden) {
|
|
2078
|
+
const el = spec.elements[key];
|
|
2079
|
+
if (!el) {
|
|
2080
|
+
return h(
|
|
2081
|
+
"div",
|
|
2082
|
+
{
|
|
2083
|
+
className: "jr-tree-row",
|
|
2084
|
+
style: { paddingLeft: `${12 + depth * 14}px` }
|
|
2085
|
+
},
|
|
2086
|
+
h("span", { className: "jr-tree-toggle" }, " "),
|
|
2087
|
+
h("span", { className: "jr-tree-type" }, "<missing>"),
|
|
2088
|
+
h("span", { className: "jr-tree-key" }, key)
|
|
2089
|
+
);
|
|
2090
|
+
}
|
|
2091
|
+
const hasChildren = Array.isArray(el.children) && el.children.length > 0;
|
|
2092
|
+
const isExpanded = hasChildren && expanded.has(key);
|
|
2093
|
+
const isSelected = selected === key;
|
|
2094
|
+
const elementIssues = issues.byKey.get(key) ?? [];
|
|
2095
|
+
const hasErrors = elementIssues.some((i) => i.severity === "error");
|
|
2096
|
+
const summary = makeSummary(el);
|
|
2097
|
+
const hiddenHere = el.visible !== void 0 ? null : parentHidden;
|
|
2098
|
+
const row = h(
|
|
2099
|
+
"div",
|
|
2100
|
+
{
|
|
2101
|
+
className: `jr-tree-row${hiddenHere === true ? " jr-tree-hidden" : ""}`,
|
|
2102
|
+
"data-selected": String(isSelected),
|
|
2103
|
+
style: { paddingLeft: `${8 + depth * 14}px` },
|
|
2104
|
+
onClick: (ev) => {
|
|
2105
|
+
ev.stopPropagation();
|
|
2106
|
+
ctx.selection.set(key);
|
|
2107
|
+
},
|
|
2108
|
+
// Paint a persistent bounding-box overlay over the matching DOM
|
|
2109
|
+
// element while the tree row is hovered — gives the same visual
|
|
2110
|
+
// affordance as Chrome DevTools' element picker without needing to
|
|
2111
|
+
// enter pick mode.
|
|
2112
|
+
onMouseEnter: () => {
|
|
2113
|
+
setHoverHighlight(key);
|
|
2114
|
+
},
|
|
2115
|
+
onMouseLeave: () => {
|
|
2116
|
+
setHoverHighlight(null);
|
|
2117
|
+
}
|
|
2118
|
+
},
|
|
2119
|
+
h(
|
|
2120
|
+
"span",
|
|
2121
|
+
{
|
|
2122
|
+
className: "jr-tree-toggle",
|
|
2123
|
+
onClick: (ev) => {
|
|
2124
|
+
ev.stopPropagation();
|
|
2125
|
+
if (hasChildren) onToggle(key);
|
|
2126
|
+
}
|
|
2127
|
+
},
|
|
2128
|
+
hasChildren ? isExpanded ? "\u25BE" : "\u25B8" : " "
|
|
2129
|
+
),
|
|
2130
|
+
h("span", { className: "jr-tree-type" }, el.type),
|
|
2131
|
+
h("span", { className: "jr-tree-key" }, `#${key}`),
|
|
2132
|
+
summary ? h("span", { className: "jr-tree-summary" }, summary) : null,
|
|
2133
|
+
el.visible !== void 0 ? h("span", { className: "jr-badge", "data-tone": "amber" }, "visible") : null,
|
|
2134
|
+
el.repeat ? h("span", { className: "jr-badge", "data-tone": "accent" }, "repeat") : null,
|
|
2135
|
+
hasErrors ? h(
|
|
2136
|
+
"span",
|
|
2137
|
+
{
|
|
2138
|
+
className: "jr-badge",
|
|
2139
|
+
"data-tone": "red",
|
|
2140
|
+
title: elementIssues.map((i) => i.message).join("\n")
|
|
2141
|
+
},
|
|
2142
|
+
`${elementIssues.length}`
|
|
2143
|
+
) : null
|
|
2144
|
+
);
|
|
2145
|
+
const container = h("div", null, row);
|
|
2146
|
+
if (isExpanded && hasChildren) {
|
|
2147
|
+
for (const childKey of el.children) {
|
|
2148
|
+
const childNode = renderNode(
|
|
2149
|
+
spec,
|
|
2150
|
+
childKey,
|
|
2151
|
+
depth + 1,
|
|
2152
|
+
expanded,
|
|
2153
|
+
selected,
|
|
2154
|
+
issues,
|
|
2155
|
+
ctx,
|
|
2156
|
+
onToggle,
|
|
2157
|
+
hiddenHere
|
|
2158
|
+
);
|
|
2159
|
+
if (childNode) container.appendChild(childNode);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
return container;
|
|
2163
|
+
}
|
|
2164
|
+
function makeSummary(el) {
|
|
2165
|
+
const props = el.props;
|
|
2166
|
+
if (!props) return null;
|
|
2167
|
+
const prefer = ["title", "label", "text", "name", "value", "status"];
|
|
2168
|
+
for (const p of prefer) {
|
|
2169
|
+
const v = props[p];
|
|
2170
|
+
if (typeof v === "string" && v.length > 0) return shorten(v, 60);
|
|
2171
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
2172
|
+
}
|
|
2173
|
+
for (const [, v] of Object.entries(props)) {
|
|
2174
|
+
if (typeof v === "string" && v.length > 0) return shorten(v, 60);
|
|
2175
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
2176
|
+
}
|
|
2177
|
+
return null;
|
|
2178
|
+
}
|
|
2179
|
+
function shorten(text, max) {
|
|
2180
|
+
if (text.length <= max) return text;
|
|
2181
|
+
return text.slice(0, max - 1) + "\u2026";
|
|
2182
|
+
}
|
|
2183
|
+
function renderDetail(container, spec, key, issues) {
|
|
2184
|
+
const el = spec.elements[key];
|
|
2185
|
+
if (!el) {
|
|
2186
|
+
replaceChildren(
|
|
2187
|
+
container,
|
|
2188
|
+
h("div", { className: "jr-empty" }, `Element '${key}' not found.`)
|
|
2189
|
+
);
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
const elIssues = issues.byKey.get(key) ?? [];
|
|
2193
|
+
const children = el.children?.length ?? 0;
|
|
2194
|
+
replaceChildren(
|
|
2195
|
+
container,
|
|
2196
|
+
h(
|
|
2197
|
+
"div",
|
|
2198
|
+
{ className: "jr-detail" },
|
|
2199
|
+
h("h4", null, "Element"),
|
|
2200
|
+
h(
|
|
2201
|
+
"div",
|
|
2202
|
+
{ style: { marginBottom: "10px" } },
|
|
2203
|
+
h("span", { className: "jr-tree-type" }, el.type),
|
|
2204
|
+
" ",
|
|
2205
|
+
h("span", { className: "jr-tree-key" }, `#${key}`),
|
|
2206
|
+
" ",
|
|
2207
|
+
h(
|
|
2208
|
+
"span",
|
|
2209
|
+
{ className: "jr-tree-summary" },
|
|
2210
|
+
`${children} child${children === 1 ? "" : "ren"}`
|
|
2211
|
+
)
|
|
2212
|
+
),
|
|
2213
|
+
elIssues.length > 0 ? [
|
|
2214
|
+
h("h4", null, "Issues"),
|
|
2215
|
+
...elIssues.map(
|
|
2216
|
+
(issue) => h(
|
|
2217
|
+
"div",
|
|
2218
|
+
{
|
|
2219
|
+
className: "jr-badge",
|
|
2220
|
+
"data-tone": issue.severity === "error" ? "red" : "amber",
|
|
2221
|
+
style: {
|
|
2222
|
+
marginBottom: "6px",
|
|
2223
|
+
display: "block",
|
|
2224
|
+
padding: "4px 8px"
|
|
2225
|
+
}
|
|
2226
|
+
},
|
|
2227
|
+
`${issue.severity.toUpperCase()}: ${issue.message}`
|
|
2228
|
+
)
|
|
2229
|
+
)
|
|
2230
|
+
] : null,
|
|
2231
|
+
el.visible !== void 0 ? [
|
|
2232
|
+
h("h4", null, "Visibility"),
|
|
2233
|
+
h("pre", null, jsonPreview(el.visible, 2e3))
|
|
2234
|
+
] : null,
|
|
2235
|
+
el.on && Object.keys(el.on).length > 0 ? [h("h4", null, "Events"), h("pre", null, jsonPreview(el.on, 2e3))] : null,
|
|
2236
|
+
el.watch && Object.keys(el.watch).length > 0 ? [
|
|
2237
|
+
h("h4", null, "Watchers"),
|
|
2238
|
+
h("pre", null, jsonPreview(el.watch, 2e3))
|
|
2239
|
+
] : null,
|
|
2240
|
+
el.repeat ? [
|
|
2241
|
+
h("h4", null, "Repeat"),
|
|
2242
|
+
h("pre", null, jsonPreview(el.repeat, 2e3))
|
|
2243
|
+
] : null,
|
|
2244
|
+
h("h4", null, "Props"),
|
|
2245
|
+
h("pre", null, JSON.stringify(el.props, null, 2))
|
|
2246
|
+
)
|
|
2247
|
+
);
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
// src/panel/tabs/state.ts
|
|
2251
|
+
var import_store_utils = require("@json-render/core/store-utils");
|
|
2252
|
+
var TAB_ID2 = "state";
|
|
2253
|
+
function stateTab() {
|
|
2254
|
+
return {
|
|
2255
|
+
id: TAB_ID2,
|
|
2256
|
+
label: "State",
|
|
2257
|
+
badge(ctx) {
|
|
2258
|
+
const store = ctx.getStateStore();
|
|
2259
|
+
if (!store) return void 0;
|
|
2260
|
+
try {
|
|
2261
|
+
const flat = (0, import_store_utils.flattenToPointers)(store.getSnapshot());
|
|
2262
|
+
const count = Object.keys(flat).length;
|
|
2263
|
+
return count || void 0;
|
|
2264
|
+
} catch {
|
|
2265
|
+
return void 0;
|
|
2266
|
+
}
|
|
2267
|
+
},
|
|
2268
|
+
mount(root, ctx) {
|
|
2269
|
+
return mountStateTab(root, ctx);
|
|
2270
|
+
}
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2273
|
+
function mountStateTab(root, ctx) {
|
|
2274
|
+
const container = h("div", {
|
|
2275
|
+
style: { height: "100%", display: "flex", flexDirection: "column" }
|
|
2276
|
+
});
|
|
2277
|
+
const toolbar = h("div", { className: "jr-toolbar" });
|
|
2278
|
+
const body = h("div", {
|
|
2279
|
+
style: { overflow: "auto", flex: "1", minHeight: "0" }
|
|
2280
|
+
});
|
|
2281
|
+
container.appendChild(toolbar);
|
|
2282
|
+
container.appendChild(body);
|
|
2283
|
+
root.appendChild(container);
|
|
2284
|
+
let filter = "";
|
|
2285
|
+
const editing = /* @__PURE__ */ new Set();
|
|
2286
|
+
const searchInput = h("input", {
|
|
2287
|
+
className: "jr-search",
|
|
2288
|
+
type: "text",
|
|
2289
|
+
placeholder: "Filter paths\u2026",
|
|
2290
|
+
onInput: (ev) => {
|
|
2291
|
+
filter = (ev.target.value || "").toLowerCase();
|
|
2292
|
+
renderBody();
|
|
2293
|
+
}
|
|
2294
|
+
});
|
|
2295
|
+
function renderToolbar() {
|
|
2296
|
+
replaceChildren(
|
|
2297
|
+
toolbar,
|
|
2298
|
+
searchInput,
|
|
2299
|
+
h(
|
|
2300
|
+
"button",
|
|
2301
|
+
{
|
|
2302
|
+
className: "jr-icon-btn",
|
|
2303
|
+
title: "Refresh state listing",
|
|
2304
|
+
onClick: () => renderBody()
|
|
2305
|
+
},
|
|
2306
|
+
"refresh"
|
|
2307
|
+
)
|
|
2308
|
+
);
|
|
2309
|
+
}
|
|
2310
|
+
function renderBody() {
|
|
2311
|
+
const store = ctx.getStateStore();
|
|
2312
|
+
if (!store) {
|
|
2313
|
+
replaceChildren(
|
|
2314
|
+
body,
|
|
2315
|
+
h(
|
|
2316
|
+
"div",
|
|
2317
|
+
{ className: "jr-empty" },
|
|
2318
|
+
"No StateStore attached. Devtools sees state once a provider mounts."
|
|
2319
|
+
)
|
|
2320
|
+
);
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
let flat = {};
|
|
2324
|
+
try {
|
|
2325
|
+
flat = (0, import_store_utils.flattenToPointers)(store.getSnapshot());
|
|
2326
|
+
} catch (err) {
|
|
2327
|
+
replaceChildren(
|
|
2328
|
+
body,
|
|
2329
|
+
h(
|
|
2330
|
+
"div",
|
|
2331
|
+
{ className: "jr-empty" },
|
|
2332
|
+
`State read failed: ${String(err)}`
|
|
2333
|
+
)
|
|
2334
|
+
);
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2337
|
+
const entries = Object.entries(flat).filter(([path]) => !filter || path.toLowerCase().includes(filter)).sort(([a], [b]) => a.localeCompare(b));
|
|
2338
|
+
if (entries.length === 0) {
|
|
2339
|
+
replaceChildren(
|
|
2340
|
+
body,
|
|
2341
|
+
h(
|
|
2342
|
+
"div",
|
|
2343
|
+
{ className: "jr-empty" },
|
|
2344
|
+
filter ? "No paths match the filter." : "State is empty."
|
|
2345
|
+
)
|
|
2346
|
+
);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
replaceChildren(
|
|
2350
|
+
body,
|
|
2351
|
+
entries.map(
|
|
2352
|
+
([path, value]) => renderStateRow(path, value, store.set, editing)
|
|
2353
|
+
)
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
renderToolbar();
|
|
2357
|
+
renderBody();
|
|
2358
|
+
return {
|
|
2359
|
+
update() {
|
|
2360
|
+
if (editing.size === 0) renderBody();
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
function renderStateRow(path, value, setValue, editing) {
|
|
2365
|
+
const input = h("input", {
|
|
2366
|
+
className: "jr-state-value",
|
|
2367
|
+
type: "text",
|
|
2368
|
+
value: stringifyValue(value),
|
|
2369
|
+
spellcheck: "false",
|
|
2370
|
+
onFocus: () => {
|
|
2371
|
+
editing.add(path);
|
|
2372
|
+
},
|
|
2373
|
+
onBlur: (ev) => {
|
|
2374
|
+
editing.delete(path);
|
|
2375
|
+
const raw = ev.target.value;
|
|
2376
|
+
try {
|
|
2377
|
+
const parsed = raw === "" ? "" : JSON.parse(raw);
|
|
2378
|
+
setValue(path, parsed);
|
|
2379
|
+
input.dataset.dirty = "false";
|
|
2380
|
+
} catch {
|
|
2381
|
+
setValue(path, raw);
|
|
2382
|
+
input.dataset.dirty = "false";
|
|
2383
|
+
}
|
|
2384
|
+
},
|
|
2385
|
+
onInput: () => {
|
|
2386
|
+
input.dataset.dirty = "true";
|
|
2387
|
+
},
|
|
2388
|
+
onKeyDown: (ev) => {
|
|
2389
|
+
if (ev.key === "Enter") {
|
|
2390
|
+
ev.preventDefault();
|
|
2391
|
+
ev.target.blur();
|
|
2392
|
+
}
|
|
2393
|
+
if (ev.key === "Escape") {
|
|
2394
|
+
input.value = stringifyValue(value);
|
|
2395
|
+
input.dataset.dirty = "false";
|
|
2396
|
+
ev.target.blur();
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
});
|
|
2400
|
+
return h(
|
|
2401
|
+
"div",
|
|
2402
|
+
{
|
|
2403
|
+
className: "jr-state-row",
|
|
2404
|
+
title: `${path}
|
|
2405
|
+
${jsonPreview(value, 1e3)}`
|
|
2406
|
+
},
|
|
2407
|
+
h("span", { className: "jr-state-path" }, path),
|
|
2408
|
+
input
|
|
2409
|
+
);
|
|
2410
|
+
}
|
|
2411
|
+
function stringifyValue(value) {
|
|
2412
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
2413
|
+
if (value === null) return "null";
|
|
2414
|
+
if (value === void 0) return "";
|
|
2415
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
2416
|
+
return String(value);
|
|
2417
|
+
try {
|
|
2418
|
+
return JSON.stringify(value);
|
|
2419
|
+
} catch {
|
|
2420
|
+
return String(value);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
// src/panel/tabs/actions.ts
|
|
2425
|
+
var TAB_ID3 = "actions";
|
|
2426
|
+
function actionsTab() {
|
|
2427
|
+
return {
|
|
2428
|
+
id: TAB_ID3,
|
|
2429
|
+
label: "Actions",
|
|
2430
|
+
badge(ctx) {
|
|
2431
|
+
const rows = foldActions(ctx.events.snapshot()).length;
|
|
2432
|
+
return rows || void 0;
|
|
2433
|
+
},
|
|
2434
|
+
mount(root, ctx) {
|
|
2435
|
+
return mountActionsTab(root, ctx);
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
function mountActionsTab(root, ctx) {
|
|
2440
|
+
const container = h("div", {
|
|
2441
|
+
style: { height: "100%", display: "flex", flexDirection: "column" }
|
|
2442
|
+
});
|
|
2443
|
+
const toolbar = h("div", { className: "jr-toolbar" });
|
|
2444
|
+
const body = h("div", {
|
|
2445
|
+
style: { overflow: "auto", flex: "1", minHeight: "0" }
|
|
2446
|
+
});
|
|
2447
|
+
container.appendChild(toolbar);
|
|
2448
|
+
container.appendChild(body);
|
|
2449
|
+
root.appendChild(container);
|
|
2450
|
+
let filter = "";
|
|
2451
|
+
const expanded = /* @__PURE__ */ new Set();
|
|
2452
|
+
const searchInput = h("input", {
|
|
2453
|
+
className: "jr-search",
|
|
2454
|
+
type: "text",
|
|
2455
|
+
placeholder: "Filter by action name\u2026",
|
|
2456
|
+
onInput: (ev) => {
|
|
2457
|
+
filter = (ev.target.value || "").toLowerCase();
|
|
2458
|
+
renderBody();
|
|
2459
|
+
}
|
|
2460
|
+
});
|
|
2461
|
+
const countBadge = h("span", { className: "jr-badge" }, "0 actions");
|
|
2462
|
+
const clearBtn = h(
|
|
2463
|
+
"button",
|
|
2464
|
+
{
|
|
2465
|
+
className: "jr-icon-btn",
|
|
2466
|
+
title: "Clear action log",
|
|
2467
|
+
onClick: () => {
|
|
2468
|
+
const keep = ctx.events.snapshot().filter(
|
|
2469
|
+
(e) => e.kind !== "action-dispatched" && e.kind !== "action-settled"
|
|
2470
|
+
);
|
|
2471
|
+
ctx.events.clear();
|
|
2472
|
+
for (const e of keep) ctx.events.push(e);
|
|
2473
|
+
}
|
|
2474
|
+
},
|
|
2475
|
+
"clear"
|
|
2476
|
+
);
|
|
2477
|
+
toolbar.append(searchInput, countBadge, clearBtn);
|
|
2478
|
+
function renderBody() {
|
|
2479
|
+
const rows = foldActions(ctx.events.snapshot()).filter((r) => !filter || r.name.toLowerCase().includes(filter)).reverse();
|
|
2480
|
+
countBadge.textContent = `${rows.length} action${rows.length === 1 ? "" : "s"}`;
|
|
2481
|
+
if (rows.length === 0) {
|
|
2482
|
+
replaceChildren(
|
|
2483
|
+
body,
|
|
2484
|
+
h(
|
|
2485
|
+
"div",
|
|
2486
|
+
{ className: "jr-empty" },
|
|
2487
|
+
filter ? "No actions match the filter." : "No actions dispatched yet."
|
|
2488
|
+
)
|
|
2489
|
+
);
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
replaceChildren(
|
|
2493
|
+
body,
|
|
2494
|
+
rows.map((r) => renderRow(r, expanded, renderBody))
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
renderBody();
|
|
2498
|
+
return { update: renderBody };
|
|
2499
|
+
}
|
|
2500
|
+
function foldActions(events) {
|
|
2501
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2502
|
+
const order = [];
|
|
2503
|
+
for (const evt of events) {
|
|
2504
|
+
if (evt.kind === "action-dispatched") {
|
|
2505
|
+
if (byId.has(evt.id)) continue;
|
|
2506
|
+
byId.set(evt.id, {
|
|
2507
|
+
id: evt.id,
|
|
2508
|
+
name: evt.name,
|
|
2509
|
+
params: evt.params,
|
|
2510
|
+
dispatchedAt: evt.at
|
|
2511
|
+
});
|
|
2512
|
+
order.push(evt.id);
|
|
2513
|
+
} else if (evt.kind === "action-settled") {
|
|
2514
|
+
const row = byId.get(evt.id);
|
|
2515
|
+
if (!row) continue;
|
|
2516
|
+
row.settledAt = evt.at;
|
|
2517
|
+
row.durationMs = evt.durationMs;
|
|
2518
|
+
row.ok = evt.ok;
|
|
2519
|
+
row.result = evt.result;
|
|
2520
|
+
row.error = evt.error;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
return order.map((id) => byId.get(id)).filter(Boolean);
|
|
2524
|
+
}
|
|
2525
|
+
function renderRow(row, expanded, refresh) {
|
|
2526
|
+
const isExpanded = expanded.has(row.id);
|
|
2527
|
+
const pending = row.settledAt === void 0;
|
|
2528
|
+
const tone = pending ? "amber" : row.ok ? "green" : "red";
|
|
2529
|
+
const status = pending ? "pending" : row.ok ? "ok" : "error";
|
|
2530
|
+
const duration = row.durationMs !== void 0 ? `${row.durationMs.toFixed(0)}ms` : "";
|
|
2531
|
+
const rowEl = h(
|
|
2532
|
+
"div",
|
|
2533
|
+
{
|
|
2534
|
+
className: "jr-row",
|
|
2535
|
+
onClick: () => {
|
|
2536
|
+
if (isExpanded) expanded.delete(row.id);
|
|
2537
|
+
else expanded.add(row.id);
|
|
2538
|
+
refresh();
|
|
2539
|
+
}
|
|
2540
|
+
},
|
|
2541
|
+
h("span", { className: "jr-row-time" }, formatTime(row.dispatchedAt)),
|
|
2542
|
+
h(
|
|
2543
|
+
"span",
|
|
2544
|
+
{ className: "jr-row-main" },
|
|
2545
|
+
h("strong", null, row.name),
|
|
2546
|
+
row.params !== void 0 ? h(
|
|
2547
|
+
"span",
|
|
2548
|
+
{ className: "jr-tree-summary", style: { marginLeft: "8px" } },
|
|
2549
|
+
jsonPreview(row.params, 80)
|
|
2550
|
+
) : null
|
|
2551
|
+
),
|
|
2552
|
+
h(
|
|
2553
|
+
"span",
|
|
2554
|
+
{ className: "jr-row-meta" },
|
|
2555
|
+
h("span", { className: "jr-badge", "data-tone": tone }, status),
|
|
2556
|
+
duration ? h("span", { style: { marginLeft: "6px" } }, duration) : null
|
|
2557
|
+
)
|
|
2558
|
+
);
|
|
2559
|
+
if (!isExpanded) return rowEl;
|
|
2560
|
+
const container = h("div", null, rowEl);
|
|
2561
|
+
container.appendChild(
|
|
2562
|
+
h(
|
|
2563
|
+
"div",
|
|
2564
|
+
{ className: "jr-row-expanded" },
|
|
2565
|
+
h("h4", null, "Params"),
|
|
2566
|
+
h("pre", null, JSON.stringify(row.params ?? null, null, 2)),
|
|
2567
|
+
row.ok === false ? [
|
|
2568
|
+
h("h4", null, "Error"),
|
|
2569
|
+
h("pre", null, row.error ?? "(unknown error)")
|
|
2570
|
+
] : null,
|
|
2571
|
+
row.result !== void 0 && row.ok !== false ? [
|
|
2572
|
+
h("h4", null, "Result"),
|
|
2573
|
+
h("pre", null, JSON.stringify(row.result, null, 2))
|
|
2574
|
+
] : null
|
|
2575
|
+
)
|
|
2576
|
+
);
|
|
2577
|
+
return container;
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// src/panel/tabs/stream.ts
|
|
2581
|
+
var TAB_ID4 = "stream";
|
|
2582
|
+
function streamTab() {
|
|
2583
|
+
return {
|
|
2584
|
+
id: TAB_ID4,
|
|
2585
|
+
label: "Stream",
|
|
2586
|
+
badge(ctx) {
|
|
2587
|
+
const gens = groupGenerations(ctx.events.snapshot());
|
|
2588
|
+
return gens.length || void 0;
|
|
2589
|
+
},
|
|
2590
|
+
mount(root, ctx) {
|
|
2591
|
+
return mountStreamTab(root, ctx);
|
|
2592
|
+
}
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
function mountStreamTab(root, ctx) {
|
|
2596
|
+
const container = h("div", {
|
|
2597
|
+
style: { height: "100%", display: "flex", flexDirection: "column" }
|
|
2598
|
+
});
|
|
2599
|
+
const toolbar = h("div", { className: "jr-toolbar" });
|
|
2600
|
+
const body = h("div", {
|
|
2601
|
+
style: { overflow: "auto", flex: "1", minHeight: "0" }
|
|
2602
|
+
});
|
|
2603
|
+
container.appendChild(toolbar);
|
|
2604
|
+
container.appendChild(body);
|
|
2605
|
+
root.appendChild(container);
|
|
2606
|
+
function render() {
|
|
2607
|
+
const all = ctx.events.snapshot();
|
|
2608
|
+
const gens = groupGenerations(all);
|
|
2609
|
+
const orphan = collectOrphanEvents(all);
|
|
2610
|
+
replaceChildren(
|
|
2611
|
+
toolbar,
|
|
2612
|
+
h(
|
|
2613
|
+
"span",
|
|
2614
|
+
{ className: "jr-badge" },
|
|
2615
|
+
`${gens.length} generation${gens.length === 1 ? "" : "s"}`
|
|
2616
|
+
)
|
|
2617
|
+
);
|
|
2618
|
+
if (gens.length === 0 && orphan.length === 0) {
|
|
2619
|
+
replaceChildren(
|
|
2620
|
+
body,
|
|
2621
|
+
h(
|
|
2622
|
+
"div",
|
|
2623
|
+
{ className: "jr-empty" },
|
|
2624
|
+
"No stream events yet. Tap a stream or pass messages to capture patches."
|
|
2625
|
+
)
|
|
2626
|
+
);
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
const content = [];
|
|
2630
|
+
if (orphan.length > 0) {
|
|
2631
|
+
content.push(
|
|
2632
|
+
renderGeneration(
|
|
2633
|
+
{ startedAt: orphan[0]?.at ?? Date.now(), events: orphan },
|
|
2634
|
+
"live",
|
|
2635
|
+
true
|
|
2636
|
+
)
|
|
2637
|
+
);
|
|
2638
|
+
}
|
|
2639
|
+
for (let i = gens.length - 1; i >= 0; i--) {
|
|
2640
|
+
const gen = gens[i];
|
|
2641
|
+
content.push(renderGeneration(gen, `gen-${i}`, i === gens.length - 1));
|
|
2642
|
+
}
|
|
2643
|
+
replaceChildren(body, content);
|
|
2644
|
+
}
|
|
2645
|
+
render();
|
|
2646
|
+
return { update: render };
|
|
2647
|
+
}
|
|
2648
|
+
function groupGenerations(events) {
|
|
2649
|
+
const gens = [];
|
|
2650
|
+
let current = null;
|
|
2651
|
+
for (const evt of events) {
|
|
2652
|
+
if (evt.kind === "stream-lifecycle") {
|
|
2653
|
+
if (evt.phase === "start") {
|
|
2654
|
+
if (current) gens.push(current);
|
|
2655
|
+
current = { startedAt: evt.at, events: [] };
|
|
2656
|
+
} else {
|
|
2657
|
+
if (!current) {
|
|
2658
|
+
current = { startedAt: evt.at, events: [] };
|
|
2659
|
+
}
|
|
2660
|
+
current.endedAt = evt.at;
|
|
2661
|
+
current.ok = evt.ok;
|
|
2662
|
+
gens.push(current);
|
|
2663
|
+
current = null;
|
|
2664
|
+
}
|
|
2665
|
+
continue;
|
|
2666
|
+
}
|
|
2667
|
+
if (evt.kind === "stream-patch" || evt.kind === "stream-text" || evt.kind === "stream-usage") {
|
|
2668
|
+
if (!current) current = { startedAt: evt.at, events: [] };
|
|
2669
|
+
current.events.push(evt);
|
|
2670
|
+
if (evt.kind === "stream-usage") current.usage = evt.usage;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
if (current) gens.push(current);
|
|
2674
|
+
return gens;
|
|
2675
|
+
}
|
|
2676
|
+
function collectOrphanEvents(events) {
|
|
2677
|
+
const hasAnyLifecycle = events.some((e) => e.kind === "stream-lifecycle");
|
|
2678
|
+
if (hasAnyLifecycle) return [];
|
|
2679
|
+
return events.filter(
|
|
2680
|
+
(e) => e.kind === "stream-patch" || e.kind === "stream-text" || e.kind === "stream-usage"
|
|
2681
|
+
);
|
|
2682
|
+
}
|
|
2683
|
+
function renderGeneration(gen, key, expandedByDefault) {
|
|
2684
|
+
const patchCount = gen.events.filter((e) => e.kind === "stream-patch").length;
|
|
2685
|
+
const textCount = gen.events.filter((e) => e.kind === "stream-text").length;
|
|
2686
|
+
const duration = gen.endedAt !== void 0 ? gen.endedAt - gen.startedAt : void 0;
|
|
2687
|
+
const tone = gen.endedAt === void 0 ? "amber" : gen.ok === false ? "red" : "green";
|
|
2688
|
+
const status = gen.endedAt === void 0 ? "live" : gen.ok === false ? "error" : "done";
|
|
2689
|
+
const body = h(
|
|
2690
|
+
"div",
|
|
2691
|
+
{
|
|
2692
|
+
style: {
|
|
2693
|
+
borderTop: "1px solid var(--jr-border)",
|
|
2694
|
+
background: "var(--jr-bg-soft)",
|
|
2695
|
+
display: expandedByDefault ? "block" : "none"
|
|
2696
|
+
}
|
|
2697
|
+
},
|
|
2698
|
+
...gen.events.map(renderStreamEvent)
|
|
2699
|
+
);
|
|
2700
|
+
const header = h(
|
|
2701
|
+
"div",
|
|
2702
|
+
{
|
|
2703
|
+
className: "jr-row",
|
|
2704
|
+
style: { cursor: "pointer" },
|
|
2705
|
+
onClick: () => {
|
|
2706
|
+
body.style.display = body.style.display === "none" ? "block" : "none";
|
|
2707
|
+
}
|
|
2708
|
+
},
|
|
2709
|
+
h("span", { className: "jr-row-time" }, formatTime(gen.startedAt)),
|
|
2710
|
+
h(
|
|
2711
|
+
"span",
|
|
2712
|
+
{ className: "jr-row-main" },
|
|
2713
|
+
h("strong", null, `generation #${key}`),
|
|
2714
|
+
h(
|
|
2715
|
+
"span",
|
|
2716
|
+
{ className: "jr-tree-summary", style: { marginLeft: "8px" } },
|
|
2717
|
+
`${patchCount} patch${patchCount === 1 ? "" : "es"}`,
|
|
2718
|
+
textCount > 0 ? `, ${textCount} text chunk${textCount === 1 ? "" : "s"}` : ""
|
|
2719
|
+
)
|
|
2720
|
+
),
|
|
2721
|
+
h(
|
|
2722
|
+
"span",
|
|
2723
|
+
{ className: "jr-row-meta" },
|
|
2724
|
+
h("span", { className: "jr-badge", "data-tone": tone }, status),
|
|
2725
|
+
duration !== void 0 ? h("span", { style: { marginLeft: "6px" } }, `${duration}ms`) : null,
|
|
2726
|
+
gen.usage ? h(
|
|
2727
|
+
"span",
|
|
2728
|
+
{ style: { marginLeft: "6px" } },
|
|
2729
|
+
`${formatTokens(gen.usage.totalTokens)} tok`
|
|
2730
|
+
) : null
|
|
2731
|
+
)
|
|
2732
|
+
);
|
|
2733
|
+
return h("div", null, header, body);
|
|
2734
|
+
}
|
|
2735
|
+
function renderStreamEvent(evt) {
|
|
2736
|
+
if (evt.kind === "stream-patch") {
|
|
2737
|
+
return h(
|
|
2738
|
+
"div",
|
|
2739
|
+
{ className: "jr-row" },
|
|
2740
|
+
h("span", { className: "jr-row-time" }, formatTime(evt.at)),
|
|
2741
|
+
h(
|
|
2742
|
+
"span",
|
|
2743
|
+
{ className: "jr-row-main" },
|
|
2744
|
+
h(
|
|
2745
|
+
"span",
|
|
2746
|
+
{ className: "jr-badge", "data-tone": "accent" },
|
|
2747
|
+
evt.patch.op
|
|
2748
|
+
),
|
|
2749
|
+
h(
|
|
2750
|
+
"span",
|
|
2751
|
+
{ className: "jr-tree-summary", style: { marginLeft: "8px" } },
|
|
2752
|
+
evt.patch.path,
|
|
2753
|
+
evt.patch.value !== void 0 ? ` = ${jsonPreview(evt.patch.value, 80)}` : ""
|
|
2754
|
+
)
|
|
2755
|
+
),
|
|
2756
|
+
h("span", { className: "jr-row-meta" }, evt.source)
|
|
2757
|
+
);
|
|
2758
|
+
}
|
|
2759
|
+
if (evt.kind === "stream-text") {
|
|
2760
|
+
return h(
|
|
2761
|
+
"div",
|
|
2762
|
+
{ className: "jr-row" },
|
|
2763
|
+
h("span", { className: "jr-row-time" }, formatTime(evt.at)),
|
|
2764
|
+
h(
|
|
2765
|
+
"span",
|
|
2766
|
+
{ className: "jr-row-main", style: { whiteSpace: "pre-wrap" } },
|
|
2767
|
+
evt.text
|
|
2768
|
+
),
|
|
2769
|
+
h(
|
|
2770
|
+
"span",
|
|
2771
|
+
{ className: "jr-row-meta" },
|
|
2772
|
+
h("span", { className: "jr-badge" }, "text")
|
|
2773
|
+
)
|
|
2774
|
+
);
|
|
2775
|
+
}
|
|
2776
|
+
if (evt.kind === "stream-usage") {
|
|
2777
|
+
return h(
|
|
2778
|
+
"div",
|
|
2779
|
+
{ className: "jr-row" },
|
|
2780
|
+
h("span", { className: "jr-row-time" }, formatTime(evt.at)),
|
|
2781
|
+
h(
|
|
2782
|
+
"span",
|
|
2783
|
+
{ className: "jr-row-main" },
|
|
2784
|
+
`prompt ${formatTokens(evt.usage.promptTokens)} / completion ${formatTokens(
|
|
2785
|
+
evt.usage.completionTokens
|
|
2786
|
+
)} / total ${formatTokens(evt.usage.totalTokens)}`
|
|
2787
|
+
),
|
|
2788
|
+
h(
|
|
2789
|
+
"span",
|
|
2790
|
+
{ className: "jr-row-meta" },
|
|
2791
|
+
h("span", { className: "jr-badge", "data-tone": "accent" }, "usage")
|
|
2792
|
+
)
|
|
2793
|
+
);
|
|
2794
|
+
}
|
|
2795
|
+
return h("div");
|
|
2796
|
+
}
|
|
2797
|
+
function formatTokens(n) {
|
|
2798
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1).replace(/\.0$/, "")}k`;
|
|
2799
|
+
return String(n);
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
// src/panel/tabs/catalog.ts
|
|
2803
|
+
var TAB_ID5 = "catalog";
|
|
2804
|
+
function catalogTab() {
|
|
2805
|
+
return {
|
|
2806
|
+
id: TAB_ID5,
|
|
2807
|
+
label: "Catalog",
|
|
2808
|
+
badge(ctx) {
|
|
2809
|
+
const cat = ctx.getCatalog();
|
|
2810
|
+
if (!cat) return void 0;
|
|
2811
|
+
const n = cat.componentNames.length + cat.actionNames.length;
|
|
2812
|
+
return n || void 0;
|
|
2813
|
+
},
|
|
2814
|
+
mount(root, ctx) {
|
|
2815
|
+
return mountCatalogTab(root, ctx);
|
|
2816
|
+
}
|
|
2817
|
+
};
|
|
2818
|
+
}
|
|
2819
|
+
function mountCatalogTab(root, ctx) {
|
|
2820
|
+
const container = h("div", {
|
|
2821
|
+
style: { height: "100%", display: "flex", flexDirection: "column" }
|
|
2822
|
+
});
|
|
2823
|
+
const toolbar = h("div", { className: "jr-toolbar" });
|
|
2824
|
+
const body = h("div", {
|
|
2825
|
+
style: { overflow: "auto", flex: "1", minHeight: "0" }
|
|
2826
|
+
});
|
|
2827
|
+
container.appendChild(toolbar);
|
|
2828
|
+
container.appendChild(body);
|
|
2829
|
+
root.appendChild(container);
|
|
2830
|
+
let view = "components";
|
|
2831
|
+
let filter = "";
|
|
2832
|
+
const componentsBtn = h(
|
|
2833
|
+
"button",
|
|
2834
|
+
{
|
|
2835
|
+
className: "jr-icon-btn",
|
|
2836
|
+
"data-active": "true",
|
|
2837
|
+
onClick: () => {
|
|
2838
|
+
if (view === "components") return;
|
|
2839
|
+
view = "components";
|
|
2840
|
+
updateToolbarChrome();
|
|
2841
|
+
renderBody();
|
|
2842
|
+
}
|
|
2843
|
+
},
|
|
2844
|
+
"components"
|
|
2845
|
+
);
|
|
2846
|
+
const actionsBtn = h(
|
|
2847
|
+
"button",
|
|
2848
|
+
{
|
|
2849
|
+
className: "jr-icon-btn",
|
|
2850
|
+
"data-active": "false",
|
|
2851
|
+
onClick: () => {
|
|
2852
|
+
if (view === "actions") return;
|
|
2853
|
+
view = "actions";
|
|
2854
|
+
updateToolbarChrome();
|
|
2855
|
+
renderBody();
|
|
2856
|
+
}
|
|
2857
|
+
},
|
|
2858
|
+
"actions"
|
|
2859
|
+
);
|
|
2860
|
+
const searchInput = h("input", {
|
|
2861
|
+
className: "jr-search",
|
|
2862
|
+
type: "text",
|
|
2863
|
+
placeholder: "Filter by name\u2026",
|
|
2864
|
+
onInput: (ev) => {
|
|
2865
|
+
filter = (ev.target.value || "").toLowerCase();
|
|
2866
|
+
renderBody();
|
|
2867
|
+
}
|
|
2868
|
+
});
|
|
2869
|
+
toolbar.append(componentsBtn, actionsBtn, searchInput);
|
|
2870
|
+
function updateToolbarChrome() {
|
|
2871
|
+
const catalog = ctx.getCatalog();
|
|
2872
|
+
const display = catalog ? buildCatalogDisplayData(catalog.data) : null;
|
|
2873
|
+
const cCount = display?.components.length ?? 0;
|
|
2874
|
+
const aCount = display?.actions.length ?? 0;
|
|
2875
|
+
componentsBtn.textContent = `components (${cCount})`;
|
|
2876
|
+
actionsBtn.textContent = `actions (${aCount})`;
|
|
2877
|
+
componentsBtn.dataset.active = String(view === "components");
|
|
2878
|
+
actionsBtn.dataset.active = String(view === "actions");
|
|
2879
|
+
}
|
|
2880
|
+
function renderBody() {
|
|
2881
|
+
const catalog = ctx.getCatalog();
|
|
2882
|
+
if (!catalog) {
|
|
2883
|
+
replaceChildren(
|
|
2884
|
+
body,
|
|
2885
|
+
h(
|
|
2886
|
+
"div",
|
|
2887
|
+
{ className: "jr-empty" },
|
|
2888
|
+
"No catalog attached. Pass `catalog` to `<JsonRenderDevtools>` to browse it here."
|
|
2889
|
+
)
|
|
2890
|
+
);
|
|
2891
|
+
return;
|
|
2892
|
+
}
|
|
2893
|
+
const display = buildCatalogDisplayData(catalog.data);
|
|
2894
|
+
if (view === "components") {
|
|
2895
|
+
const items = display.components.filter(
|
|
2896
|
+
(c) => !filter || c.name.toLowerCase().includes(filter)
|
|
2897
|
+
);
|
|
2898
|
+
if (items.length === 0) {
|
|
2899
|
+
replaceChildren(
|
|
2900
|
+
body,
|
|
2901
|
+
h("div", { className: "jr-empty" }, "No components match.")
|
|
2902
|
+
);
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
replaceChildren(body, items.map(renderComponent));
|
|
2906
|
+
} else {
|
|
2907
|
+
const items = display.actions.filter(
|
|
2908
|
+
(a) => !filter || a.name.toLowerCase().includes(filter)
|
|
2909
|
+
);
|
|
2910
|
+
if (items.length === 0) {
|
|
2911
|
+
replaceChildren(
|
|
2912
|
+
body,
|
|
2913
|
+
h("div", { className: "jr-empty" }, "No actions match.")
|
|
2914
|
+
);
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2917
|
+
replaceChildren(body, items.map(renderAction));
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
function render() {
|
|
2921
|
+
updateToolbarChrome();
|
|
2922
|
+
renderBody();
|
|
2923
|
+
}
|
|
2924
|
+
render();
|
|
2925
|
+
return { update: render };
|
|
2926
|
+
}
|
|
2927
|
+
function renderComponent(c) {
|
|
2928
|
+
return h(
|
|
2929
|
+
"div",
|
|
2930
|
+
{ className: "jr-catalog-item" },
|
|
2931
|
+
h("div", { className: "jr-catalog-name" }, c.name),
|
|
2932
|
+
c.description ? h("div", { className: "jr-catalog-desc" }, c.description) : null,
|
|
2933
|
+
c.props.length > 0 || c.events.length > 0 ? h(
|
|
2934
|
+
"div",
|
|
2935
|
+
{ className: "jr-chips" },
|
|
2936
|
+
...c.props.map(
|
|
2937
|
+
(p) => h(
|
|
2938
|
+
"span",
|
|
2939
|
+
{ className: "jr-chip", "data-tone": "prop" },
|
|
2940
|
+
`${p.name}: ${p.type}`
|
|
2941
|
+
)
|
|
2942
|
+
),
|
|
2943
|
+
...c.events.map(
|
|
2944
|
+
(e) => h(
|
|
2945
|
+
"span",
|
|
2946
|
+
{ className: "jr-chip", "data-tone": "event" },
|
|
2947
|
+
`on.${e}`
|
|
2948
|
+
)
|
|
2949
|
+
)
|
|
2950
|
+
) : null
|
|
2951
|
+
);
|
|
2952
|
+
}
|
|
2953
|
+
function renderAction(a) {
|
|
2954
|
+
return h(
|
|
2955
|
+
"div",
|
|
2956
|
+
{ className: "jr-catalog-item" },
|
|
2957
|
+
h("div", { className: "jr-catalog-name" }, a.name),
|
|
2958
|
+
a.description ? h("div", { className: "jr-catalog-desc" }, a.description) : null,
|
|
2959
|
+
a.params.length > 0 ? h(
|
|
2960
|
+
"div",
|
|
2961
|
+
{ className: "jr-chips" },
|
|
2962
|
+
...a.params.map(
|
|
2963
|
+
(p) => h(
|
|
2964
|
+
"span",
|
|
2965
|
+
{ className: "jr-chip", "data-tone": "param" },
|
|
2966
|
+
`${p.name}: ${p.type}`
|
|
2967
|
+
)
|
|
2968
|
+
)
|
|
2969
|
+
) : null
|
|
2970
|
+
);
|
|
2971
|
+
}
|
|
2972
|
+
function buildCatalogDisplayData(rawCatalogData) {
|
|
2973
|
+
const raw = rawCatalogData ?? {};
|
|
2974
|
+
const componentsRaw = raw.components ?? {};
|
|
2975
|
+
const actionsRaw = raw.actions ?? {};
|
|
2976
|
+
const components = Object.entries(componentsRaw).map(([name, def]) => {
|
|
2977
|
+
const d = def ?? {};
|
|
2978
|
+
return {
|
|
2979
|
+
name,
|
|
2980
|
+
description: d.description ?? "",
|
|
2981
|
+
props: extractFields(d.props),
|
|
2982
|
+
slots: Array.isArray(d.slots) ? d.slots : [],
|
|
2983
|
+
events: Array.isArray(d.events) ? d.events : []
|
|
2984
|
+
};
|
|
2985
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
2986
|
+
const actions = Object.entries(actionsRaw).map(([name, def]) => {
|
|
2987
|
+
const d = def ?? {};
|
|
2988
|
+
return {
|
|
2989
|
+
name,
|
|
2990
|
+
description: d.description ?? "",
|
|
2991
|
+
params: extractFields(d.params)
|
|
2992
|
+
};
|
|
2993
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
2994
|
+
return { components, actions };
|
|
2995
|
+
}
|
|
2996
|
+
function extractFields(zodObj) {
|
|
2997
|
+
if (!zodObj) return [];
|
|
2998
|
+
try {
|
|
2999
|
+
const obj = zodObj;
|
|
3000
|
+
const shape = typeof obj.shape === "object" && obj.shape !== null ? obj.shape : typeof obj._def?.shape === "function" ? obj._def.shape() : typeof obj._def?.shape === "object" ? obj._def.shape : null;
|
|
3001
|
+
if (!shape) return [];
|
|
3002
|
+
return Object.entries(shape).map(([name, schema]) => {
|
|
3003
|
+
let type = "unknown";
|
|
3004
|
+
try {
|
|
3005
|
+
const s = schema;
|
|
3006
|
+
const typeName = s?._zod?.def?.type ?? s?._def?.typeName ?? "";
|
|
3007
|
+
if (typeName.includes("string")) type = "string";
|
|
3008
|
+
else if (typeName.includes("number")) type = "number";
|
|
3009
|
+
else if (typeName.includes("boolean")) type = "boolean";
|
|
3010
|
+
else if (typeName.includes("array")) type = "array";
|
|
3011
|
+
else if (typeName.includes("enum")) {
|
|
3012
|
+
const values = s?._zod?.def?.values ?? s?._def?.values;
|
|
3013
|
+
type = Array.isArray(values) ? values.join(" | ") : "enum";
|
|
3014
|
+
} else if (typeName.includes("union")) type = "union";
|
|
3015
|
+
else if (typeName.includes("nullable")) {
|
|
3016
|
+
const inner = s?._zod?.def?.innerType ?? s?._def?.innerType;
|
|
3017
|
+
const innerName = inner?._zod?.def?.type ?? inner?._def?.typeName ?? "";
|
|
3018
|
+
if (innerName.includes("string")) type = "string?";
|
|
3019
|
+
else if (innerName.includes("number")) type = "number?";
|
|
3020
|
+
else if (innerName.includes("boolean")) type = "boolean?";
|
|
3021
|
+
else if (innerName.includes("array")) type = "array?";
|
|
3022
|
+
else if (innerName.includes("enum")) {
|
|
3023
|
+
const values = inner?._zod?.def?.values ?? inner?._def?.values;
|
|
3024
|
+
type = Array.isArray(values) ? `(${values.join(" | ")})?` : "enum?";
|
|
3025
|
+
} else type = "optional";
|
|
3026
|
+
}
|
|
3027
|
+
} catch {
|
|
3028
|
+
}
|
|
3029
|
+
return { name, type };
|
|
3030
|
+
});
|
|
3031
|
+
} catch {
|
|
3032
|
+
return [];
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
// src/panel/tabs/picker.ts
|
|
3037
|
+
var TAB_ID6 = "pick";
|
|
3038
|
+
function pickerTab() {
|
|
3039
|
+
return {
|
|
3040
|
+
id: TAB_ID6,
|
|
3041
|
+
label: "Pick",
|
|
3042
|
+
mount(root, ctx) {
|
|
3043
|
+
return mountPickerTab(root, ctx);
|
|
3044
|
+
}
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
function mountPickerTab(root, ctx) {
|
|
3048
|
+
let active = false;
|
|
3049
|
+
let stopFn = null;
|
|
3050
|
+
const body = h("div", { style: { padding: "16px 12px" } });
|
|
3051
|
+
root.appendChild(body);
|
|
3052
|
+
const unsubSelection = ctx.selection.subscribe(() => render());
|
|
3053
|
+
function start() {
|
|
3054
|
+
if (active) return;
|
|
3055
|
+
const session = ctx.startPicker?.({
|
|
3056
|
+
onPick: (key) => {
|
|
3057
|
+
active = false;
|
|
3058
|
+
stopFn = null;
|
|
3059
|
+
ctx.selection.set(key);
|
|
3060
|
+
ctx.activateTab("spec");
|
|
3061
|
+
},
|
|
3062
|
+
onCancel: () => {
|
|
3063
|
+
active = false;
|
|
3064
|
+
stopFn = null;
|
|
3065
|
+
render();
|
|
3066
|
+
}
|
|
3067
|
+
});
|
|
3068
|
+
if (!session) {
|
|
3069
|
+
render();
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
stopFn = session.stop;
|
|
3073
|
+
active = true;
|
|
3074
|
+
render();
|
|
3075
|
+
}
|
|
3076
|
+
function stop() {
|
|
3077
|
+
if (stopFn) stopFn();
|
|
3078
|
+
stopFn = null;
|
|
3079
|
+
active = false;
|
|
3080
|
+
render();
|
|
3081
|
+
}
|
|
3082
|
+
function render() {
|
|
3083
|
+
const selected = ctx.selection.get();
|
|
3084
|
+
const canPick = typeof document !== "undefined";
|
|
3085
|
+
const selectedMounted = selected ? !!findElementByKey(selected) : false;
|
|
3086
|
+
replaceChildren(
|
|
3087
|
+
body,
|
|
3088
|
+
h(
|
|
3089
|
+
"div",
|
|
3090
|
+
{ style: { marginBottom: "12px", color: "var(--jr-fg-muted)" } },
|
|
3091
|
+
"Click in the page to pick an element. Hovering paints an outline."
|
|
3092
|
+
),
|
|
3093
|
+
h(
|
|
3094
|
+
"button",
|
|
3095
|
+
{
|
|
3096
|
+
className: "jr-icon-btn",
|
|
3097
|
+
style: {
|
|
3098
|
+
padding: "6px 12px",
|
|
3099
|
+
width: "auto",
|
|
3100
|
+
border: "1px solid var(--jr-border)",
|
|
3101
|
+
borderRadius: "6px"
|
|
3102
|
+
},
|
|
3103
|
+
disabled: !canPick,
|
|
3104
|
+
"data-active": String(active),
|
|
3105
|
+
onClick: () => active ? stop() : start()
|
|
3106
|
+
},
|
|
3107
|
+
active ? "Stop picking (Esc)" : "Start picking"
|
|
3108
|
+
),
|
|
3109
|
+
h(
|
|
3110
|
+
"div",
|
|
3111
|
+
{ style: { marginTop: "18px" } },
|
|
3112
|
+
h(
|
|
3113
|
+
"h4",
|
|
3114
|
+
{
|
|
3115
|
+
style: {
|
|
3116
|
+
margin: "0 0 6px 0",
|
|
3117
|
+
fontSize: "10px",
|
|
3118
|
+
color: "var(--jr-fg-dim)",
|
|
3119
|
+
textTransform: "uppercase",
|
|
3120
|
+
letterSpacing: "0.06em"
|
|
3121
|
+
}
|
|
3122
|
+
},
|
|
3123
|
+
"Current selection"
|
|
3124
|
+
),
|
|
3125
|
+
selected ? h(
|
|
3126
|
+
"div",
|
|
3127
|
+
{
|
|
3128
|
+
className: "jr-badge",
|
|
3129
|
+
style: { padding: "4px 8px", fontSize: "11px" }
|
|
3130
|
+
},
|
|
3131
|
+
`#${selected}`
|
|
3132
|
+
) : h(
|
|
3133
|
+
"div",
|
|
3134
|
+
{ style: { color: "var(--jr-fg-dim)", fontSize: "11px" } },
|
|
3135
|
+
"No selection"
|
|
3136
|
+
),
|
|
3137
|
+
selected && !selectedMounted ? h(
|
|
3138
|
+
"div",
|
|
3139
|
+
{
|
|
3140
|
+
style: {
|
|
3141
|
+
marginTop: "6px",
|
|
3142
|
+
color: "var(--jr-amber)",
|
|
3143
|
+
fontSize: "10px"
|
|
3144
|
+
}
|
|
3145
|
+
},
|
|
3146
|
+
"This element has no DOM node right now (hidden by visibility, or renderer not wrapping for picker)."
|
|
3147
|
+
) : null
|
|
3148
|
+
),
|
|
3149
|
+
!canPick ? h(
|
|
3150
|
+
"div",
|
|
3151
|
+
{
|
|
3152
|
+
style: {
|
|
3153
|
+
marginTop: "18px",
|
|
3154
|
+
color: "var(--jr-fg-dim)",
|
|
3155
|
+
fontSize: "11px"
|
|
3156
|
+
}
|
|
3157
|
+
},
|
|
3158
|
+
"Picker requires a DOM environment."
|
|
3159
|
+
) : null
|
|
3160
|
+
);
|
|
3161
|
+
}
|
|
3162
|
+
render();
|
|
3163
|
+
return {
|
|
3164
|
+
update: render,
|
|
3165
|
+
destroy() {
|
|
3166
|
+
stop();
|
|
3167
|
+
unsubSelection();
|
|
3168
|
+
}
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
// src/stream-tap.ts
|
|
3173
|
+
var import_core2 = require("@json-render/core");
|
|
3174
|
+
function tapJsonRenderStream(stream, events) {
|
|
3175
|
+
const [a, b] = stream.tee();
|
|
3176
|
+
void consumeToEvents(b, events, "json");
|
|
3177
|
+
return a;
|
|
3178
|
+
}
|
|
3179
|
+
function tapYamlStream(stream, events) {
|
|
3180
|
+
const [a, b] = stream.tee();
|
|
3181
|
+
void consumeToEvents(b, events, "yaml");
|
|
3182
|
+
return a;
|
|
3183
|
+
}
|
|
3184
|
+
async function consumeToEvents(stream, events, source) {
|
|
3185
|
+
events.push({ kind: "stream-lifecycle", at: Date.now(), phase: "start" });
|
|
3186
|
+
let ok = true;
|
|
3187
|
+
try {
|
|
3188
|
+
const reader = stream.getReader();
|
|
3189
|
+
while (true) {
|
|
3190
|
+
const { done, value } = await reader.read();
|
|
3191
|
+
if (done) break;
|
|
3192
|
+
if (!value) continue;
|
|
3193
|
+
forwardChunk(value, events, source);
|
|
3194
|
+
}
|
|
3195
|
+
} catch (err) {
|
|
3196
|
+
ok = false;
|
|
3197
|
+
events.push({
|
|
3198
|
+
kind: "stream-text",
|
|
3199
|
+
at: Date.now(),
|
|
3200
|
+
text: `[stream error: ${String(err)}]`
|
|
3201
|
+
});
|
|
3202
|
+
} finally {
|
|
3203
|
+
events.push({
|
|
3204
|
+
kind: "stream-lifecycle",
|
|
3205
|
+
at: Date.now(),
|
|
3206
|
+
phase: "end",
|
|
3207
|
+
ok
|
|
3208
|
+
});
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
function forwardChunk(chunk, events, source) {
|
|
3212
|
+
if (chunk.type === import_core2.SPEC_DATA_PART_TYPE) {
|
|
3213
|
+
const data = chunk.data;
|
|
3214
|
+
if (!data) return;
|
|
3215
|
+
if (data.type === "patch") {
|
|
3216
|
+
events.push({
|
|
3217
|
+
kind: "stream-patch",
|
|
3218
|
+
at: Date.now(),
|
|
3219
|
+
patch: data.patch,
|
|
3220
|
+
source
|
|
3221
|
+
});
|
|
3222
|
+
}
|
|
3223
|
+
return;
|
|
3224
|
+
}
|
|
3225
|
+
if (chunk.type === "text-delta") {
|
|
3226
|
+
const delta = chunk.delta ?? "";
|
|
3227
|
+
if (delta) {
|
|
3228
|
+
events.push({ kind: "stream-text", at: Date.now(), text: delta });
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
function scanMessageParts(parts, events, seen) {
|
|
3233
|
+
if (!parts) return;
|
|
3234
|
+
for (const part of parts) {
|
|
3235
|
+
if (!part || typeof part !== "object") continue;
|
|
3236
|
+
if (seen.has(part)) continue;
|
|
3237
|
+
seen.add(part);
|
|
3238
|
+
if (part.type === import_core2.SPEC_DATA_PART_TYPE) {
|
|
3239
|
+
const data = part.data;
|
|
3240
|
+
if (!data) continue;
|
|
3241
|
+
if (data.type === "patch") {
|
|
3242
|
+
events.push({
|
|
3243
|
+
kind: "stream-patch",
|
|
3244
|
+
at: Date.now(),
|
|
3245
|
+
patch: data.patch,
|
|
3246
|
+
source: "json"
|
|
3247
|
+
});
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
function recordUsage(events, usage) {
|
|
3253
|
+
events.push({ kind: "stream-usage", at: Date.now(), usage });
|
|
3254
|
+
}
|
|
3255
|
+
function extractSpecFromParts(parts) {
|
|
3256
|
+
if (!parts) return null;
|
|
3257
|
+
const spec = { root: "", elements: {} };
|
|
3258
|
+
let hasSpec = false;
|
|
3259
|
+
for (const part of parts) {
|
|
3260
|
+
if (!part || typeof part !== "object") continue;
|
|
3261
|
+
if (part.type !== import_core2.SPEC_DATA_PART_TYPE) continue;
|
|
3262
|
+
const data = part.data;
|
|
3263
|
+
if (!data) continue;
|
|
3264
|
+
if (data.type === "patch") {
|
|
3265
|
+
hasSpec = true;
|
|
3266
|
+
(0, import_core2.applySpecPatch)(spec, data.patch);
|
|
3267
|
+
} else if (data.type === "flat") {
|
|
3268
|
+
hasSpec = true;
|
|
3269
|
+
Object.assign(spec, data.spec);
|
|
3270
|
+
} else if (data.type === "nested") {
|
|
3271
|
+
hasSpec = true;
|
|
3272
|
+
Object.assign(spec, (0, import_core2.nestedToFlat)(data.spec));
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
return hasSpec ? spec : null;
|
|
3276
|
+
}
|
|
3277
|
+
function recordEvent(events, event) {
|
|
3278
|
+
events.push(event);
|
|
3279
|
+
}
|
|
3280
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3281
|
+
0 && (module.exports = {
|
|
3282
|
+
DEVTOOLS_KEY_ATTR,
|
|
3283
|
+
actionsTab,
|
|
3284
|
+
buildCatalogDisplayData,
|
|
3285
|
+
catalogTab,
|
|
3286
|
+
createEventStore,
|
|
3287
|
+
createPanel,
|
|
3288
|
+
createSelectionBus,
|
|
3289
|
+
createStubTab,
|
|
3290
|
+
extractSpecFromParts,
|
|
3291
|
+
findAllElementsByKey,
|
|
3292
|
+
findElementByKey,
|
|
3293
|
+
formatTime,
|
|
3294
|
+
h,
|
|
3295
|
+
highlightElement,
|
|
3296
|
+
isProduction,
|
|
3297
|
+
jsonPreview,
|
|
3298
|
+
pickerTab,
|
|
3299
|
+
recordEvent,
|
|
3300
|
+
recordUsage,
|
|
3301
|
+
replaceChildren,
|
|
3302
|
+
scanMessageParts,
|
|
3303
|
+
setHoverHighlight,
|
|
3304
|
+
specTab,
|
|
3305
|
+
startPicker,
|
|
3306
|
+
stateTab,
|
|
3307
|
+
streamTab,
|
|
3308
|
+
tapJsonRenderStream,
|
|
3309
|
+
tapYamlStream
|
|
3310
|
+
});
|
|
3311
|
+
//# sourceMappingURL=index.js.map
|