@zenfg/inspector 0.1.0-beta.3 → 0.1.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/GUIDE.md +182 -0
- package/README.md +61 -89
- package/THEMING.md +124 -0
- package/dist/FrameGraphInspector.d.ts +23 -4
- package/dist/FrameGraphInspector.d.ts.map +1 -1
- package/dist/FrameGraphInspector.js +137 -29
- package/dist/FrameGraphInspector.js.map +1 -1
- package/dist/capture.d.ts +5 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +2 -0
- package/dist/capture.js.map +1 -0
- package/dist/debugCaptureModel.d.ts +19 -0
- package/dist/debugCaptureModel.d.ts.map +1 -1
- package/dist/debugCaptureModel.js +36 -2
- package/dist/debugCaptureModel.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/panelAliasAnalysis.d.ts +1 -0
- package/dist/panelAliasAnalysis.d.ts.map +1 -1
- package/dist/panelAliasAnalysis.js +23 -54
- package/dist/panelAliasAnalysis.js.map +1 -1
- package/dist/panelCytoscapeGraphRenderer.d.ts +15 -2
- package/dist/panelCytoscapeGraphRenderer.d.ts.map +1 -1
- package/dist/panelCytoscapeGraphRenderer.js +145 -23
- package/dist/panelCytoscapeGraphRenderer.js.map +1 -1
- package/dist/panelDetailLayout.d.ts +27 -0
- package/dist/panelDetailLayout.d.ts.map +1 -0
- package/dist/panelDetailLayout.js +174 -0
- package/dist/panelDetailLayout.js.map +1 -0
- package/dist/panelDetailStyles.d.ts +2 -0
- package/dist/panelDetailStyles.d.ts.map +1 -0
- package/dist/panelDetailStyles.js +57 -0
- package/dist/panelDetailStyles.js.map +1 -0
- package/dist/panelDiagnosticsView.d.ts +10 -1
- package/dist/panelDiagnosticsView.d.ts.map +1 -1
- package/dist/panelDiagnosticsView.js +166 -57
- package/dist/panelDiagnosticsView.js.map +1 -1
- package/dist/panelDomHelpers.d.ts.map +1 -1
- package/dist/panelDomHelpers.js +6 -5
- package/dist/panelDomHelpers.js.map +1 -1
- package/dist/panelGraphLayout.d.ts +3 -2
- package/dist/panelGraphLayout.d.ts.map +1 -1
- package/dist/panelGraphLayout.js +6 -5
- package/dist/panelGraphLayout.js.map +1 -1
- package/dist/panelGraphRenderer.d.ts +10 -0
- package/dist/panelGraphRenderer.d.ts.map +1 -1
- package/dist/panelGraphRenderer.js.map +1 -1
- package/dist/panelGraphScene.d.ts +1 -0
- package/dist/panelGraphScene.d.ts.map +1 -1
- package/dist/panelGraphScene.js +69 -32
- package/dist/panelGraphScene.js.map +1 -1
- package/dist/panelGraphSearch.d.ts +16 -0
- package/dist/panelGraphSearch.d.ts.map +1 -0
- package/dist/panelGraphSearch.js +93 -0
- package/dist/panelGraphSearch.js.map +1 -0
- package/dist/panelGraphView.d.ts +82 -0
- package/dist/panelGraphView.d.ts.map +1 -1
- package/dist/panelGraphView.js +15 -4
- package/dist/panelGraphView.js.map +1 -1
- package/dist/panelGraphVisuals.d.ts +14 -6
- package/dist/panelGraphVisuals.d.ts.map +1 -1
- package/dist/panelGraphVisuals.js +126 -51
- package/dist/panelGraphVisuals.js.map +1 -1
- package/dist/panelInspectorView.d.ts +8 -0
- package/dist/panelInspectorView.d.ts.map +1 -1
- package/dist/panelInspectorView.js +242 -48
- package/dist/panelInspectorView.js.map +1 -1
- package/dist/panelListStyles.d.ts +3 -0
- package/dist/panelListStyles.d.ts.map +1 -0
- package/dist/panelListStyles.js +155 -0
- package/dist/panelListStyles.js.map +1 -0
- package/dist/panelMemoryStyles.d.ts +3 -0
- package/dist/panelMemoryStyles.d.ts.map +1 -0
- package/dist/panelMemoryStyles.js +42 -0
- package/dist/panelMemoryStyles.js.map +1 -0
- package/dist/panelMemoryView.d.ts +13 -1
- package/dist/panelMemoryView.d.ts.map +1 -1
- package/dist/panelMemoryView.js +161 -41
- package/dist/panelMemoryView.js.map +1 -1
- package/dist/panelPassesView.d.ts +15 -0
- package/dist/panelPassesView.d.ts.map +1 -1
- package/dist/panelPassesView.js +202 -72
- package/dist/panelPassesView.js.map +1 -1
- package/dist/panelRawView.d.ts +15 -0
- package/dist/panelRawView.d.ts.map +1 -0
- package/dist/panelRawView.js +105 -0
- package/dist/panelRawView.js.map +1 -0
- package/dist/panelResourcesView.d.ts +8 -0
- package/dist/panelResourcesView.d.ts.map +1 -1
- package/dist/panelResourcesView.js +62 -31
- package/dist/panelResourcesView.js.map +1 -1
- package/dist/panelSelection.d.ts +9 -1
- package/dist/panelSelection.d.ts.map +1 -1
- package/dist/panelSelection.js +34 -4
- package/dist/panelSelection.js.map +1 -1
- package/dist/panelTheme.d.ts +13 -0
- package/dist/panelTheme.d.ts.map +1 -0
- package/dist/panelTheme.js +104 -0
- package/dist/panelTheme.js.map +1 -0
- package/dist/panelTypes.d.ts +10 -1
- package/dist/panelTypes.d.ts.map +1 -1
- package/dist/panelTypes.js.map +1 -1
- package/dist/panelVisualTheme.d.ts +147 -50
- package/dist/panelVisualTheme.d.ts.map +1 -1
- package/dist/panelVisualTheme.js +34 -44
- package/dist/panelVisualTheme.js.map +1 -1
- package/dist/panelWorkbenchHelpers.d.ts +12 -1
- package/dist/panelWorkbenchHelpers.d.ts.map +1 -1
- package/dist/panelWorkbenchHelpers.js +62 -1
- package/dist/panelWorkbenchHelpers.js.map +1 -1
- package/dist/panelWorkbenchStyles.d.ts +2 -0
- package/dist/panelWorkbenchStyles.d.ts.map +1 -0
- package/dist/panelWorkbenchStyles.js +101 -0
- package/dist/panelWorkbenchStyles.js.map +1 -0
- package/dist/panelWorkbenchView.d.ts +16 -0
- package/dist/panelWorkbenchView.d.ts.map +1 -1
- package/dist/panelWorkbenchView.js +228 -50
- package/dist/panelWorkbenchView.js.map +1 -1
- package/dist/query.js.map +1 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +147 -195
- package/dist/styles.js.map +1 -1
- package/dist/theme.d.ts +22 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +10 -0
- package/dist/theme.js.map +1 -0
- package/dist/themeDefinitions.d.ts +251 -0
- package/dist/themeDefinitions.d.ts.map +1 -0
- package/dist/themeDefinitions.js +96 -0
- package/dist/themeDefinitions.js.map +1 -0
- package/dist/themePalette.d.ts +66 -0
- package/dist/themePalette.d.ts.map +1 -0
- package/dist/themePalette.js +37 -0
- package/dist/themePalette.js.map +1 -0
- package/dist/themes.css +165 -0
- package/package.json +44 -10
- package/src/FrameGraphInspector.ts +130 -31
- package/src/capture.ts +4 -0
- package/src/debugCaptureModel.ts +53 -2
- package/src/index.ts +6 -2
- package/src/panelAliasAnalysis.ts +24 -70
- package/src/panelCytoscapeGraphRenderer.ts +140 -23
- package/src/panelDetailLayout.ts +155 -0
- package/src/panelDetailStyles.ts +56 -0
- package/src/panelDiagnosticsView.ts +166 -85
- package/src/panelDomHelpers.ts +4 -5
- package/src/panelGraphLayout.ts +6 -5
- package/src/panelGraphRenderer.ts +7 -0
- package/src/panelGraphScene.ts +68 -30
- package/src/panelGraphSearch.ts +96 -0
- package/src/panelGraphView.ts +14 -4
- package/src/panelGraphVisuals.ts +122 -52
- package/src/panelInspectorView.ts +227 -48
- package/src/panelListStyles.ts +154 -0
- package/src/panelMemoryStyles.ts +41 -0
- package/src/panelMemoryView.ts +178 -53
- package/src/panelPassesView.ts +190 -137
- package/src/panelRawView.ts +99 -0
- package/src/panelResourcesView.ts +69 -56
- package/src/panelSelection.ts +37 -5
- package/src/panelTheme.ts +85 -0
- package/src/panelTypes.ts +7 -1
- package/src/panelVisualTheme.ts +38 -49
- package/src/panelWorkbenchHelpers.ts +60 -2
- package/src/panelWorkbenchStyles.ts +100 -0
- package/src/panelWorkbenchView.ts +201 -52
- package/src/query.ts +11 -11
- package/src/styles.ts +148 -195
- package/src/theme.ts +24 -0
- package/src/themeDefinitions.ts +103 -0
- package/src/themePalette.ts +44 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GRAPH_VISUAL_THEME, type GraphVisualTheme } from './panelVisualTheme.ts';
|
|
1
2
|
import type cytoscape from 'cytoscape';
|
|
2
3
|
import type { ELK } from 'elkjs/lib/elk-api';
|
|
3
4
|
|
|
@@ -18,6 +19,9 @@ import {
|
|
|
18
19
|
} from './panelGraphScene.ts';
|
|
19
20
|
import {
|
|
20
21
|
createGraphStyles,
|
|
22
|
+
fitGraphLabel,
|
|
23
|
+
fitCollapsedGroupLabel,
|
|
24
|
+
graphLabelFontSize,
|
|
21
25
|
expandedGroupLabelMaxWidth,
|
|
22
26
|
GRAPH_GEOMETRY,
|
|
23
27
|
graphEdgeDisplayLabel,
|
|
@@ -38,7 +42,7 @@ export type GraphResizeSubscription = { disconnect(): void };
|
|
|
38
42
|
export type GraphRendererEnvironment = {
|
|
39
43
|
readonly createElement: (tagName: string) => HTMLElement;
|
|
40
44
|
readonly loadRuntime: () => Promise<GraphRendererRuntime>;
|
|
41
|
-
readonly layoutScene: (elk: ELK, scene: GraphScene) => Promise<GraphLayoutResult>;
|
|
45
|
+
readonly layoutScene: (elk: ELK, scene: GraphScene, theme?: GraphVisualTheme) => Promise<GraphLayoutResult>;
|
|
42
46
|
readonly observeResize?: (element: HTMLElement, onResize: () => void) => GraphResizeSubscription;
|
|
43
47
|
};
|
|
44
48
|
|
|
@@ -49,6 +53,9 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
49
53
|
private readonly statusMessage: HTMLElement;
|
|
50
54
|
private readonly retryButton: HTMLButtonElement;
|
|
51
55
|
private readonly resizeSubscription: GraphResizeSubscription | undefined;
|
|
56
|
+
private theme: GraphVisualTheme = GRAPH_VISUAL_THEME;
|
|
57
|
+
private geometryRevision = 0;
|
|
58
|
+
private wasVisible = false;
|
|
52
59
|
private core: cytoscape.Core | undefined;
|
|
53
60
|
private elk: ELK | undefined;
|
|
54
61
|
private loading: Promise<void> | undefined;
|
|
@@ -64,6 +71,15 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
64
71
|
private anchorTargetContentKey: string | undefined;
|
|
65
72
|
private failedSceneContentKey: string | undefined;
|
|
66
73
|
private overview = false;
|
|
74
|
+
private dragPanning = false;
|
|
75
|
+
private pointerInside = false;
|
|
76
|
+
private pointerOverElement = false;
|
|
77
|
+
private readonly cancelPointerInteraction = () => {
|
|
78
|
+
this.dragPanning = false;
|
|
79
|
+
this.cancelGroupTap();
|
|
80
|
+
this.clearHover();
|
|
81
|
+
};
|
|
82
|
+
private pendingReveal: { readonly request: NonNullable<GraphRenderRequest['reveal']>; readonly contentKey: string; readonly captureRevision: number | undefined } | undefined;
|
|
67
83
|
private pendingGroupTap: { readonly id: string; readonly timer: ReturnType<typeof setTimeout> } | undefined;
|
|
68
84
|
|
|
69
85
|
constructor(
|
|
@@ -72,11 +88,15 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
72
88
|
) {
|
|
73
89
|
this.canvasHost = environment.createElement('div');
|
|
74
90
|
this.canvasHost.className = 'zenfg-inspector-graph-canvas';
|
|
91
|
+
this.updateCursor();
|
|
75
92
|
this.tooltip = environment.createElement('div');
|
|
76
93
|
this.tooltip.className = 'zenfg-inspector-graph-tooltip';
|
|
77
94
|
this.tooltip.hidden = true;
|
|
78
95
|
// Cytoscape's node mouseout is not guaranteed when leaving its canvas directly.
|
|
79
|
-
this.canvasHost.addEventListener('
|
|
96
|
+
this.canvasHost.addEventListener('pointerenter', () => { this.pointerInside = true; });
|
|
97
|
+
this.canvasHost.addEventListener('pointerleave', () => { this.pointerInside = false; this.clearHover(); });
|
|
98
|
+
this.canvasHost.addEventListener('pointercancel', this.cancelPointerInteraction);
|
|
99
|
+
this.canvasHost.ownerDocument?.defaultView?.addEventListener('blur', this.cancelPointerInteraction);
|
|
80
100
|
this.status = environment.createElement('div');
|
|
81
101
|
this.status.className = 'zenfg-inspector-graph-status';
|
|
82
102
|
this.status.hidden = true;
|
|
@@ -90,11 +110,17 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
90
110
|
this.retryButton.addEventListener('click', () => this.relayout());
|
|
91
111
|
this.status.append(this.statusMessage, this.retryButton);
|
|
92
112
|
this.host.replaceChildren(this.canvasHost, this.status, this.tooltip);
|
|
93
|
-
this.resizeSubscription = environment.observeResize?.(host, () =>
|
|
113
|
+
this.resizeSubscription = environment.observeResize?.(host, () => {
|
|
114
|
+
const visible = host.clientWidth > 0 && host.clientHeight > 0;
|
|
115
|
+
if (visible && !this.wasVisible) this.latestRequest?.onVisible?.();
|
|
116
|
+
this.wasVisible = visible;
|
|
117
|
+
this.core?.resize();
|
|
118
|
+
});
|
|
94
119
|
}
|
|
95
120
|
|
|
96
121
|
render(request: GraphRenderRequest): void {
|
|
97
122
|
if (this.destroyed) return;
|
|
123
|
+
if (request.theme) this.setTheme(request.theme);
|
|
98
124
|
if (this.latestRequest?.scene !== request.scene
|
|
99
125
|
|| (this.latestRequest?.selected && selectionKey(this.latestRequest.selected)) !== (request.selected && selectionKey(request.selected))) {
|
|
100
126
|
this.cancelGroupTap();
|
|
@@ -104,6 +130,13 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
104
130
|
this.core?.elements().removeClass('semantic-hover');
|
|
105
131
|
}
|
|
106
132
|
this.latestRequest = request;
|
|
133
|
+
if (request.reveal) {
|
|
134
|
+
this.pendingReveal = { request: request.reveal, contentKey: request.scene.contentKey, captureRevision: request.captureRevision };
|
|
135
|
+
} else if (this.pendingReveal && (this.pendingReveal.captureRevision !== request.captureRevision
|
|
136
|
+
|| this.pendingReveal.contentKey !== request.scene.contentKey
|
|
137
|
+
|| !request.selected || selectionKey(request.selected) !== selectionKey(this.pendingReveal.request.selection))) {
|
|
138
|
+
this.pendingReveal = undefined;
|
|
139
|
+
}
|
|
107
140
|
if (request.fit) {
|
|
108
141
|
this.fitTargetContentKey = request.scene.contentKey;
|
|
109
142
|
} else if (this.fitTargetContentKey !== request.scene.contentKey) {
|
|
@@ -132,16 +165,48 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
132
165
|
this.syncInteraction(request);
|
|
133
166
|
this.consumeFitIfCurrentTarget();
|
|
134
167
|
this.consumeAnchorIfCurrentTarget();
|
|
168
|
+
this.consumeReveal();
|
|
135
169
|
this.processedVersion = version;
|
|
136
170
|
return;
|
|
137
171
|
}
|
|
138
172
|
this.scheduleProcessing();
|
|
139
173
|
}
|
|
140
174
|
|
|
175
|
+
setTheme(theme: GraphVisualTheme): void {
|
|
176
|
+
if (this.destroyed || JSON.stringify(theme) === JSON.stringify(this.theme)) return;
|
|
177
|
+
const typographyChanged = theme.fontFamily !== this.theme.fontFamily || theme.fontSize !== this.theme.fontSize;
|
|
178
|
+
this.theme = theme;
|
|
179
|
+
this.core?.style().fromJson(createGraphStyles(theme)).update();
|
|
180
|
+
if (!typographyChanged) return;
|
|
181
|
+
this.geometryRevision++;
|
|
182
|
+
if (this.core && this.latestRequest && this.appliedScene) {
|
|
183
|
+
const selected = this.core.nodes('.semantic-selected').first();
|
|
184
|
+
const center = this.core.extent();
|
|
185
|
+
const target = selected.nonempty() ? selected : this.core.nodes().filter(node => !node.isParent()).sort((a, b) => {
|
|
186
|
+
const distance = (node: cytoscape.NodeSingular) => Math.hypot(node.position('x') - center.x1 - center.w / 2, node.position('y') - center.y1 - center.h / 2);
|
|
187
|
+
return distance(a as cytoscape.NodeSingular) - distance(b as cytoscape.NodeSingular);
|
|
188
|
+
}).first();
|
|
189
|
+
if (target.nonempty()) {
|
|
190
|
+
this.anchorElementId = target.id();
|
|
191
|
+
this.anchorTargetContentKey = this.latestRequest.scene.contentKey;
|
|
192
|
+
}
|
|
193
|
+
this.forceRelayout = true;
|
|
194
|
+
++this.requestVersion;
|
|
195
|
+
this.scheduleProcessing();
|
|
196
|
+
}
|
|
197
|
+
this.updateSemanticZoom(true);
|
|
198
|
+
}
|
|
199
|
+
|
|
141
200
|
resize(): void {
|
|
142
201
|
this.core?.resize();
|
|
143
202
|
}
|
|
144
203
|
|
|
204
|
+
cancelReveal(): void {
|
|
205
|
+
this.pendingReveal = undefined;
|
|
206
|
+
this.cancelGroupTap();
|
|
207
|
+
this.clearHover();
|
|
208
|
+
}
|
|
209
|
+
|
|
145
210
|
fit(): void {
|
|
146
211
|
const targetContentKey = this.latestRequest?.scene.contentKey;
|
|
147
212
|
if (targetContentKey) this.fitTargetContentKey = targetContentKey;
|
|
@@ -171,6 +236,7 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
171
236
|
this.cancelGroupTap();
|
|
172
237
|
this.requestVersion++;
|
|
173
238
|
this.resizeSubscription?.disconnect();
|
|
239
|
+
this.canvasHost.ownerDocument?.defaultView?.removeEventListener('blur', this.cancelPointerInteraction);
|
|
174
240
|
this.hideTooltip();
|
|
175
241
|
this.core?.destroy();
|
|
176
242
|
this.core = undefined;
|
|
@@ -204,7 +270,7 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
204
270
|
|
|
205
271
|
if (request.scene.nodes.length === 0) {
|
|
206
272
|
this.hideTooltip();
|
|
207
|
-
if (this.core) replaceElements(this.core, request.scene);
|
|
273
|
+
if (this.core) replaceElements(this.core, request.scene, this.theme);
|
|
208
274
|
this.appliedScene = request.scene;
|
|
209
275
|
this.forceRelayout = false;
|
|
210
276
|
if (this.fitTargetContentKey === request.scene.contentKey) this.fitTargetContentKey = undefined;
|
|
@@ -227,6 +293,7 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
227
293
|
this.syncInteraction(request);
|
|
228
294
|
this.consumeFitIfCurrentTarget();
|
|
229
295
|
this.consumeAnchorIfCurrentTarget();
|
|
296
|
+
this.consumeReveal();
|
|
230
297
|
this.hideStatus();
|
|
231
298
|
this.processedVersion = version;
|
|
232
299
|
continue;
|
|
@@ -247,16 +314,18 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
247
314
|
|
|
248
315
|
if (geometryChanged || this.forceRelayout) {
|
|
249
316
|
this.showStatus('layout', 'Laying out graph…');
|
|
250
|
-
const
|
|
317
|
+
const geometryRevision = this.geometryRevision;
|
|
318
|
+
const result = await this.environment.layoutScene(this.elk!, request.scene, this.theme);
|
|
319
|
+
if (geometryRevision !== this.geometryRevision) continue;
|
|
251
320
|
const layoutRequest = this.resolveCurrentRequest(version, request.scene.contentKey);
|
|
252
321
|
if (!layoutRequest) continue;
|
|
253
322
|
({ version, request } = layoutRequest);
|
|
254
|
-
if (topologyChanged || !this.appliedScene) replaceElements(core, request.scene);
|
|
255
|
-
else updateElementData(core, request.scene);
|
|
323
|
+
if (topologyChanged || !this.appliedScene) replaceElements(core, request.scene, this.theme);
|
|
324
|
+
else updateElementData(core, request.scene, this.theme);
|
|
256
325
|
applyGraphLayout(core, request.scene, result);
|
|
257
326
|
laidOut = true;
|
|
258
327
|
} else {
|
|
259
|
-
updateElementData(core, request.scene);
|
|
328
|
+
updateElementData(core, request.scene, this.theme);
|
|
260
329
|
}
|
|
261
330
|
|
|
262
331
|
syncExpandedGroupLabelGeometry(core);
|
|
@@ -276,6 +345,7 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
276
345
|
restoreViewport(core, previousViewport);
|
|
277
346
|
}
|
|
278
347
|
this.consumeAnchorIfCurrentTarget();
|
|
348
|
+
this.consumeReveal();
|
|
279
349
|
this.updateSemanticZoom(true);
|
|
280
350
|
this.hideStatus();
|
|
281
351
|
this.processedVersion = version;
|
|
@@ -313,7 +383,7 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
313
383
|
this.core = runtime.createCore({
|
|
314
384
|
container: this.canvasHost,
|
|
315
385
|
elements: [],
|
|
316
|
-
style: createGraphStyles(),
|
|
386
|
+
style: createGraphStyles(this.theme),
|
|
317
387
|
layout: { name: 'preset' },
|
|
318
388
|
minZoom: 0.03,
|
|
319
389
|
maxZoom: 4,
|
|
@@ -352,9 +422,12 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
352
422
|
});
|
|
353
423
|
const hoverElement = (event: cytoscape.EventObject) => {
|
|
354
424
|
const request = this.interactiveRequest();
|
|
355
|
-
if (!request) return;
|
|
425
|
+
if (!request || this.dragPanning) return;
|
|
356
426
|
const id = event.target.id() as GraphSceneElementId;
|
|
357
427
|
const selection = request.scene.interaction.selectionByElementId.get(id);
|
|
428
|
+
this.pointerInside = true;
|
|
429
|
+
this.pointerOverElement = !!selection;
|
|
430
|
+
this.updateCursor();
|
|
358
431
|
if (selection) request.onHover(selection);
|
|
359
432
|
const title = event.target.data('tooltip') as string | undefined;
|
|
360
433
|
if (title) {
|
|
@@ -371,6 +444,18 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
371
444
|
const clearHover = () => this.clearHover();
|
|
372
445
|
core.on('mouseout', 'node', clearHover);
|
|
373
446
|
core.on('mouseout', 'edge', clearHover);
|
|
447
|
+
core.on('dragpan', () => {
|
|
448
|
+
if (this.dragPanning) return;
|
|
449
|
+
this.dragPanning = true;
|
|
450
|
+
this.cancelGroupTap();
|
|
451
|
+
this.clearHover();
|
|
452
|
+
});
|
|
453
|
+
core.on('tapend', (event) => {
|
|
454
|
+
this.dragPanning = false;
|
|
455
|
+
this.pointerOverElement = this.pointerInside && event.target !== core
|
|
456
|
+
&& !!this.interactiveRequest()?.scene.interaction.selectionByElementId.get(event.target.id());
|
|
457
|
+
this.updateCursor();
|
|
458
|
+
});
|
|
374
459
|
core.on('zoom', () => this.updateSemanticZoom());
|
|
375
460
|
}
|
|
376
461
|
|
|
@@ -416,6 +501,23 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
416
501
|
this.anchorTargetContentKey = undefined;
|
|
417
502
|
}
|
|
418
503
|
|
|
504
|
+
private consumeReveal(): void {
|
|
505
|
+
const pending = this.pendingReveal;
|
|
506
|
+
const request = this.latestRequest;
|
|
507
|
+
if (!pending || !request || !this.core || this.targetNeedsApply()) return;
|
|
508
|
+
this.pendingReveal = undefined;
|
|
509
|
+
if (pending.captureRevision !== request.captureRevision || pending.contentKey !== request.scene.contentKey) return;
|
|
510
|
+
const ids = request.scene.interaction.primaryElementIdsBySelection.get(selectionKey(pending.request.selection)) ?? [];
|
|
511
|
+
const targets = this.core.collection();
|
|
512
|
+
for (const id of ids) targets.merge(this.core.getElementById(id));
|
|
513
|
+
if (!targets.length) return;
|
|
514
|
+
this.core.resize();
|
|
515
|
+
this.core.fit(targets, 64);
|
|
516
|
+
if (this.core.zoom() > 1.2) this.core.zoom(1.2);
|
|
517
|
+
this.core.center(targets);
|
|
518
|
+
this.updateSemanticZoom();
|
|
519
|
+
}
|
|
520
|
+
|
|
419
521
|
private targetNeedsApply(): boolean {
|
|
420
522
|
const target = this.latestRequest?.scene;
|
|
421
523
|
if (!target || !this.appliedScene) return target !== undefined;
|
|
@@ -425,7 +527,7 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
425
527
|
private updateSemanticZoom(force = false): void {
|
|
426
528
|
const core = this.core;
|
|
427
529
|
if (!core) return;
|
|
428
|
-
const overview = isOverviewGraphScale(core.zoom());
|
|
530
|
+
const overview = isOverviewGraphScale(core.zoom(), this.theme);
|
|
429
531
|
if (!force && overview === this.overview) return;
|
|
430
532
|
this.overview = overview;
|
|
431
533
|
core.batch(() => {
|
|
@@ -456,10 +558,16 @@ export class CytoscapeGraphRenderer implements GraphRenderer {
|
|
|
456
558
|
}
|
|
457
559
|
|
|
458
560
|
private clearHover(): void {
|
|
561
|
+
this.pointerOverElement = false;
|
|
562
|
+
this.updateCursor();
|
|
459
563
|
this.hideTooltip();
|
|
460
564
|
this.latestRequest?.onHover(undefined);
|
|
461
565
|
}
|
|
462
566
|
|
|
567
|
+
private updateCursor(): void {
|
|
568
|
+
this.canvasHost.style.cursor = this.dragPanning ? 'grabbing' : this.pointerOverElement ? 'pointer' : 'grab';
|
|
569
|
+
}
|
|
570
|
+
|
|
463
571
|
private showStatus(kind: 'loading' | 'layout' | 'empty' | 'error', message: string, retry = false): void {
|
|
464
572
|
this.status.dataset.state = kind;
|
|
465
573
|
this.statusMessage.textContent = message;
|
|
@@ -525,9 +633,9 @@ const browserGraphRendererEnvironment: GraphRendererEnvironment = {
|
|
|
525
633
|
},
|
|
526
634
|
};
|
|
527
635
|
|
|
528
|
-
function replaceElements(core: cytoscape.Core, scene: GraphScene): void {
|
|
636
|
+
function replaceElements(core: cytoscape.Core, scene: GraphScene, theme: GraphVisualTheme = GRAPH_VISUAL_THEME): void {
|
|
529
637
|
const definitions: cytoscape.ElementDefinition[] = [
|
|
530
|
-
...scene.nodes.map(nodeDefinition),
|
|
638
|
+
...scene.nodes.map(node => nodeDefinition(node, theme)),
|
|
531
639
|
...scene.edges.map(edgeDefinition),
|
|
532
640
|
];
|
|
533
641
|
core.startBatch();
|
|
@@ -536,11 +644,11 @@ function replaceElements(core: cytoscape.Core, scene: GraphScene): void {
|
|
|
536
644
|
core.endBatch();
|
|
537
645
|
}
|
|
538
646
|
|
|
539
|
-
export function updateElementData(core: cytoscape.Core, scene: GraphScene): void {
|
|
647
|
+
export function updateElementData(core: cytoscape.Core, scene: GraphScene, theme: GraphVisualTheme = GRAPH_VISUAL_THEME): void {
|
|
540
648
|
core.batch(() => {
|
|
541
649
|
for (const node of scene.nodes) {
|
|
542
650
|
const element = core.getElementById(node.id);
|
|
543
|
-
if (element.nonempty()) element.data(nodeRenderableData(node));
|
|
651
|
+
if (element.nonempty()) element.data(nodeRenderableData(node, theme));
|
|
544
652
|
}
|
|
545
653
|
for (const edge of scene.edges) {
|
|
546
654
|
const element = core.getElementById(edge.id);
|
|
@@ -549,22 +657,30 @@ export function updateElementData(core: cytoscape.Core, scene: GraphScene): void
|
|
|
549
657
|
});
|
|
550
658
|
}
|
|
551
659
|
|
|
552
|
-
function nodeDefinition(node: GraphSceneNode): cytoscape.ElementDefinition {
|
|
660
|
+
function nodeDefinition(node: GraphSceneNode, theme: GraphVisualTheme): cytoscape.ElementDefinition {
|
|
553
661
|
return {
|
|
554
662
|
group: 'nodes',
|
|
663
|
+
pannable: true,
|
|
555
664
|
data: {
|
|
556
665
|
id: node.id,
|
|
557
666
|
parent: node.parentId,
|
|
558
|
-
...nodeRenderableData(node),
|
|
667
|
+
...nodeRenderableData(node, theme),
|
|
559
668
|
},
|
|
560
669
|
};
|
|
561
670
|
}
|
|
562
671
|
|
|
563
|
-
function nodeRenderableData(node: GraphSceneNode): Record<string, unknown> {
|
|
564
|
-
const dimensions = nodeDimensions(node);
|
|
672
|
+
function nodeRenderableData(node: GraphSceneNode, theme: GraphVisualTheme): Record<string, unknown> {
|
|
673
|
+
const dimensions = nodeDimensions(node, theme);
|
|
565
674
|
const labelMaxWidth = node.kind === 'group' && node.collapsed
|
|
566
675
|
? expandedGroupLabelMaxWidth(dimensions.width)
|
|
567
|
-
: 160;
|
|
676
|
+
: 160 * theme.fontSize / GRAPH_GEOMETRY.baseFontSize;
|
|
677
|
+
const fitLabel = (label: string): string => node.kind !== 'group'
|
|
678
|
+
? fitGraphLabel(label, labelMaxWidth, theme)
|
|
679
|
+
: node.collapsed ? fitCollapsedGroupLabel(label, labelMaxWidth, theme) : label;
|
|
680
|
+
const detailLabel = fitLabel(node.label);
|
|
681
|
+
const labelFontSize = node.kind === 'group' && !node.collapsed
|
|
682
|
+
? theme.fontSize * 1.2
|
|
683
|
+
: graphLabelFontSize(detailLabel, labelMaxWidth, dimensions.height - 12 * theme.fontSize / GRAPH_GEOMETRY.baseFontSize, theme);
|
|
568
684
|
return {
|
|
569
685
|
kind: node.kind,
|
|
570
686
|
passKind: node.kind === 'pass' ? node.passKind : undefined,
|
|
@@ -572,9 +688,10 @@ function nodeRenderableData(node: GraphSceneNode): Record<string, unknown> {
|
|
|
572
688
|
collapsed: node.kind === 'group' && node.collapsed ? 1 : 0,
|
|
573
689
|
hasCulled: node.kind === 'group' && node.culledNodeCount > 0 ? 1 : 0,
|
|
574
690
|
depthBand: node.kind === 'group' ? node.depthBand : undefined,
|
|
575
|
-
detailLabel
|
|
576
|
-
|
|
577
|
-
|
|
691
|
+
detailLabel,
|
|
692
|
+
labelFontSize,
|
|
693
|
+
overviewLabel: fitLabel(node.overviewLabel),
|
|
694
|
+
displayLabel: detailLabel,
|
|
578
695
|
tooltip: node.title,
|
|
579
696
|
labelMaxWidth,
|
|
580
697
|
width: dimensions.width,
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const MIN_DETAIL_WIDTH = 300;
|
|
2
|
+
const MAX_DETAIL_WIDTH = 480;
|
|
3
|
+
const MIN_MAIN_WIDTH = 520;
|
|
4
|
+
const DIVIDER_WIDTH = 8;
|
|
5
|
+
|
|
6
|
+
/** Owns the dock/drawer geometry and modal keyboard boundary for one workbench. */
|
|
7
|
+
export class DetailLayout {
|
|
8
|
+
private readonly backdrop = document.createElement('button');
|
|
9
|
+
private readonly divider = document.createElement('div');
|
|
10
|
+
private readonly resizeObserver: ResizeObserver | undefined;
|
|
11
|
+
private readonly abort = new AbortController();
|
|
12
|
+
private open = false;
|
|
13
|
+
private preferredWidth = MIN_DETAIL_WIDTH;
|
|
14
|
+
private previousFocus: HTMLElement | undefined;
|
|
15
|
+
private dragging = false;
|
|
16
|
+
private modal = false;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
private readonly workspace: HTMLElement,
|
|
20
|
+
private readonly main: HTMLElement,
|
|
21
|
+
private readonly aside: HTMLElement,
|
|
22
|
+
private readonly chrome: HTMLElement,
|
|
23
|
+
private readonly onClose: () => void,
|
|
24
|
+
) {
|
|
25
|
+
this.backdrop.type = 'button';
|
|
26
|
+
this.backdrop.className = 'zenfg-inspector-detail-backdrop';
|
|
27
|
+
this.backdrop.setAttribute('aria-label', 'Close selection inspector');
|
|
28
|
+
this.backdrop.tabIndex = -1;
|
|
29
|
+
this.backdrop.hidden = true;
|
|
30
|
+
this.backdrop.addEventListener('click', onClose);
|
|
31
|
+
this.divider.className = 'zenfg-inspector-detail-divider';
|
|
32
|
+
this.divider.setAttribute('role', 'separator');
|
|
33
|
+
this.divider.setAttribute('aria-label', 'Resize selection inspector');
|
|
34
|
+
this.divider.setAttribute('aria-orientation', 'vertical');
|
|
35
|
+
this.divider.tabIndex = 0;
|
|
36
|
+
this.divider.hidden = true;
|
|
37
|
+
workspace.insertBefore(this.divider, aside);
|
|
38
|
+
workspace.insertBefore(this.backdrop, aside);
|
|
39
|
+
this.divider.addEventListener('pointerdown', (event) => {
|
|
40
|
+
this.dragging = true;
|
|
41
|
+
this.divider.setPointerCapture(event.pointerId);
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
});
|
|
44
|
+
this.divider.addEventListener('pointermove', (event) => {
|
|
45
|
+
if (!this.dragging) return;
|
|
46
|
+
this.preferredWidth = this.clampWidth(workspace.getBoundingClientRect().right - event.clientX);
|
|
47
|
+
this.applyGeometry();
|
|
48
|
+
});
|
|
49
|
+
this.divider.addEventListener('pointerup', () => { this.dragging = false; });
|
|
50
|
+
this.divider.addEventListener('lostpointercapture', () => { this.dragging = false; });
|
|
51
|
+
this.divider.addEventListener('keydown', (event) => {
|
|
52
|
+
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
event.stopPropagation();
|
|
55
|
+
this.preferredWidth = this.clampWidth(event.key === 'Home' ? MIN_DETAIL_WIDTH : event.key === 'End' ? MAX_DETAIL_WIDTH
|
|
56
|
+
: this.preferredWidth + (event.key === 'ArrowLeft' ? 20 : -20));
|
|
57
|
+
this.applyGeometry();
|
|
58
|
+
});
|
|
59
|
+
workspace.parentElement?.addEventListener('keydown', this.handleKey, { signal: this.abort.signal });
|
|
60
|
+
this.resizeObserver = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(() => this.applyGeometry());
|
|
61
|
+
this.resizeObserver?.observe(workspace);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get isDrawer(): boolean { return this.modal; }
|
|
65
|
+
|
|
66
|
+
update(open: boolean): void {
|
|
67
|
+
const changed = open !== this.open;
|
|
68
|
+
if (changed && open) this.previousFocus = this.workspace.ownerDocument.activeElement as HTMLElement | undefined;
|
|
69
|
+
this.open = open;
|
|
70
|
+
this.applyGeometry();
|
|
71
|
+
if (changed && !open) this.restoreFocus();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
destroy(): void {
|
|
75
|
+
this.abort.abort();
|
|
76
|
+
this.resizeObserver?.disconnect();
|
|
77
|
+
this.main.inert = false;
|
|
78
|
+
this.chrome.inert = false;
|
|
79
|
+
for (const sibling of this.chrome.parentElement?.children ?? []) if (sibling !== this.workspace) (sibling as HTMLElement).inert = false;
|
|
80
|
+
if (this.open && this.modal) this.restoreFocus();
|
|
81
|
+
this.open = false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private restoreFocus(): void {
|
|
85
|
+
const isAvailable = (element: HTMLElement | undefined): element is HTMLElement => {
|
|
86
|
+
if (!element?.isConnected || element === element.ownerDocument.body || element.closest('[hidden], [inert]') || element.matches(':disabled')) return false;
|
|
87
|
+
for (let parent: HTMLElement | null = element; parent; parent = parent.parentElement) if (parent.inert) return false;
|
|
88
|
+
return true;
|
|
89
|
+
};
|
|
90
|
+
const target = isAvailable(this.previousFocus) ? this.previousFocus
|
|
91
|
+
: this.chrome.querySelector<HTMLElement>('[role="tab"][aria-selected="true"]') ?? undefined;
|
|
92
|
+
if (isAvailable(target)) target.focus({ preventScroll: true });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private clampWidth(width: number): number {
|
|
96
|
+
const available = this.workspace.getBoundingClientRect().width || 1024;
|
|
97
|
+
return Math.max(MIN_DETAIL_WIDTH, Math.min(MAX_DETAIL_WIDTH, available - MIN_MAIN_WIDTH - DIVIDER_WIDTH, width));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private applyGeometry(): void {
|
|
101
|
+
const wasModal = this.modal;
|
|
102
|
+
const available = this.workspace.getBoundingClientRect().width || 1024;
|
|
103
|
+
this.modal = available < MIN_MAIN_WIDTH + DIVIDER_WIDTH + MIN_DETAIL_WIDTH;
|
|
104
|
+
if (this.modal && !wasModal && this.open && !this.aside.contains(document.activeElement)) this.previousFocus = this.workspace.ownerDocument.activeElement as HTMLElement | undefined;
|
|
105
|
+
const width = this.clampWidth(this.preferredWidth);
|
|
106
|
+
this.workspace.style.setProperty('--fgd-detail-width', `${width}px`);
|
|
107
|
+
this.workspace.classList.toggle('detail-drawer', this.modal);
|
|
108
|
+
this.workspace.classList.toggle('inspector-open', this.open);
|
|
109
|
+
this.divider.hidden = !this.open || this.modal;
|
|
110
|
+
this.divider.setAttribute('aria-valuemin', String(MIN_DETAIL_WIDTH));
|
|
111
|
+
this.divider.setAttribute('aria-valuemax', String(Math.max(MIN_DETAIL_WIDTH, Math.min(MAX_DETAIL_WIDTH, available - MIN_MAIN_WIDTH - DIVIDER_WIDTH))));
|
|
112
|
+
this.divider.setAttribute('aria-valuenow', String(Math.round(width)));
|
|
113
|
+
this.backdrop.hidden = !this.open || !this.modal;
|
|
114
|
+
this.main.inert = this.open && this.modal;
|
|
115
|
+
this.chrome.inert = this.open && this.modal;
|
|
116
|
+
for (const sibling of this.chrome.parentElement?.children ?? []) if (sibling !== this.workspace) (sibling as HTMLElement).inert = this.open && this.modal;
|
|
117
|
+
if (this.open && this.modal) {
|
|
118
|
+
this.aside.setAttribute('role', 'dialog');
|
|
119
|
+
this.aside.setAttribute('aria-modal', 'true');
|
|
120
|
+
if (!this.aside.contains(document.activeElement)) this.focusable()[0]?.focus();
|
|
121
|
+
} else {
|
|
122
|
+
this.aside.removeAttribute('role');
|
|
123
|
+
this.aside.removeAttribute('aria-modal');
|
|
124
|
+
}
|
|
125
|
+
if (wasModal !== this.modal && this.open) this.workspace.dispatchEvent(new Event('detail-layout-change'));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private focusable(): HTMLElement[] {
|
|
129
|
+
return Array.from(this.aside.querySelectorAll<HTMLElement>('button:not(:disabled), input:not(:disabled), select:not(:disabled), summary, [tabindex="0"]'))
|
|
130
|
+
.filter((element) => {
|
|
131
|
+
if (element.closest('[hidden]') || element.tabIndex < 0 && element.tagName !== 'SUMMARY') return false;
|
|
132
|
+
for (let parent = element.parentElement; parent && parent !== this.aside; parent = parent.parentElement) {
|
|
133
|
+
if (parent.tagName === 'DETAILS' && !parent.hasAttribute('open') && !parent.querySelector(':scope > summary')?.contains(element)) return false;
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private readonly handleKey = (event: KeyboardEvent): void => {
|
|
140
|
+
if (event.defaultPrevented || !this.open) return;
|
|
141
|
+
if (event.key === 'Escape') {
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
event.stopPropagation();
|
|
144
|
+
this.onClose();
|
|
145
|
+
} else if (this.modal && event.key === 'Tab') {
|
|
146
|
+
const items = this.focusable();
|
|
147
|
+
if (!items.length) { event.preventDefault(); return; }
|
|
148
|
+
const index = items.indexOf(document.activeElement as HTMLElement);
|
|
149
|
+
if (event.shiftKey && index <= 0 || !event.shiftKey && (index === items.length - 1 || index < 0)) {
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
(event.shiftKey ? items.at(-1) : items[0])?.focus();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const PANEL_DETAIL_CSS = `
|
|
2
|
+
.zenfg-inspector-detail-actions,
|
|
3
|
+
.zenfg-inspector-relation-links,
|
|
4
|
+
.zenfg-inspector-copy-id,
|
|
5
|
+
.zenfg-inspector-raw-toolbar {
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
flex-wrap: wrap;
|
|
9
|
+
gap: 6px 10px;
|
|
10
|
+
min-width: 0;
|
|
11
|
+
}
|
|
12
|
+
.zenfg-inspector-detail-actions { margin-bottom: var(--fgd-space-3); }
|
|
13
|
+
.zenfg-inspector-detail-actions:empty { display: none; }
|
|
14
|
+
.zenfg-inspector-inline-action,
|
|
15
|
+
.zenfg-inspector-copy-id button,
|
|
16
|
+
.zenfg-inspector-raw-toolbar button,
|
|
17
|
+
.zenfg-inspector-capture-summary section > button,
|
|
18
|
+
.zenfg-inspector-graph-search-results > button {
|
|
19
|
+
padding: 5px var(--fgd-space-2); border: 1px solid var(--fgd-border); border-radius: var(--fgd-radius-sm);
|
|
20
|
+
color: var(--fgd-text-secondary); background: var(--fgd-surface);
|
|
21
|
+
font: var(--fgd-font-size)/1.4 var(--fgd-font-ui); cursor: pointer;
|
|
22
|
+
}
|
|
23
|
+
.zenfg-inspector-inline-action:hover,
|
|
24
|
+
.zenfg-inspector-copy-id button:hover,
|
|
25
|
+
.zenfg-inspector-raw-toolbar button:hover,
|
|
26
|
+
.zenfg-inspector-capture-summary section > button:hover,
|
|
27
|
+
.zenfg-inspector-graph-search-results > button:hover { color: var(--fgd-text); background: var(--fgd-surface-hover); }
|
|
28
|
+
.zenfg-inspector-raw-toolbar input {
|
|
29
|
+
padding: 6px var(--fgd-space-2); border: 1px solid var(--fgd-border); border-radius: var(--fgd-radius-sm);
|
|
30
|
+
color: var(--fgd-text); background: var(--fgd-panel); font: var(--fgd-font-size) var(--fgd-font-ui);
|
|
31
|
+
}
|
|
32
|
+
.zenfg-inspector-inspector-summary dd,
|
|
33
|
+
.zenfg-inspector-relation-entry,
|
|
34
|
+
.zenfg-inspector-detail-diagnostics { min-width: 0; overflow-wrap: anywhere; }
|
|
35
|
+
.zenfg-inspector-inspector-summary { grid-template-columns: 108px minmax(0, 1fr); font-size: var(--fgd-font-size-small); }
|
|
36
|
+
.zenfg-inspector-inspector-summary dd { font-family: var(--fgd-font-ui); }
|
|
37
|
+
.zenfg-inspector-inspector-relations .zenfg-inspector-relation-button,
|
|
38
|
+
.zenfg-inspector-relation-entry > span { font: var(--fgd-font-size)/1.5 var(--fgd-font-ui); }
|
|
39
|
+
.zenfg-inspector-relation-links .zenfg-inspector-relation-button { flex: 1 1 130px; min-width: 0; }
|
|
40
|
+
.zenfg-inspector-relation-links .zenfg-inspector-inline-action { flex: 0 0 auto; }
|
|
41
|
+
.zenfg-inspector-copy-id code { overflow-wrap: anywhere; }
|
|
42
|
+
.zenfg-inspector-inspector-content > details { margin-top: 14px; }
|
|
43
|
+
.zenfg-inspector-inspector-content summary { cursor: pointer; }
|
|
44
|
+
.zenfg-inspector-inspector-content h3 { font-size: var(--fgd-font-size-small); margin: 14px 0 6px; }
|
|
45
|
+
.zenfg-inspector-detail-diagnostics article { padding: var(--fgd-space-2) 0; border-bottom: 1px solid var(--fgd-border); }
|
|
46
|
+
.zenfg-inspector-detail-diagnostics p { margin: 6px 0; white-space: pre-wrap; }
|
|
47
|
+
.zenfg-inspector-detail-diagnostics article[data-severity="error"] > strong { color: var(--fgd-danger); }
|
|
48
|
+
.zenfg-inspector-detail-diagnostics article[data-severity="warning"] > strong { color: var(--fgd-warning); }
|
|
49
|
+
.zenfg-inspector-raw-view { min-width: 0; display: grid; gap: 10px; }
|
|
50
|
+
.zenfg-inspector-raw-path { overflow-wrap: anywhere; font-size: var(--fgd-font-size-small); }
|
|
51
|
+
.zenfg-inspector-raw-toolbar input { flex: 1 1 170px; min-width: 0; }
|
|
52
|
+
.zenfg-inspector-inspector .zenfg-inspector-raw-detail { white-space: normal; overflow: visible; overflow-wrap: anywhere; font: var(--fgd-font-size-small)/1.55 var(--fgd-font-mono); }
|
|
53
|
+
.zenfg-inspector-raw-children { padding-left: var(--fgd-space-3); border-left: 1px solid var(--fgd-border); }
|
|
54
|
+
.zenfg-inspector-raw-detail summary, .zenfg-inspector-raw-leaf { padding: 3px 0; }
|
|
55
|
+
.zenfg-inspector-inspector-content :is(button, input, summary):focus-visible { outline: 2px solid var(--fgd-accent); outline-offset: 2px; }
|
|
56
|
+
`;
|