@seed-ship/mcp-ui-solid 6.8.1 → 6.9.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/CHANGELOG.md +49 -0
- package/dist/components/ChartJSRenderer.cjs +27 -13
- package/dist/components/ChartJSRenderer.cjs.map +1 -1
- package/dist/components/ChartJSRenderer.d.ts.map +1 -1
- package/dist/components/ChartJSRenderer.js +28 -14
- package/dist/components/ChartJSRenderer.js.map +1 -1
- package/dist/components/DegradedFallback.cjs +73 -0
- package/dist/components/DegradedFallback.cjs.map +1 -0
- package/dist/components/DegradedFallback.d.ts +37 -0
- package/dist/components/DegradedFallback.d.ts.map +1 -0
- package/dist/components/DegradedFallback.js +73 -0
- package/dist/components/DegradedFallback.js.map +1 -0
- package/dist/components/GraphRenderer.cjs +30 -15
- package/dist/components/GraphRenderer.cjs.map +1 -1
- package/dist/components/GraphRenderer.d.ts.map +1 -1
- package/dist/components/GraphRenderer.js +31 -16
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/MapRenderer.cjs +128 -107
- package/dist/components/MapRenderer.cjs.map +1 -1
- package/dist/components/MapRenderer.d.ts.map +1 -1
- package/dist/components/MapRenderer.js +129 -108
- package/dist/components/MapRenderer.js.map +1 -1
- package/dist/index.cjs +4 -4
- package/dist/index.js +1 -1
- package/dist/services/validation.cjs +43 -9
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +43 -9
- package/dist/services/validation.js.map +1 -1
- package/dist/utils/degraded-projections.cjs +87 -0
- package/dist/utils/degraded-projections.cjs.map +1 -0
- package/dist/utils/degraded-projections.d.ts +64 -0
- package/dist/utils/degraded-projections.d.ts.map +1 -0
- package/dist/utils/degraded-projections.js +87 -0
- package/dist/utils/degraded-projections.js.map +1 -0
- package/package.json +1 -1
- package/src/components/ChartJSRenderer.tsx +94 -85
- package/src/components/DegradedFallback.test.tsx +61 -0
- package/src/components/DegradedFallback.tsx +93 -0
- package/src/components/GraphRenderer.tsx +26 -4
- package/src/components/MapRenderer.tsx +446 -392
- package/src/services/validation.test.ts +298 -232
- package/src/services/validation.ts +210 -136
- package/src/utils/degraded-projections.test.ts +113 -0
- package/src/utils/degraded-projections.ts +149 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { getNextElement, template, getNextMarker, insert, createComponent } from "solid-js/web";
|
|
2
|
+
import { Show, For } from "solid-js";
|
|
3
|
+
var _tmpl$ = /* @__PURE__ */ template(`<div class="mt-2 max-h-64 overflow-auto rounded border border-amber-200 dark:border-amber-800"><table class="w-full border-collapse text-left text-xs"><thead class="sticky top-0 bg-amber-100 dark:bg-amber-900/40"><tr></tr></thead><tbody>`), _tmpl$2 = /* @__PURE__ */ template(`<p class="mt-1 text-[10px] text-amber-600 dark:text-amber-400">+<!$><!/> more rows not shown.`), _tmpl$3 = /* @__PURE__ */ template(`<div class="w-full rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-800 dark:bg-amber-900/20"role=alert><p class="text-sm font-medium text-amber-900 dark:text-amber-100"></p><p class="mt-0.5 text-xs text-amber-700 dark:text-amber-300"></p><!$><!/>`), _tmpl$4 = /* @__PURE__ */ template(`<th class="px-2 py-1 font-medium text-amber-900 dark:text-amber-100">`), _tmpl$5 = /* @__PURE__ */ template(`<tr class="border-t border-amber-100 dark:border-amber-800/60">`), _tmpl$6 = /* @__PURE__ */ template(`<td class="px-2 py-1 text-amber-800 dark:text-amber-200">`);
|
|
4
|
+
const DegradedFallback = (props) => {
|
|
5
|
+
const maxRows = () => props.maxRows ?? 50;
|
|
6
|
+
const allRows = () => props.rows ?? [];
|
|
7
|
+
const shownRows = () => allRows().slice(0, maxRows());
|
|
8
|
+
const hiddenCount = () => Math.max(0, allRows().length - shownRows().length);
|
|
9
|
+
const hasTable = () => {
|
|
10
|
+
var _a;
|
|
11
|
+
return (((_a = props.columns) == null ? void 0 : _a.length) ?? 0) > 0 && allRows().length > 0;
|
|
12
|
+
};
|
|
13
|
+
return (() => {
|
|
14
|
+
var _el$ = getNextElement(_tmpl$3), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$12 = _el$3.nextSibling, [_el$13, _co$2] = getNextMarker(_el$12.nextSibling);
|
|
15
|
+
insert(_el$2, () => props.message);
|
|
16
|
+
insert(_el$3, () => props.caption ?? "Showing the underlying data — the interactive view is unavailable.");
|
|
17
|
+
insert(_el$, createComponent(Show, {
|
|
18
|
+
get when() {
|
|
19
|
+
return hasTable();
|
|
20
|
+
},
|
|
21
|
+
get children() {
|
|
22
|
+
return [(() => {
|
|
23
|
+
var _el$4 = getNextElement(_tmpl$), _el$5 = _el$4.firstChild, _el$6 = _el$5.firstChild, _el$7 = _el$6.firstChild, _el$8 = _el$6.nextSibling;
|
|
24
|
+
insert(_el$7, createComponent(For, {
|
|
25
|
+
get each() {
|
|
26
|
+
return props.columns;
|
|
27
|
+
},
|
|
28
|
+
children: (col) => (() => {
|
|
29
|
+
var _el$14 = getNextElement(_tmpl$4);
|
|
30
|
+
insert(_el$14, col);
|
|
31
|
+
return _el$14;
|
|
32
|
+
})()
|
|
33
|
+
}));
|
|
34
|
+
insert(_el$8, createComponent(For, {
|
|
35
|
+
get each() {
|
|
36
|
+
return shownRows();
|
|
37
|
+
},
|
|
38
|
+
children: (row) => (() => {
|
|
39
|
+
var _el$15 = getNextElement(_tmpl$5);
|
|
40
|
+
insert(_el$15, createComponent(For, {
|
|
41
|
+
get each() {
|
|
42
|
+
return props.columns;
|
|
43
|
+
},
|
|
44
|
+
children: (_col, i) => (() => {
|
|
45
|
+
var _el$16 = getNextElement(_tmpl$6);
|
|
46
|
+
insert(_el$16, () => String(row[i()] ?? ""));
|
|
47
|
+
return _el$16;
|
|
48
|
+
})()
|
|
49
|
+
}));
|
|
50
|
+
return _el$15;
|
|
51
|
+
})()
|
|
52
|
+
}));
|
|
53
|
+
return _el$4;
|
|
54
|
+
})(), createComponent(Show, {
|
|
55
|
+
get when() {
|
|
56
|
+
return hiddenCount() > 0;
|
|
57
|
+
},
|
|
58
|
+
get children() {
|
|
59
|
+
var _el$9 = getNextElement(_tmpl$2), _el$0 = _el$9.firstChild, _el$10 = _el$0.nextSibling, [_el$11, _co$] = getNextMarker(_el$10.nextSibling);
|
|
60
|
+
_el$11.nextSibling;
|
|
61
|
+
insert(_el$9, hiddenCount, _el$11, _co$);
|
|
62
|
+
return _el$9;
|
|
63
|
+
}
|
|
64
|
+
})];
|
|
65
|
+
}
|
|
66
|
+
}), _el$13, _co$2);
|
|
67
|
+
return _el$;
|
|
68
|
+
})();
|
|
69
|
+
};
|
|
70
|
+
export {
|
|
71
|
+
DegradedFallback
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=DegradedFallback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DegradedFallback.js","sources":["../../src/components/DegradedFallback.tsx"],"sourcesContent":["/**\n * DegradedFallback — the middle rung of the renderer fallback ladder\n * (audit 2026-05-30, P2.5).\n *\n * Each heavy renderer (graph / map / chart) follows the same contract:\n * 1. native render when its peer lib is available and succeeds;\n * 2. **degraded but useful** view when the native render throws — this\n * component: a visible notice + a plain data table so the user still\n * sees the underlying data instead of a blank space;\n * 3. (the caller also emits a `component:error` telemetry event).\n *\n * Pure / presentational — no peer deps, no side effects — so a render-path\n * failure in a heavy lib can never cascade into the fallback itself, and it\n * is trivially unit-testable. Rows/cells are rendered as text; callers are\n * responsible for stringifying complex cell values.\n */\n\nimport { Component, For, Show } from 'solid-js';\n\nexport interface DegradedFallbackProps {\n /** Short, human-readable reason the native render was skipped/failed. */\n message: string;\n /**\n * Column headers for the degraded data table. When omitted (or empty),\n * only the notice banner is shown.\n */\n columns?: string[];\n /** Row data — each row is an array of cells aligned to `columns`. */\n rows?: Array<Array<string | number>>;\n /**\n * Caption under the table. Defaults to a generic\n * \"interactive view unavailable\" line.\n */\n caption?: string;\n /** Max rows to render before truncating (default 50). */\n maxRows?: number;\n}\n\nexport const DegradedFallback: Component<DegradedFallbackProps> = (props) => {\n const maxRows = () => props.maxRows ?? 50;\n const allRows = () => props.rows ?? [];\n const shownRows = () => allRows().slice(0, maxRows());\n const hiddenCount = () => Math.max(0, allRows().length - shownRows().length);\n const hasTable = () => (props.columns?.length ?? 0) > 0 && allRows().length > 0;\n\n return (\n <div\n class=\"w-full rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-800 dark:bg-amber-900/20\"\n role=\"alert\"\n >\n <p class=\"text-sm font-medium text-amber-900 dark:text-amber-100\">{props.message}</p>\n <p class=\"mt-0.5 text-xs text-amber-700 dark:text-amber-300\">\n {props.caption ?? 'Showing the underlying data — the interactive view is unavailable.'}\n </p>\n\n <Show when={hasTable()}>\n <div class=\"mt-2 max-h-64 overflow-auto rounded border border-amber-200 dark:border-amber-800\">\n <table class=\"w-full border-collapse text-left text-xs\">\n <thead class=\"sticky top-0 bg-amber-100 dark:bg-amber-900/40\">\n <tr>\n <For each={props.columns}>\n {(col) => (\n <th class=\"px-2 py-1 font-medium text-amber-900 dark:text-amber-100\">{col}</th>\n )}\n </For>\n </tr>\n </thead>\n <tbody>\n <For each={shownRows()}>\n {(row) => (\n <tr class=\"border-t border-amber-100 dark:border-amber-800/60\">\n <For each={props.columns}>\n {(_col, i) => (\n <td class=\"px-2 py-1 text-amber-800 dark:text-amber-200\">\n {String(row[i()] ?? '')}\n </td>\n )}\n </For>\n </tr>\n )}\n </For>\n </tbody>\n </table>\n </div>\n <Show when={hiddenCount() > 0}>\n <p class=\"mt-1 text-[10px] text-amber-600 dark:text-amber-400\">\n +{hiddenCount()} more rows not shown.\n </p>\n </Show>\n </Show>\n </div>\n );\n};\n"],"names":["DegradedFallback","props","maxRows","allRows","rows","shownRows","slice","hiddenCount","Math","max","length","hasTable","columns","_el$","_$getNextElement","_tmpl$3","_el$2","firstChild","_el$3","nextSibling","_el$12","_el$13","_co$2","_$getNextMarker","_$insert","message","caption","_$createComponent","Show","when","children","_el$4","_tmpl$","_el$5","_el$6","_el$7","_el$8","For","each","col","_el$14","_tmpl$4","row","_el$15","_tmpl$5","_col","i","_el$16","_tmpl$6","String","_el$9","_tmpl$2","_el$0","_el$10","_el$11","_co$"],"mappings":";;;AAsCO,MAAMA,mBAAsDC,CAAAA,UAAU;AAC3E,QAAMC,UAAUA,MAAMD,MAAMC,WAAW;AACvC,QAAMC,UAAUA,MAAMF,MAAMG,QAAQ,CAAA;AACpC,QAAMC,YAAYA,MAAMF,QAAAA,EAAUG,MAAM,GAAGJ,SAAS;AACpD,QAAMK,cAAcA,MAAMC,KAAKC,IAAI,GAAGN,UAAUO,SAASL,UAAAA,EAAYK,MAAM;AAC3E,QAAMC,WAAWA,MAAAA;;AAAOV,yBAAMW,YAANX,mBAAeS,WAAU,KAAK,KAAKP,UAAUO,SAAS;AAAA;AAE9E,UAAA,MAAA;AAAA,QAAAG,OAAAC,eAAAC,OAAA,GAAAC,QAAAH,KAAAI,YAAAC,QAAAF,MAAAG,aAAAC,SAAAF,MAAAC,aAAA,CAAAE,QAAAC,KAAA,IAAAC,cAAAH,OAAAD,WAAA;AAAAK,WAAAR,OAAA,MAKuEf,MAAMwB,OAAO;AAAAD,WAAAN,OAAA,MAE7EjB,MAAMyB,WAAW,oEAAoE;AAAAF,WAAAX,MAAAc,gBAGvFC,MAAI;AAAA,MAAA,IAACC,OAAI;AAAA,eAAElB,SAAAA;AAAAA,MAAU;AAAA,MAAA,IAAAmB,WAAA;AAAA,eAAA,EAAA,MAAA;AAAA,cAAAC,QAAAjB,eAAAkB,MAAA,GAAAC,QAAAF,MAAAd,YAAAiB,QAAAD,MAAAhB,YAAAkB,QAAAD,MAAAjB,YAAAmB,QAAAF,MAAAf;AAAAK,iBAAAW,OAAAR,gBAKXU,KAAG;AAAA,YAAA,IAACC,OAAI;AAAA,qBAAErC,MAAMW;AAAAA,YAAO;AAAA,YAAAkB,UACpBS,UAAG,MAAA;AAAA,kBAAAC,SAAA1B,eAAA2B,OAAA;AAAAjB,qBAAAgB,QACmED,GAAG;AAAA,qBAAAC;AAAAA,YAAA,GAAA;AAAA,UAAA,CAC1E,CAAA;AAAAhB,iBAAAY,OAAAT,gBAKJU,KAAG;AAAA,YAAA,IAACC,OAAI;AAAA,qBAAEjC,UAAAA;AAAAA,YAAW;AAAA,YAAAyB,UAClBY,UAAG,MAAA;AAAA,kBAAAC,SAAA7B,eAAA8B,OAAA;AAAApB,qBAAAmB,QAAAhB,gBAEAU,KAAG;AAAA,gBAAA,IAACC,OAAI;AAAA,yBAAErC,MAAMW;AAAAA,gBAAO;AAAA,gBAAAkB,UACrBA,CAACe,MAAMC,OAAC,MAAA;AAAA,sBAAAC,SAAAjC,eAAAkC,OAAA;AAAAxB,yBAAAuB,QAAA,MAEJE,OAAOP,IAAII,GAAG,KAAK,EAAE,CAAC;AAAA,yBAAAC;AAAAA,gBAAA,GAAA;AAAA,cAAA,CAE1B,CAAA;AAAA,qBAAAJ;AAAAA,YAAA,GAAA;AAAA,UAAA,CAGN,CAAA;AAAA,iBAAAZ;AAAAA,QAAA,GAAA,GAAAJ,gBAKRC,MAAI;AAAA,UAAA,IAACC,OAAI;AAAA,mBAAEtB,gBAAgB;AAAA,UAAC;AAAA,UAAA,IAAAuB,WAAA;AAAA,gBAAAoB,QAAApC,eAAAqC,OAAA,GAAAC,QAAAF,MAAAjC,YAAAoC,SAAAD,MAAAjC,aAAA,CAAAmC,QAAAC,IAAA,IAAAhC,cAAA8B,OAAAlC,WAAA;AAAAmC,mBAAAnC;AAAAK,mBAAA0B,OAEvB3C,aAAW+C,QAAAC,IAAA;AAAA,mBAAAL;AAAAA,UAAA;AAAA,QAAA,CAAA,CAAA;AAAA,MAAA;AAAA,IAAA,CAAA,GAAA7B,QAAAC,KAAA;AAAA,WAAAT;AAAAA,EAAA,GAAA;AAMzB;"}
|
|
@@ -4,7 +4,10 @@ const web = require("solid-js/web");
|
|
|
4
4
|
const solidJs = require("solid-js");
|
|
5
5
|
const ExpandableWrapper = require("./ExpandableWrapper.cjs");
|
|
6
6
|
const PortalDropdownMenu = require("./PortalDropdownMenu.cjs");
|
|
7
|
-
|
|
7
|
+
const DegradedFallback = require("./DegradedFallback.cjs");
|
|
8
|
+
const degradedProjections = require("../utils/degraded-projections.cjs");
|
|
9
|
+
const MCPUITelemetryContext = require("../context/MCPUITelemetryContext.cjs");
|
|
10
|
+
var _tmpl$ = /* @__PURE__ */ web.template(`<div><div class="absolute right-2 top-2 z-10"><button type=button class="px-2 py-1 text-xs bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"title="Export graph"aria-label="Export graph"aria-haspopup=menu>Export ▾</button><!$><!/></div><div></div><!$><!/>`), _tmpl$2 = /* @__PURE__ */ web.template(`<div class="w-full p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg"><p class="text-sm font-medium text-yellow-900 dark:text-yellow-100">Graph rendering unavailable</p><p class="text-xs text-yellow-700 dark:text-yellow-300 mt-1">Install <code>@antv/g6</code> peer dependency to render <code>type: "graph"</code> components.`), _tmpl$3 = /* @__PURE__ */ web.template(`<div class="w-full p-4 bg-gray-50 dark:bg-gray-800/40 border border-gray-200 dark:border-gray-700 rounded-lg animate-pulse"><div class="h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-2"></div><div class="h-3 w-48 bg-gray-200 dark:bg-gray-700 rounded">`), _tmpl$4 = /* @__PURE__ */ web.template(`<button type=button class="w-full text-left px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-gray-700 last:border-b-0"><div class=font-medium></div><div class="text-[10px] text-gray-500 dark:text-gray-400">`);
|
|
8
11
|
let g6ModulePromise;
|
|
9
12
|
async function isG6Available() {
|
|
10
13
|
try {
|
|
@@ -94,6 +97,7 @@ function downloadBlob(content, filename, mimeType) {
|
|
|
94
97
|
const GraphRenderer = (props) => {
|
|
95
98
|
const params = () => props.component.params;
|
|
96
99
|
const isExpanded = ExpandableWrapper.useExpanded();
|
|
100
|
+
const telemetry = MCPUITelemetryContext.useTelemetry();
|
|
97
101
|
const [available, setAvailable] = solidJs.createSignal(null);
|
|
98
102
|
const [error, setError] = solidJs.createSignal();
|
|
99
103
|
const [exportMenuOpen, setExportMenuOpen] = solidJs.createSignal(false);
|
|
@@ -139,7 +143,15 @@ const GraphRenderer = (props) => {
|
|
|
139
143
|
graphInstance = new Graph(config);
|
|
140
144
|
await graphInstance.render();
|
|
141
145
|
} catch (err) {
|
|
142
|
-
|
|
146
|
+
const message = err instanceof Error ? err.message : "Failed to render graph";
|
|
147
|
+
setError(message);
|
|
148
|
+
telemetry == null ? void 0 : telemetry.dispatch({
|
|
149
|
+
type: "render:error",
|
|
150
|
+
errorMessage: message,
|
|
151
|
+
id: props.component.id ?? "",
|
|
152
|
+
componentType: "graph",
|
|
153
|
+
ts: Date.now()
|
|
154
|
+
});
|
|
143
155
|
}
|
|
144
156
|
});
|
|
145
157
|
solidJs.onCleanup(() => {
|
|
@@ -190,11 +202,11 @@ const GraphRenderer = (props) => {
|
|
|
190
202
|
get fallback() {
|
|
191
203
|
return (
|
|
192
204
|
// Loading skeleton while we determine peer availability
|
|
193
|
-
web.getNextElement(_tmpl$
|
|
205
|
+
web.getNextElement(_tmpl$3)
|
|
194
206
|
);
|
|
195
207
|
},
|
|
196
208
|
get children() {
|
|
197
|
-
return web.getNextElement(_tmpl$
|
|
209
|
+
return web.getNextElement(_tmpl$2);
|
|
198
210
|
}
|
|
199
211
|
});
|
|
200
212
|
},
|
|
@@ -211,7 +223,7 @@ const GraphRenderer = (props) => {
|
|
|
211
223
|
return props.toolbarVariant;
|
|
212
224
|
},
|
|
213
225
|
get children() {
|
|
214
|
-
var _el$ = web.getNextElement(_tmpl$
|
|
226
|
+
var _el$ = web.getNextElement(_tmpl$), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling, [_el$5, _co$] = web.getNextMarker(_el$4.nextSibling), _el$6 = _el$2.nextSibling, _el$7 = _el$6.nextSibling, [_el$8, _co$2] = web.getNextMarker(_el$7.nextSibling);
|
|
215
227
|
_el$3.$$click = () => setExportMenuOpen((v) => !v);
|
|
216
228
|
var _ref$ = exportTriggerRef;
|
|
217
229
|
typeof _ref$ === "function" ? web.use(_ref$, _el$3) : exportTriggerRef = _el$3;
|
|
@@ -239,12 +251,12 @@ const GraphRenderer = (props) => {
|
|
|
239
251
|
hint: "raw data"
|
|
240
252
|
}],
|
|
241
253
|
children: (item) => (() => {
|
|
242
|
-
var _el$
|
|
243
|
-
web.addEventListener(_el$
|
|
244
|
-
web.insert(_el$
|
|
245
|
-
web.insert(_el$
|
|
254
|
+
var _el$1 = web.getNextElement(_tmpl$4), _el$10 = _el$1.firstChild, _el$11 = _el$10.nextSibling;
|
|
255
|
+
web.addEventListener(_el$1, "click", item.onClick, true);
|
|
256
|
+
web.insert(_el$10, () => item.label);
|
|
257
|
+
web.insert(_el$11, () => item.hint);
|
|
246
258
|
web.runHydrationEvents();
|
|
247
|
-
return _el$
|
|
259
|
+
return _el$1;
|
|
248
260
|
})()
|
|
249
261
|
});
|
|
250
262
|
}
|
|
@@ -256,13 +268,16 @@ const GraphRenderer = (props) => {
|
|
|
256
268
|
return error();
|
|
257
269
|
},
|
|
258
270
|
get children() {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
271
|
+
return web.createComponent(DegradedFallback.DegradedFallback, web.mergeProps({
|
|
272
|
+
get message() {
|
|
273
|
+
return `Graph rendering failed: ${error()}`;
|
|
274
|
+
},
|
|
275
|
+
caption: "Showing the graph data as a table — the interactive view is unavailable."
|
|
276
|
+
}, () => degradedProjections.graphToDegradedTable(params())));
|
|
262
277
|
}
|
|
263
|
-
}), _el$
|
|
278
|
+
}), _el$8, _co$2);
|
|
264
279
|
web.effect((_p$) => {
|
|
265
|
-
var _v$ = `relative w-full ${params().className ?? ""} ${isExpanded() ? "flex-1 min-h-0 flex flex-col" : ""}`, _v$2 = exportMenuOpen(), _v$3 = `bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${isExpanded() ? "flex-1 min-h-0" : ""}`, _v$4 = isExpanded() ? `height: 100%; width: ${params().width ?? "100%"};` : `height: ${params().height ?? "400px"}; width: ${params().width ?? "100%"};`;
|
|
280
|
+
var _v$ = `relative w-full ${params().className ?? ""} ${isExpanded() ? "flex-1 min-h-0 flex flex-col" : ""}`, _v$2 = exportMenuOpen(), _v$3 = `bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${error() ? "hidden" : ""} ${isExpanded() ? "flex-1 min-h-0" : ""}`, _v$4 = isExpanded() ? `height: 100%; width: ${params().width ?? "100%"};` : `height: ${params().height ?? "400px"}; width: ${params().width ?? "100%"};`;
|
|
266
281
|
_v$ !== _p$.e && web.className(_el$, _p$.e = _v$);
|
|
267
282
|
_v$2 !== _p$.t && web.setAttribute(_el$3, "aria-expanded", _p$.t = _v$2);
|
|
268
283
|
_v$3 !== _p$.a && web.className(_el$6, _p$.a = _v$3);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GraphRenderer.cjs","sources":["../../src/components/GraphRenderer.tsx"],"sourcesContent":["/**\n * GraphRenderer (v6.0.0) — generic node-link visualization powered by\n * `@antv/g6 ^5` (peer-optional). Same lazy-load pattern as\n * `ChartJSRenderer` and `MapRenderer` : the heavy lib is dynamically\n * imported only on first mount, and apps that don't install the peer\n * see an informative fallback instead of a crash.\n *\n * Spec : `@seed-ship/mcp-ui-spec@5.0.4` exports `GraphComponentParamsSchema`,\n * `GraphNode`, `GraphEdge`, `GraphLayoutName`, `GraphLayout`,\n * `GraphComponentParams` — the shape consumed here. Domain semantics\n * (`weight` etc.) are opaque to this renderer ; consumers decide what\n * the values mean.\n *\n * Copy + export : the renderer ships with `<ExpandableWrapper>` (default\n * copy = JSON of `{nodes, edges}`) plus a 3-format export menu — **PNG**\n * (visual snapshot via the underlying canvas/SVG), **Mermaid** (markdown\n * / GitHub-renderable `flowchart` syntax), **JSON** (raw reimportable\n * data). All three are computed lazily on click.\n */\n\nimport { Component, createSignal, onCleanup, onMount, Show, For } from 'solid-js';\nimport type { UIComponent } from '../types';\nimport type {\n GraphComponentParams,\n GraphLayout,\n GraphNode,\n GraphEdge,\n} from '@seed-ship/mcp-ui-spec';\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper';\nimport { PortalDropdownMenu } from './PortalDropdownMenu';\n\n// Module-scoped lazy import promise — first call triggers the dynamic\n// import, subsequent calls reuse the resolved module.\nlet g6ModulePromise: Promise<typeof import('@antv/g6')> | undefined;\n\n/**\n * Whether the `@antv/g6` peer dependency is installed and importable.\n * Resolves to `true` when the lib is available, `false` otherwise.\n *\n * Mirrors `isChartJSAvailable()` from `ChartJSRenderer`.\n */\nexport async function isG6Available(): Promise<boolean> {\n try {\n if (!g6ModulePromise) {\n g6ModulePromise = import('@antv/g6');\n }\n await g6ModulePromise;\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve the spec layout shorthand or object form into the config object\n * G6 v5 expects. When `layout` is omitted, picks `'force'` if edges are\n * present (universal default) or `'circular'` otherwise.\n */\nfunction resolveLayout(params: GraphComponentParams): { type: string; [key: string]: unknown } {\n const layout: GraphLayout | undefined = params.layout;\n if (layout === undefined) {\n const hasEdges = (params.edges?.length ?? 0) > 0;\n return { type: hasEdges ? 'force' : 'circular' };\n }\n if (typeof layout === 'string') {\n return { type: layout };\n }\n // Object form: spread the passthrough options alongside `type`.\n return { type: layout.type, ...(layout.options ?? {}) };\n}\n\n/**\n * Build the G6 v5 `Graph` constructor config from the component params.\n *\n * Pure (no DOM/lib side effects beyond reading `container`), so it can be\n * unit-tested directly without a jsdom render — this is the contract the\n * 2026-05-30 audit (P0.2) wants locked.\n *\n * ⚠️ G6 v5's `renderer` is a factory `(layer) => IRenderer`, NOT a string\n * (that was the v4 contract). Passing the string `'canvas'` / `'svg'` makes\n * G6 throw `renderer is not a function` — and because the default path used\n * to pass the string `'canvas'`, EVERY graph crashed, not just svg. So we\n * **omit `renderer` entirely**: G6 then uses its built-in canvas renderer\n * (documented default `() => new CanvasRenderer()`).\n *\n * `rendererPref: 'svg'` is reserved but NOT wired yet — a real G6 v5 SVG\n * renderer needs the `@antv/g-svg` factory (a transitive dep of `@antv/g6`,\n * not statically resolvable at build time). Until that's wired behind proper\n * optional-peer resolution, svg degrades to the canvas default with a\n * one-time warning. It must NEVER inject a string `renderer`.\n */\nexport function buildGraphConfig(\n p: GraphComponentParams,\n container?: HTMLElement\n): Record<string, unknown> {\n const config: Record<string, unknown> = {\n container,\n data: { nodes: p.nodes, edges: p.edges ?? [] },\n layout: resolveLayout(p),\n behaviors: resolveBehaviors(p),\n };\n\n if (p.rendererPref === 'svg') {\n console.warn(\n '[MCP-UI] GraphRenderer: rendererPref \"svg\" is not yet supported; using the default canvas renderer.'\n );\n }\n\n if (p.fitView !== false) {\n config.autoFit = 'view';\n }\n\n if (p.tooltip !== false) {\n config.plugins = [\n {\n type: 'tooltip',\n getContent: (_evt: unknown, items: any[]) => {\n const item = items?.[0];\n if (!item) return '';\n const label = item.label ?? item.id ?? '';\n const data = item.data ? JSON.stringify(item.data) : '';\n return `<div style=\"padding:4px 8px\"><strong>${escapeHtml(String(label))}</strong>${\n data ? `<br><span style=\"font-size:11px;opacity:0.7\">${escapeHtml(data)}</span>` : ''\n }</div>`;\n },\n },\n ];\n }\n\n return config;\n}\n\n/**\n * Build the G6 v5 `behaviors` array from the params interactivity flags.\n * Defaults : drag-canvas + zoom-canvas + drag-element + click-select.\n * Any flag set to `false` opts out.\n */\nfunction resolveBehaviors(params: GraphComponentParams): string[] {\n const behaviors: string[] = [];\n if (params.enableDrag !== false) behaviors.push('drag-element');\n if (params.enableZoom !== false) {\n behaviors.push('zoom-canvas', 'drag-canvas');\n }\n if (params.enableSelect !== false) behaviors.push('click-select');\n return behaviors;\n}\n\n/**\n * Pick a sensible Mermaid `flowchart` direction from the resolved layout.\n * `dagre` / `tree` / `mindmap` are top-down hierarchies → TD ; everything\n * else (force, concentric, circular, grid) → LR (default mermaid).\n */\nfunction mermaidDirection(layoutType: string): 'TD' | 'LR' {\n return layoutType === 'dagre' || layoutType === 'tree' || layoutType === 'mindmap' ? 'TD' : 'LR';\n}\n\n/**\n * Sanitize a string for use inside a Mermaid node label. Mermaid breaks\n * on raw quotes / brackets / pipes ; we strip the worst offenders.\n */\nfunction mermaidLabel(s: string): string {\n return s\n .replace(/[\"[\\]|]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/**\n * Convert the graph data to Mermaid `flowchart` syntax. The edge label\n * carries the optional `weight` prefix when present (e.g. `|3| label`).\n */\nfunction toMermaid(params: GraphComponentParams): string {\n const layoutType = resolveLayout(params).type;\n const dir = mermaidDirection(layoutType);\n const lines: string[] = [`flowchart ${dir}`];\n for (const n of params.nodes) {\n const label = mermaidLabel(n.label ?? n.id);\n lines.push(` ${n.id}[\"${label}\"]`);\n }\n for (const e of params.edges ?? []) {\n const labelParts: string[] = [];\n if (e.weight !== undefined) labelParts.push(String(e.weight));\n if (e.label) labelParts.push(mermaidLabel(e.label));\n const labelText = labelParts.join(' · ');\n if (labelText) {\n lines.push(` ${e.source} -->|${labelText}| ${e.target}`);\n } else {\n lines.push(` ${e.source} --> ${e.target}`);\n }\n }\n return lines.join('\\n');\n}\n\nfunction toJSON(params: GraphComponentParams): string {\n return JSON.stringify({ nodes: params.nodes, edges: params.edges ?? [] }, null, 2);\n}\n\nfunction downloadBlob(content: string | Blob, filename: string, mimeType?: string): void {\n const blob =\n typeof content === 'string' ? new Blob([content], { type: mimeType ?? 'text/plain' }) : content;\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n}\n\nexport interface GraphRendererProps {\n component: UIComponent;\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible';\n}\n\nexport const GraphRenderer: Component<GraphRendererProps> = (props) => {\n const params = () => props.component.params as GraphComponentParams;\n const isExpanded = useExpanded();\n const [available, setAvailable] = createSignal<boolean | null>(null);\n const [error, setError] = createSignal<string | undefined>();\n const [exportMenuOpen, setExportMenuOpen] = createSignal(false);\n let containerRef: HTMLDivElement | undefined;\n // v6.4.0 — trigger ref consumed by <PortalDropdownMenu> for positioning\n let exportTriggerRef: HTMLButtonElement | undefined;\n // Loosely typed because G6 is a peer-optional — we don't pull its\n // types into the bundle just to type a transient local handle.\n let graphInstance: any | undefined;\n\n onMount(async () => {\n const g6Available = await isG6Available();\n setAvailable(g6Available);\n if (!g6Available || !containerRef) return;\n\n try {\n const { Graph } = await g6ModulePromise!;\n const p = params();\n const config: Record<string, unknown> = {\n container: containerRef,\n data: { nodes: p.nodes, edges: p.edges ?? [] },\n layout: resolveLayout(p),\n behaviors: resolveBehaviors(p),\n };\n\n // G6 v5's `renderer` is a factory `(layer) => IRenderer`, NOT a string\n // (that was the v4 contract). Passing the string `'canvas'` / `'svg'`\n // makes G6 throw `renderer is not a function` — and because the default\n // path also passed the string `'canvas'`, EVERY graph crashed, not just\n // the svg one.\n //\n // Fix: omit `renderer` entirely. G6 then uses its built-in canvas\n // renderer (documented default `() => new CanvasRenderer()`), which is\n // what we want for every graph.\n //\n // `rendererPref: 'svg'` is NOT wired yet: a real G6 v5 SVG renderer\n // needs the `@antv/g-svg` factory, which is a *transitive* dep of\n // `@antv/g6` (not a declared peer) and isn't statically resolvable at\n // build time. Rather than ship a fragile/unresolvable import, we treat\n // svg as \"not yet supported\" and fall back to the canvas default with a\n // one-time warning. Tracked for a follow-up that wires the factory\n // behind a proper optional-peer resolution. (Never pass a string here.)\n if (p.rendererPref === 'svg') {\n console.warn(\n '[MCP-UI] GraphRenderer: rendererPref \"svg\" is not yet supported; using the default canvas renderer.'\n );\n }\n if (p.fitView !== false) {\n config.autoFit = 'view';\n }\n if (p.tooltip !== false) {\n // Built-in tooltip plugin — shows label + a compact dump of\n // node.data on hover. Consumers can opt out with `tooltip: false`.\n config.plugins = [\n {\n type: 'tooltip',\n getContent: (_evt: unknown, items: any[]) => {\n const item = items?.[0];\n if (!item) return '';\n const label = item.label ?? item.id ?? '';\n const data = item.data ? JSON.stringify(item.data) : '';\n return `<div style=\"padding:4px 8px\"><strong>${escapeHtml(String(label))}</strong>${\n data\n ? `<br><span style=\"font-size:11px;opacity:0.7\">${escapeHtml(data)}</span>`\n : ''\n }</div>`;\n },\n },\n ];\n }\n graphInstance = new (Graph as any)(config);\n await graphInstance.render();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to render graph');\n }\n });\n\n onCleanup(() => {\n try {\n graphInstance?.destroy();\n } catch {\n // G6 destroy can throw on already-destroyed instances or partial\n // init failures — silent because the component is unmounting anyway.\n }\n graphInstance = undefined;\n });\n\n // ─── Export handlers ────────────────────────────────────────────────\n const handleExportJSON = () => {\n downloadBlob(toJSON(params()), `${graphFilenameStem(params())}.json`, 'application/json');\n setExportMenuOpen(false);\n };\n\n const handleExportMermaid = () => {\n downloadBlob(toMermaid(params()), `${graphFilenameStem(params())}.mmd`, 'text/plain');\n setExportMenuOpen(false);\n };\n\n const handleExportPNG = async () => {\n if (!graphInstance) return;\n try {\n // G6 v5 exposes `toDataURL()` on the graph instance.\n const dataUrl: string = await graphInstance.toDataURL?.('image/png');\n if (!dataUrl) {\n // Fallback: try to grab the underlying canvas directly.\n const canvas = containerRef?.querySelector('canvas');\n if (canvas) {\n const url = (canvas as HTMLCanvasElement).toDataURL('image/png');\n await downloadDataUrl(url, `${graphFilenameStem(params())}.png`);\n } else {\n setError('PNG export not supported in current renderer mode');\n }\n } else {\n await downloadDataUrl(dataUrl, `${graphFilenameStem(params())}.png`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'PNG export failed');\n }\n setExportMenuOpen(false);\n };\n\n return (\n <Show\n when={available() === true}\n fallback={\n <Show\n when={available() === false}\n fallback={\n // Loading skeleton while we determine peer availability\n <div class=\"w-full p-4 bg-gray-50 dark:bg-gray-800/40 border border-gray-200 dark:border-gray-700 rounded-lg animate-pulse\">\n <div class=\"h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-2\" />\n <div class=\"h-3 w-48 bg-gray-200 dark:bg-gray-700 rounded\" />\n </div>\n }\n >\n <div class=\"w-full p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg\">\n <p class=\"text-sm font-medium text-yellow-900 dark:text-yellow-100\">\n Graph rendering unavailable\n </p>\n <p class=\"text-xs text-yellow-700 dark:text-yellow-300 mt-1\">\n Install <code>@antv/g6</code> peer dependency to render <code>type: \"graph\"</code>{' '}\n components.\n </p>\n </div>\n </Show>\n }\n >\n <ExpandableWrapper\n title={params().title ?? 'Graph'}\n copyData={toJSON(params())}\n copyLabel=\"Copy graph (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div\n class={`relative w-full ${params().className ?? ''} ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}\n >\n {/* Export menu — top-right, mirrors TableRenderer's pattern */}\n <div class=\"absolute right-2 top-2 z-10\">\n <button\n ref={exportTriggerRef}\n type=\"button\"\n onClick={() => setExportMenuOpen((v) => !v)}\n class=\"px-2 py-1 text-xs bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300\"\n title=\"Export graph\"\n aria-label=\"Export graph\"\n aria-haspopup=\"menu\"\n aria-expanded={exportMenuOpen()}\n >\n Export ▾\n </button>\n <PortalDropdownMenu\n open={exportMenuOpen()}\n onClose={() => setExportMenuOpen(false)}\n trigger={exportTriggerRef}\n width={176}\n class=\"text-xs\"\n >\n <For\n each={[\n { label: 'Download PNG', onClick: handleExportPNG, hint: 'visual snapshot' },\n {\n label: 'Download Mermaid',\n onClick: handleExportMermaid,\n hint: 'markdown / GitHub',\n },\n { label: 'Download JSON', onClick: handleExportJSON, hint: 'raw data' },\n ]}\n >\n {(item) => (\n <button\n type=\"button\"\n onClick={item.onClick}\n class=\"w-full text-left px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-gray-700 last:border-b-0\"\n >\n <div class=\"font-medium\">{item.label}</div>\n <div class=\"text-[10px] text-gray-500 dark:text-gray-400\">{item.hint}</div>\n </button>\n )}\n </For>\n </PortalDropdownMenu>\n </div>\n\n <div\n ref={containerRef}\n class={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${\n isExpanded() ? 'flex-1 min-h-0' : ''\n }`}\n style={\n isExpanded()\n ? `height: 100%; width: ${params().width ?? '100%'};`\n : `height: ${params().height ?? '400px'}; width: ${params().width ?? '100%'};`\n }\n />\n <Show when={error()}>\n <p class=\"text-xs text-red-600 dark:text-red-400 mt-1\">Render error: {error()}</p>\n </Show>\n </div>\n </ExpandableWrapper>\n </Show>\n );\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────\n\nfunction graphFilenameStem(params: GraphComponentParams): string {\n const base = (params.title ?? 'graph').replace(/[^a-z0-9-_]+/gi, '-').replace(/^-+|-+$/g, '');\n return base || 'graph';\n}\n\nasync function downloadDataUrl(dataUrl: string, filename: string): Promise<void> {\n const res = await fetch(dataUrl);\n const blob = await res.blob();\n downloadBlob(blob, filename);\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => {\n switch (c) {\n case '&':\n return '&';\n case '<':\n return '<';\n case '>':\n return '>';\n case '\"':\n return '"';\n case \"'\":\n return ''';\n default:\n return c;\n }\n });\n}\n\n// Re-export for tests + consumers that want to compose their own export menu\nexport { toMermaid as graphToMermaid, toJSON as graphToJSON };\n"],"names":["g6ModulePromise","isG6Available","resolveLayout","params","layout","undefined","hasEdges","edges","length","type","options","resolveBehaviors","behaviors","enableDrag","push","enableZoom","enableSelect","mermaidDirection","layoutType","mermaidLabel","s","replace","trim","toMermaid","dir","lines","n","nodes","label","id","e","labelParts","weight","String","labelText","join","source","target","toJSON","JSON","stringify","downloadBlob","content","filename","mimeType","blob","Blob","url","URL","createObjectURL","a","document","createElement","href","download","body","appendChild","click","removeChild","revokeObjectURL","GraphRenderer","props","component","isExpanded","useExpanded","available","setAvailable","createSignal","error","setError","exportMenuOpen","setExportMenuOpen","containerRef","exportTriggerRef","graphInstance","onMount","g6Available","Graph","p","config","container","data","rendererPref","console","warn","fitView","autoFit","tooltip","plugins","getContent","_evt","items","item","escapeHtml","render","err","Error","message","onCleanup","destroy","handleExportJSON","graphFilenameStem","handleExportMermaid","handleExportPNG","dataUrl","toDataURL","canvas","querySelector","downloadDataUrl","_$createComponent","Show","when","fallback","_$getNextElement","_tmpl$4","children","_tmpl$3","ExpandableWrapper","title","copyData","copyLabel","toolbarVariant","_el$","_tmpl$2","_el$2","firstChild","_el$3","_el$4","nextSibling","_el$5","_co$","_$getNextMarker","_el$6","_el$1","_el$10","_co$3","$$click","v","_ref$","_$use","_$insert","PortalDropdownMenu","open","onClose","trigger","width","For","each","onClick","hint","_el$13","_tmpl$5","_el$14","_el$15","_$addEventListener","_$runHydrationEvents","_ref$2","_el$7","_tmpl$","_el$8","_el$9","_el$0","_co$2","_$effect","_p$","_v$","className","_v$2","_v$3","_v$4","height","_$className","t","_$setAttribute","o","_$style","base","res","fetch","c","_$delegateEvents"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GraphRenderer.cjs","sources":["../../src/components/GraphRenderer.tsx"],"sourcesContent":["/**\n * GraphRenderer (v6.0.0) — generic node-link visualization powered by\n * `@antv/g6 ^5` (peer-optional). Same lazy-load pattern as\n * `ChartJSRenderer` and `MapRenderer` : the heavy lib is dynamically\n * imported only on first mount, and apps that don't install the peer\n * see an informative fallback instead of a crash.\n *\n * Spec : `@seed-ship/mcp-ui-spec@5.0.4` exports `GraphComponentParamsSchema`,\n * `GraphNode`, `GraphEdge`, `GraphLayoutName`, `GraphLayout`,\n * `GraphComponentParams` — the shape consumed here. Domain semantics\n * (`weight` etc.) are opaque to this renderer ; consumers decide what\n * the values mean.\n *\n * Copy + export : the renderer ships with `<ExpandableWrapper>` (default\n * copy = JSON of `{nodes, edges}`) plus a 3-format export menu — **PNG**\n * (visual snapshot via the underlying canvas/SVG), **Mermaid** (markdown\n * / GitHub-renderable `flowchart` syntax), **JSON** (raw reimportable\n * data). All three are computed lazily on click.\n */\n\nimport { Component, createSignal, onCleanup, onMount, Show, For } from 'solid-js';\nimport type { UIComponent } from '../types';\nimport type {\n GraphComponentParams,\n GraphLayout,\n GraphNode,\n GraphEdge,\n} from '@seed-ship/mcp-ui-spec';\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper';\nimport { PortalDropdownMenu } from './PortalDropdownMenu';\nimport { DegradedFallback } from './DegradedFallback';\nimport { graphToDegradedTable } from '../utils/degraded-projections';\nimport { useTelemetry } from '../context/MCPUITelemetryContext';\n\n// Module-scoped lazy import promise — first call triggers the dynamic\n// import, subsequent calls reuse the resolved module.\nlet g6ModulePromise: Promise<typeof import('@antv/g6')> | undefined;\n\n/**\n * Whether the `@antv/g6` peer dependency is installed and importable.\n * Resolves to `true` when the lib is available, `false` otherwise.\n *\n * Mirrors `isChartJSAvailable()` from `ChartJSRenderer`.\n */\nexport async function isG6Available(): Promise<boolean> {\n try {\n if (!g6ModulePromise) {\n g6ModulePromise = import('@antv/g6');\n }\n await g6ModulePromise;\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve the spec layout shorthand or object form into the config object\n * G6 v5 expects. When `layout` is omitted, picks `'force'` if edges are\n * present (universal default) or `'circular'` otherwise.\n */\nfunction resolveLayout(params: GraphComponentParams): { type: string; [key: string]: unknown } {\n const layout: GraphLayout | undefined = params.layout;\n if (layout === undefined) {\n const hasEdges = (params.edges?.length ?? 0) > 0;\n return { type: hasEdges ? 'force' : 'circular' };\n }\n if (typeof layout === 'string') {\n return { type: layout };\n }\n // Object form: spread the passthrough options alongside `type`.\n return { type: layout.type, ...(layout.options ?? {}) };\n}\n\n/**\n * Build the G6 v5 `Graph` constructor config from the component params.\n *\n * Pure (no DOM/lib side effects beyond reading `container`), so it can be\n * unit-tested directly without a jsdom render — this is the contract the\n * 2026-05-30 audit (P0.2) wants locked.\n *\n * ⚠️ G6 v5's `renderer` is a factory `(layer) => IRenderer`, NOT a string\n * (that was the v4 contract). Passing the string `'canvas'` / `'svg'` makes\n * G6 throw `renderer is not a function` — and because the default path used\n * to pass the string `'canvas'`, EVERY graph crashed, not just svg. So we\n * **omit `renderer` entirely**: G6 then uses its built-in canvas renderer\n * (documented default `() => new CanvasRenderer()`).\n *\n * `rendererPref: 'svg'` is reserved but NOT wired yet — a real G6 v5 SVG\n * renderer needs the `@antv/g-svg` factory (a transitive dep of `@antv/g6`,\n * not statically resolvable at build time). Until that's wired behind proper\n * optional-peer resolution, svg degrades to the canvas default with a\n * one-time warning. It must NEVER inject a string `renderer`.\n */\nexport function buildGraphConfig(\n p: GraphComponentParams,\n container?: HTMLElement\n): Record<string, unknown> {\n const config: Record<string, unknown> = {\n container,\n data: { nodes: p.nodes, edges: p.edges ?? [] },\n layout: resolveLayout(p),\n behaviors: resolveBehaviors(p),\n };\n\n if (p.rendererPref === 'svg') {\n console.warn(\n '[MCP-UI] GraphRenderer: rendererPref \"svg\" is not yet supported; using the default canvas renderer.'\n );\n }\n\n if (p.fitView !== false) {\n config.autoFit = 'view';\n }\n\n if (p.tooltip !== false) {\n config.plugins = [\n {\n type: 'tooltip',\n getContent: (_evt: unknown, items: any[]) => {\n const item = items?.[0];\n if (!item) return '';\n const label = item.label ?? item.id ?? '';\n const data = item.data ? JSON.stringify(item.data) : '';\n return `<div style=\"padding:4px 8px\"><strong>${escapeHtml(String(label))}</strong>${\n data ? `<br><span style=\"font-size:11px;opacity:0.7\">${escapeHtml(data)}</span>` : ''\n }</div>`;\n },\n },\n ];\n }\n\n return config;\n}\n\n/**\n * Build the G6 v5 `behaviors` array from the params interactivity flags.\n * Defaults : drag-canvas + zoom-canvas + drag-element + click-select.\n * Any flag set to `false` opts out.\n */\nfunction resolveBehaviors(params: GraphComponentParams): string[] {\n const behaviors: string[] = [];\n if (params.enableDrag !== false) behaviors.push('drag-element');\n if (params.enableZoom !== false) {\n behaviors.push('zoom-canvas', 'drag-canvas');\n }\n if (params.enableSelect !== false) behaviors.push('click-select');\n return behaviors;\n}\n\n/**\n * Pick a sensible Mermaid `flowchart` direction from the resolved layout.\n * `dagre` / `tree` / `mindmap` are top-down hierarchies → TD ; everything\n * else (force, concentric, circular, grid) → LR (default mermaid).\n */\nfunction mermaidDirection(layoutType: string): 'TD' | 'LR' {\n return layoutType === 'dagre' || layoutType === 'tree' || layoutType === 'mindmap' ? 'TD' : 'LR';\n}\n\n/**\n * Sanitize a string for use inside a Mermaid node label. Mermaid breaks\n * on raw quotes / brackets / pipes ; we strip the worst offenders.\n */\nfunction mermaidLabel(s: string): string {\n return s\n .replace(/[\"[\\]|]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/**\n * Convert the graph data to Mermaid `flowchart` syntax. The edge label\n * carries the optional `weight` prefix when present (e.g. `|3| label`).\n */\nfunction toMermaid(params: GraphComponentParams): string {\n const layoutType = resolveLayout(params).type;\n const dir = mermaidDirection(layoutType);\n const lines: string[] = [`flowchart ${dir}`];\n for (const n of params.nodes) {\n const label = mermaidLabel(n.label ?? n.id);\n lines.push(` ${n.id}[\"${label}\"]`);\n }\n for (const e of params.edges ?? []) {\n const labelParts: string[] = [];\n if (e.weight !== undefined) labelParts.push(String(e.weight));\n if (e.label) labelParts.push(mermaidLabel(e.label));\n const labelText = labelParts.join(' · ');\n if (labelText) {\n lines.push(` ${e.source} -->|${labelText}| ${e.target}`);\n } else {\n lines.push(` ${e.source} --> ${e.target}`);\n }\n }\n return lines.join('\\n');\n}\n\nfunction toJSON(params: GraphComponentParams): string {\n return JSON.stringify({ nodes: params.nodes, edges: params.edges ?? [] }, null, 2);\n}\n\nfunction downloadBlob(content: string | Blob, filename: string, mimeType?: string): void {\n const blob =\n typeof content === 'string' ? new Blob([content], { type: mimeType ?? 'text/plain' }) : content;\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n}\n\nexport interface GraphRendererProps {\n component: UIComponent;\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible';\n}\n\nexport const GraphRenderer: Component<GraphRendererProps> = (props) => {\n const params = () => props.component.params as GraphComponentParams;\n const isExpanded = useExpanded();\n const telemetry = useTelemetry();\n const [available, setAvailable] = createSignal<boolean | null>(null);\n const [error, setError] = createSignal<string | undefined>();\n const [exportMenuOpen, setExportMenuOpen] = createSignal(false);\n let containerRef: HTMLDivElement | undefined;\n // v6.4.0 — trigger ref consumed by <PortalDropdownMenu> for positioning\n let exportTriggerRef: HTMLButtonElement | undefined;\n // Loosely typed because G6 is a peer-optional — we don't pull its\n // types into the bundle just to type a transient local handle.\n let graphInstance: any | undefined;\n\n onMount(async () => {\n const g6Available = await isG6Available();\n setAvailable(g6Available);\n if (!g6Available || !containerRef) return;\n\n try {\n const { Graph } = await g6ModulePromise!;\n const p = params();\n const config: Record<string, unknown> = {\n container: containerRef,\n data: { nodes: p.nodes, edges: p.edges ?? [] },\n layout: resolveLayout(p),\n behaviors: resolveBehaviors(p),\n };\n\n // G6 v5's `renderer` is a factory `(layer) => IRenderer`, NOT a string\n // (that was the v4 contract). Passing the string `'canvas'` / `'svg'`\n // makes G6 throw `renderer is not a function` — and because the default\n // path also passed the string `'canvas'`, EVERY graph crashed, not just\n // the svg one.\n //\n // Fix: omit `renderer` entirely. G6 then uses its built-in canvas\n // renderer (documented default `() => new CanvasRenderer()`), which is\n // what we want for every graph.\n //\n // `rendererPref: 'svg'` is NOT wired yet: a real G6 v5 SVG renderer\n // needs the `@antv/g-svg` factory, which is a *transitive* dep of\n // `@antv/g6` (not a declared peer) and isn't statically resolvable at\n // build time. Rather than ship a fragile/unresolvable import, we treat\n // svg as \"not yet supported\" and fall back to the canvas default with a\n // one-time warning. Tracked for a follow-up that wires the factory\n // behind a proper optional-peer resolution. (Never pass a string here.)\n if (p.rendererPref === 'svg') {\n console.warn(\n '[MCP-UI] GraphRenderer: rendererPref \"svg\" is not yet supported; using the default canvas renderer.'\n );\n }\n if (p.fitView !== false) {\n config.autoFit = 'view';\n }\n if (p.tooltip !== false) {\n // Built-in tooltip plugin — shows label + a compact dump of\n // node.data on hover. Consumers can opt out with `tooltip: false`.\n config.plugins = [\n {\n type: 'tooltip',\n getContent: (_evt: unknown, items: any[]) => {\n const item = items?.[0];\n if (!item) return '';\n const label = item.label ?? item.id ?? '';\n const data = item.data ? JSON.stringify(item.data) : '';\n return `<div style=\"padding:4px 8px\"><strong>${escapeHtml(String(label))}</strong>${\n data\n ? `<br><span style=\"font-size:11px;opacity:0.7\">${escapeHtml(data)}</span>`\n : ''\n }</div>`;\n },\n },\n ];\n }\n graphInstance = new (Graph as any)(config);\n await graphInstance.render();\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to render graph';\n setError(message);\n // Fallback ladder (P2.5): the native G6 render threw — emit telemetry\n // so the failure is observable, then degrade to the edge/node table\n // below instead of leaving a blank canvas.\n telemetry?.dispatch({\n type: 'render:error',\n errorMessage: message,\n id: props.component.id ?? '',\n componentType: 'graph',\n ts: Date.now(),\n });\n }\n });\n\n onCleanup(() => {\n try {\n graphInstance?.destroy();\n } catch {\n // G6 destroy can throw on already-destroyed instances or partial\n // init failures — silent because the component is unmounting anyway.\n }\n graphInstance = undefined;\n });\n\n // ─── Export handlers ────────────────────────────────────────────────\n const handleExportJSON = () => {\n downloadBlob(toJSON(params()), `${graphFilenameStem(params())}.json`, 'application/json');\n setExportMenuOpen(false);\n };\n\n const handleExportMermaid = () => {\n downloadBlob(toMermaid(params()), `${graphFilenameStem(params())}.mmd`, 'text/plain');\n setExportMenuOpen(false);\n };\n\n const handleExportPNG = async () => {\n if (!graphInstance) return;\n try {\n // G6 v5 exposes `toDataURL()` on the graph instance.\n const dataUrl: string = await graphInstance.toDataURL?.('image/png');\n if (!dataUrl) {\n // Fallback: try to grab the underlying canvas directly.\n const canvas = containerRef?.querySelector('canvas');\n if (canvas) {\n const url = (canvas as HTMLCanvasElement).toDataURL('image/png');\n await downloadDataUrl(url, `${graphFilenameStem(params())}.png`);\n } else {\n setError('PNG export not supported in current renderer mode');\n }\n } else {\n await downloadDataUrl(dataUrl, `${graphFilenameStem(params())}.png`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'PNG export failed');\n }\n setExportMenuOpen(false);\n };\n\n return (\n <Show\n when={available() === true}\n fallback={\n <Show\n when={available() === false}\n fallback={\n // Loading skeleton while we determine peer availability\n <div class=\"w-full p-4 bg-gray-50 dark:bg-gray-800/40 border border-gray-200 dark:border-gray-700 rounded-lg animate-pulse\">\n <div class=\"h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-2\" />\n <div class=\"h-3 w-48 bg-gray-200 dark:bg-gray-700 rounded\" />\n </div>\n }\n >\n <div class=\"w-full p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg\">\n <p class=\"text-sm font-medium text-yellow-900 dark:text-yellow-100\">\n Graph rendering unavailable\n </p>\n <p class=\"text-xs text-yellow-700 dark:text-yellow-300 mt-1\">\n Install <code>@antv/g6</code> peer dependency to render <code>type: \"graph\"</code>{' '}\n components.\n </p>\n </div>\n </Show>\n }\n >\n <ExpandableWrapper\n title={params().title ?? 'Graph'}\n copyData={toJSON(params())}\n copyLabel=\"Copy graph (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div\n class={`relative w-full ${params().className ?? ''} ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}\n >\n {/* Export menu — top-right, mirrors TableRenderer's pattern */}\n <div class=\"absolute right-2 top-2 z-10\">\n <button\n ref={exportTriggerRef}\n type=\"button\"\n onClick={() => setExportMenuOpen((v) => !v)}\n class=\"px-2 py-1 text-xs bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300\"\n title=\"Export graph\"\n aria-label=\"Export graph\"\n aria-haspopup=\"menu\"\n aria-expanded={exportMenuOpen()}\n >\n Export ▾\n </button>\n <PortalDropdownMenu\n open={exportMenuOpen()}\n onClose={() => setExportMenuOpen(false)}\n trigger={exportTriggerRef}\n width={176}\n class=\"text-xs\"\n >\n <For\n each={[\n { label: 'Download PNG', onClick: handleExportPNG, hint: 'visual snapshot' },\n {\n label: 'Download Mermaid',\n onClick: handleExportMermaid,\n hint: 'markdown / GitHub',\n },\n { label: 'Download JSON', onClick: handleExportJSON, hint: 'raw data' },\n ]}\n >\n {(item) => (\n <button\n type=\"button\"\n onClick={item.onClick}\n class=\"w-full text-left px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-gray-700 last:border-b-0\"\n >\n <div class=\"font-medium\">{item.label}</div>\n <div class=\"text-[10px] text-gray-500 dark:text-gray-400\">{item.hint}</div>\n </button>\n )}\n </For>\n </PortalDropdownMenu>\n </div>\n\n {/* Native G6 canvas — hidden once a render error degrades us. */}\n <div\n ref={containerRef}\n class={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${\n error() ? 'hidden' : ''\n } ${isExpanded() ? 'flex-1 min-h-0' : ''}`}\n style={\n isExpanded()\n ? `height: 100%; width: ${params().width ?? '100%'};`\n : `height: ${params().height ?? '400px'}; width: ${params().width ?? '100%'};`\n }\n />\n {/* Fallback ladder (P2.5): degrade to an edge/node table on error\n rather than showing a bare message. Export menu stays usable. */}\n <Show when={error()}>\n <DegradedFallback\n message={`Graph rendering failed: ${error()}`}\n caption=\"Showing the graph data as a table — the interactive view is unavailable.\"\n {...graphToDegradedTable(params())}\n />\n </Show>\n </div>\n </ExpandableWrapper>\n </Show>\n );\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────\n\nfunction graphFilenameStem(params: GraphComponentParams): string {\n const base = (params.title ?? 'graph').replace(/[^a-z0-9-_]+/gi, '-').replace(/^-+|-+$/g, '');\n return base || 'graph';\n}\n\nasync function downloadDataUrl(dataUrl: string, filename: string): Promise<void> {\n const res = await fetch(dataUrl);\n const blob = await res.blob();\n downloadBlob(blob, filename);\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => {\n switch (c) {\n case '&':\n return '&';\n case '<':\n return '<';\n case '>':\n return '>';\n case '\"':\n return '"';\n case \"'\":\n return ''';\n default:\n return c;\n }\n });\n}\n\n// Re-export for tests + consumers that want to compose their own export menu\nexport { toMermaid as graphToMermaid, toJSON as graphToJSON };\n"],"names":["g6ModulePromise","isG6Available","resolveLayout","params","layout","undefined","hasEdges","edges","length","type","options","resolveBehaviors","behaviors","enableDrag","push","enableZoom","enableSelect","mermaidDirection","layoutType","mermaidLabel","s","replace","trim","toMermaid","dir","lines","n","nodes","label","id","e","labelParts","weight","String","labelText","join","source","target","toJSON","JSON","stringify","downloadBlob","content","filename","mimeType","blob","Blob","url","URL","createObjectURL","a","document","createElement","href","download","body","appendChild","click","removeChild","revokeObjectURL","GraphRenderer","props","component","isExpanded","useExpanded","telemetry","useTelemetry","available","setAvailable","createSignal","error","setError","exportMenuOpen","setExportMenuOpen","containerRef","exportTriggerRef","graphInstance","onMount","g6Available","Graph","p","config","container","data","rendererPref","console","warn","fitView","autoFit","tooltip","plugins","getContent","_evt","items","item","escapeHtml","render","err","message","Error","dispatch","errorMessage","componentType","ts","Date","now","onCleanup","destroy","handleExportJSON","graphFilenameStem","handleExportMermaid","handleExportPNG","dataUrl","toDataURL","canvas","querySelector","downloadDataUrl","_$createComponent","Show","when","fallback","_$getNextElement","_tmpl$3","children","_tmpl$2","ExpandableWrapper","title","copyData","copyLabel","toolbarVariant","_el$","_tmpl$","_el$2","firstChild","_el$3","_el$4","nextSibling","_el$5","_co$","_$getNextMarker","_el$6","_el$7","_el$8","_co$2","$$click","v","_ref$","_$use","_$insert","PortalDropdownMenu","open","onClose","trigger","width","For","each","onClick","hint","_el$1","_tmpl$4","_el$10","_el$11","_$addEventListener","_$runHydrationEvents","_ref$2","DegradedFallback","_$mergeProps","caption","graphToDegradedTable","_$effect","_p$","_v$","className","_v$2","_v$3","_v$4","height","_$className","t","_$setAttribute","o","_$style","base","res","fetch","c","_$delegateEvents"],"mappings":";;;;;;;;;;AAoCA,IAAIA;AAQJ,eAAsBC,gBAAkC;AACtD,MAAI;AACF,QAAI,CAACD,iBAAiB;AACpBA,wBAAkB,QAAA,QAAA,EAAA,KAAA,MAAA,QAAO,0EAAU,CAAA;AAAA,IACrC;AACA,UAAMA;AACN,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,SAASE,cAAcC,QAAwE;;AAC7F,QAAMC,SAAkCD,OAAOC;AAC/C,MAAIA,WAAWC,QAAW;AACxB,UAAMC,cAAYH,YAAOI,UAAPJ,mBAAcK,WAAU,KAAK;AAC/C,WAAO;AAAA,MAAEC,MAAMH,WAAW,UAAU;AAAA,IAAA;AAAA,EACtC;AACA,MAAI,OAAOF,WAAW,UAAU;AAC9B,WAAO;AAAA,MAAEK,MAAML;AAAAA,IAAAA;AAAAA,EACjB;AAEA,SAAO;AAAA,IAAEK,MAAML,OAAOK;AAAAA,IAAM,GAAIL,OAAOM,WAAW,CAAA;AAAA,EAAC;AACrD;AAoEA,SAASC,iBAAiBR,QAAwC;AAChE,QAAMS,YAAsB,CAAA;AAC5B,MAAIT,OAAOU,eAAe,MAAOD,WAAUE,KAAK,cAAc;AAC9D,MAAIX,OAAOY,eAAe,OAAO;AAC/BH,cAAUE,KAAK,eAAe,aAAa;AAAA,EAC7C;AACA,MAAIX,OAAOa,iBAAiB,MAAOJ,WAAUE,KAAK,cAAc;AAChE,SAAOF;AACT;AAOA,SAASK,iBAAiBC,YAAiC;AACzD,SAAOA,eAAe,WAAWA,eAAe,UAAUA,eAAe,YAAY,OAAO;AAC9F;AAMA,SAASC,aAAaC,GAAmB;AACvC,SAAOA,EACJC,QAAQ,YAAY,EAAE,EACtBA,QAAQ,QAAQ,GAAG,EACnBC,KAAAA;AACL;AAMA,SAASC,UAAUpB,QAAsC;AACvD,QAAMe,aAAahB,cAAcC,MAAM,EAAEM;AACzC,QAAMe,MAAMP,iBAAiBC,UAAU;AACvC,QAAMO,QAAkB,CAAC,aAAaD,GAAG,EAAE;AAC3C,aAAWE,KAAKvB,OAAOwB,OAAO;AAC5B,UAAMC,QAAQT,aAAaO,EAAEE,SAASF,EAAEG,EAAE;AAC1CJ,UAAMX,KAAK,KAAKY,EAAEG,EAAE,KAAKD,KAAK,IAAI;AAAA,EACpC;AACA,aAAWE,KAAK3B,OAAOI,SAAS,CAAA,GAAI;AAClC,UAAMwB,aAAuB,CAAA;AAC7B,QAAID,EAAEE,WAAW3B,OAAW0B,YAAWjB,KAAKmB,OAAOH,EAAEE,MAAM,CAAC;AAC5D,QAAIF,EAAEF,MAAOG,YAAWjB,KAAKK,aAAaW,EAAEF,KAAK,CAAC;AAClD,UAAMM,YAAYH,WAAWI,KAAK,KAAK;AACvC,QAAID,WAAW;AACbT,YAAMX,KAAK,KAAKgB,EAAEM,MAAM,QAAQF,SAAS,KAAKJ,EAAEO,MAAM,EAAE;AAAA,IAC1D,OAAO;AACLZ,YAAMX,KAAK,KAAKgB,EAAEM,MAAM,QAAQN,EAAEO,MAAM,EAAE;AAAA,IAC5C;AAAA,EACF;AACA,SAAOZ,MAAMU,KAAK,IAAI;AACxB;AAEA,SAASG,OAAOnC,QAAsC;AACpD,SAAOoC,KAAKC,UAAU;AAAA,IAAEb,OAAOxB,OAAOwB;AAAAA,IAAOpB,OAAOJ,OAAOI,SAAS,CAAA;AAAA,EAAA,GAAM,MAAM,CAAC;AACnF;AAEA,SAASkC,aAAaC,SAAwBC,UAAkBC,UAAyB;AACvF,QAAMC,OACJ,OAAOH,YAAY,WAAW,IAAII,KAAK,CAACJ,OAAO,GAAG;AAAA,IAAEjC,MAAMmC,YAAY;AAAA,EAAA,CAAc,IAAIF;AAC1F,QAAMK,MAAMC,IAAIC,gBAAgBJ,IAAI;AACpC,QAAMK,IAAIC,SAASC,cAAc,GAAG;AACpCF,IAAEG,OAAON;AACTG,IAAEI,WAAWX;AACbQ,WAASI,KAAKC,YAAYN,CAAC;AAC3BA,IAAEO,MAAAA;AACFN,WAASI,KAAKG,YAAYR,CAAC;AAC3BF,MAAIW,gBAAgBZ,GAAG;AACzB;AAWO,MAAMa,gBAAgDC,CAAAA,UAAU;AACrE,QAAM1D,SAASA,MAAM0D,MAAMC,UAAU3D;AACrC,QAAM4D,aAAaC,kBAAAA,YAAAA;AACnB,QAAMC,YAAYC,sBAAAA,aAAAA;AAClB,QAAM,CAACC,WAAWC,YAAY,IAAIC,QAAAA,aAA6B,IAAI;AACnE,QAAM,CAACC,OAAOC,QAAQ,IAAIF,qBAAAA;AAC1B,QAAM,CAACG,gBAAgBC,iBAAiB,IAAIJ,QAAAA,aAAa,KAAK;AAC9D,MAAIK;AAEJ,MAAIC;AAGJ,MAAIC;AAEJC,UAAAA,QAAQ,YAAY;AAClB,UAAMC,cAAc,MAAM7E,cAAAA;AAC1BmE,iBAAaU,WAAW;AACxB,QAAI,CAACA,eAAe,CAACJ,aAAc;AAEnC,QAAI;AACF,YAAM;AAAA,QAAEK;AAAAA,MAAAA,IAAU,MAAM/E;AACxB,YAAMgF,IAAI7E,OAAAA;AACV,YAAM8E,SAAkC;AAAA,QACtCC,WAAWR;AAAAA,QACXS,MAAM;AAAA,UAAExD,OAAOqD,EAAErD;AAAAA,UAAOpB,OAAOyE,EAAEzE,SAAS,CAAA;AAAA,QAAA;AAAA,QAC1CH,QAAQF,cAAc8E,CAAC;AAAA,QACvBpE,WAAWD,iBAAiBqE,CAAC;AAAA,MAAA;AAoB/B,UAAIA,EAAEI,iBAAiB,OAAO;AAC5BC,gBAAQC,KACN,qGACF;AAAA,MACF;AACA,UAAIN,EAAEO,YAAY,OAAO;AACvBN,eAAOO,UAAU;AAAA,MACnB;AACA,UAAIR,EAAES,YAAY,OAAO;AAGvBR,eAAOS,UAAU,CACf;AAAA,UACEjF,MAAM;AAAA,UACNkF,YAAYA,CAACC,MAAeC,UAAiB;AAC3C,kBAAMC,OAAOD,+BAAQ;AACrB,gBAAI,CAACC,KAAM,QAAO;AAClB,kBAAMlE,QAAQkE,KAAKlE,SAASkE,KAAKjE,MAAM;AACvC,kBAAMsD,OAAOW,KAAKX,OAAO5C,KAAKC,UAAUsD,KAAKX,IAAI,IAAI;AACrD,mBAAO,wCAAwCY,WAAW9D,OAAOL,KAAK,CAAC,CAAC,YACtEuD,OACI,gDAAgDY,WAAWZ,IAAI,CAAC,YAChE,EAAE;AAAA,UAEV;AAAA,QAAA,CACD;AAAA,MAEL;AACAP,sBAAgB,IAAKG,MAAcE,MAAM;AACzC,YAAML,cAAcoB,OAAAA;AAAAA,IACtB,SAASC,KAAK;AACZ,YAAMC,UAAUD,eAAeE,QAAQF,IAAIC,UAAU;AACrD3B,eAAS2B,OAAO;AAIhBjC,6CAAWmC,SAAS;AAAA,QAClB3F,MAAM;AAAA,QACN4F,cAAcH;AAAAA,QACdrE,IAAIgC,MAAMC,UAAUjC,MAAM;AAAA,QAC1ByE,eAAe;AAAA,QACfC,IAAIC,KAAKC,IAAAA;AAAAA,MAAI;AAAA,IAEjB;AAAA,EACF,CAAC;AAEDC,UAAAA,UAAU,MAAM;AACd,QAAI;AACF9B,qDAAe+B;AAAAA,IACjB,QAAQ;AAAA,IAEN;AAEF/B,oBAAgBvE;AAAAA,EAClB,CAAC;AAGD,QAAMuG,mBAAmBA,MAAM;AAC7BnE,iBAAaH,OAAOnC,OAAAA,CAAQ,GAAG,GAAG0G,kBAAkB1G,QAAQ,CAAC,SAAS,kBAAkB;AACxFsE,sBAAkB,KAAK;AAAA,EACzB;AAEA,QAAMqC,sBAAsBA,MAAM;AAChCrE,iBAAalB,UAAUpB,OAAAA,CAAQ,GAAG,GAAG0G,kBAAkB1G,QAAQ,CAAC,QAAQ,YAAY;AACpFsE,sBAAkB,KAAK;AAAA,EACzB;AAEA,QAAMsC,kBAAkB,YAAY;;AAClC,QAAI,CAACnC,cAAe;AACpB,QAAI;AAEF,YAAMoC,UAAkB,QAAMpC,mBAAcqC,cAAdrC,uCAA0B;AACxD,UAAI,CAACoC,SAAS;AAEZ,cAAME,SAASxC,6CAAcyC,cAAc;AAC3C,YAAID,QAAQ;AACV,gBAAMnE,MAAOmE,OAA6BD,UAAU,WAAW;AAC/D,gBAAMG,gBAAgBrE,KAAK,GAAG8D,kBAAkB1G,OAAAA,CAAQ,CAAC,MAAM;AAAA,QACjE,OAAO;AACLoE,mBAAS,mDAAmD;AAAA,QAC9D;AAAA,MACF,OAAO;AACL,cAAM6C,gBAAgBJ,SAAS,GAAGH,kBAAkB1G,OAAAA,CAAQ,CAAC,MAAM;AAAA,MACrE;AAAA,IACF,SAAS8F,KAAK;AACZ1B,eAAS0B,eAAeE,QAAQF,IAAIC,UAAU,mBAAmB;AAAA,IACnE;AACAzB,sBAAkB,KAAK;AAAA,EACzB;AAEA,SAAA4C,IAAAA,gBACGC,QAAAA,MAAI;AAAA,IAAA,IACHC,OAAI;AAAA,aAAEpD,gBAAgB;AAAA,IAAI;AAAA,IAAA,IAC1BqD,WAAQ;AAAA,aAAAH,IAAAA,gBACLC,QAAAA,MAAI;AAAA,QAAA,IACHC,OAAI;AAAA,iBAAEpD,gBAAgB;AAAA,QAAK;AAAA,QAAA,IAC3BqD,WAAQ;AAAA;AAAA;AAAA,YACNC,IAAAA,eAAAC,OAAA;AAAA;AAAA,QAAA;AAAA,QAAA,IAAAC,WAAA;AAAA,iBAAAF,IAAAA,eAAAG,OAAA;AAAA,QAAA;AAAA,MAAA,CAAA;AAAA,IAAA;AAAA,IAAA,IAAAD,WAAA;AAAA,aAAAN,IAAAA,gBAmBLQ,kBAAAA,mBAAiB;AAAA,QAAA,IAChBC,QAAK;AAAA,iBAAE3H,OAAAA,EAAS2H,SAAS;AAAA,QAAO;AAAA,QAAA,IAChCC,WAAQ;AAAA,iBAAEzF,OAAOnC,QAAQ;AAAA,QAAC;AAAA,QAC1B6H,WAAS;AAAA,QAAA,IACTC,iBAAc;AAAA,iBAAEpE,MAAMoE;AAAAA,QAAc;AAAA,QAAA,IAAAN,WAAA;AAAA,cAAAO,OAAAT,IAAAA,eAAAU,MAAA,GAAAC,QAAAF,KAAAG,YAAAC,QAAAF,MAAAC,YAAAE,QAAAD,MAAAE,aAAA,CAAAC,OAAAC,IAAA,IAAAC,IAAAA,cAAAJ,MAAAC,WAAA,GAAAI,QAAAR,MAAAI,aAAAK,QAAAD,MAAAJ,aAAA,CAAAM,OAAAC,KAAA,IAAAJ,IAAAA,cAAAE,MAAAL,WAAA;AAAAF,gBAAAU,UAYrB,MAAMvE,kBAAmBwE,CAAAA,MAAM,CAACA,CAAC;AAAC,cAAAC,QAFtCvE;AAAgB,iBAAAuE,UAAA,aAAAC,IAAAA,IAAAD,OAAAZ,KAAA,IAAhB3D,mBAAgB2D;AAAAc,qBAAAhB,OAAAf,IAAAA,gBAWtBgC,uCAAkB;AAAA,YAAA,IACjBC,OAAI;AAAA,qBAAE9E,eAAAA;AAAAA,YAAgB;AAAA,YACtB+E,SAASA,MAAM9E,kBAAkB,KAAK;AAAA,YACtC+E,SAAS7E;AAAAA,YACT8E,OAAO;AAAA,YAAG,SAAA;AAAA,YAAA,IAAA9B,WAAA;AAAA,qBAAAN,IAAAA,gBAGTqC,QAAAA,KAAG;AAAA,gBACFC,MAAM,CACJ;AAAA,kBAAE/H,OAAO;AAAA,kBAAgBgI,SAAS7C;AAAAA,kBAAiB8C,MAAM;AAAA,gBAAA,GACzD;AAAA,kBACEjI,OAAO;AAAA,kBACPgI,SAAS9C;AAAAA,kBACT+C,MAAM;AAAA,gBAAA,GAER;AAAA,kBAAEjI,OAAO;AAAA,kBAAiBgI,SAAShD;AAAAA,kBAAkBiD,MAAM;AAAA,gBAAA,CAAY;AAAA,gBACxElC,UAEC7B,WAAI,MAAA;AAAA,sBAAAgE,QAAArC,mBAAAsC,OAAA,GAAAC,SAAAF,MAAAzB,YAAA4B,SAAAD,OAAAxB;AAAA0B,sBAAAA,iBAAAJ,OAAA,SAGOhE,KAAK8D,SAAO,IAAA;AAAAR,sBAAAA,OAAAY,QAAA,MAGKlE,KAAKlE,KAAK;AAAAwH,sBAAAA,OAAAa,QAAA,MACuBnE,KAAK+D,IAAI;AAAAM,yCAAAA;AAAA,yBAAAL;AAAAA,gBAAA,GAAA;AAAA,cAAA,CAEvE;AAAA,YAAA;AAAA,UAAA,CAAA,GAAArB,OAAAC,IAAA;AAAA,cAAA0B,SAOA1F;AAAY,iBAAA0F,WAAA,aAAAjB,IAAAA,IAAAiB,QAAAxB,KAAA,IAAZlE,eAAYkE;AAAAQ,qBAAAlB,MAAAb,IAAAA,gBAYlBC,cAAI;AAAA,YAAA,IAACC,OAAI;AAAA,qBAAEjD,MAAAA;AAAAA,YAAO;AAAA,YAAA,IAAAqD,WAAA;AAAA,qBAAAN,IAAAA,gBAChBgD,iBAAAA,kBAAgBC,eAAA;AAAA,gBAAA,IACfpE,UAAO;AAAA,yBAAE,2BAA2B5B,OAAO;AAAA,gBAAE;AAAA,gBAC7CiG,SAAO;AAAA,cAAA,GAAA,MACHC,oBAAAA,qBAAqBrK,OAAAA,CAAQ,CAAC,CAAA;AAAA,YAAA;AAAA,UAAA,CAAA,GAAA2I,OAAAC,KAAA;AAAA0B,cAAAA,OAAAC,CAAAA,QAAA;AAAA,gBAAAC,MApE/B,mBAAmBxK,OAAAA,EAASyK,aAAa,EAAE,IAChD7G,eAAe,iCAAiC,EAAE,IAClD8G,OAYiBrG,kBAAgBsG,OAuC1B,8GACLxG,MAAAA,IAAU,WAAW,EAAE,IACrBP,eAAe,mBAAmB,EAAE,IAAEgH,OAExChH,eACI,wBAAwB5D,SAASsJ,SAAS,MAAM,MAChD,WAAWtJ,SAAS6K,UAAU,OAAO,YAAY7K,SAASsJ,SAAS,MAAM;AAAGkB,oBAAAD,IAAA5I,KAAAmJ,IAAAA,UAAA/C,MAAAwC,IAAA5I,IAAA6I,GAAA;AAAAE,qBAAAH,IAAAQ,KAAAC,IAAAA,aAAA7C,OAAA,iBAAAoC,IAAAQ,IAAAL,IAAA;AAAAC,qBAAAJ,IAAAxH,KAAA+H,IAAAA,UAAArC,OAAA8B,IAAAxH,IAAA4H,IAAA;AAAAJ,gBAAAU,IAAAC,IAAAA,MAAAzC,OAAAmC,MAAAL,IAAAU,CAAA;AAAA,mBAAAV;AAAAA,UAAA,GAAA;AAAA,YAAA5I,GAAAzB;AAAAA,YAAA6K,GAAA7K;AAAAA,YAAA6C,GAAA7C;AAAAA,YAAA+K,GAAA/K;AAAAA,UAAAA,CAAA;AAAA8J,iCAAAA;AAAA,iBAAAjC;AAAAA,QAAA;AAAA,MAAA,CAAA;AAAA,IAAA;AAAA,EAAA,CAAA;AAgB9F;AAIA,SAASrB,kBAAkB1G,QAAsC;AAC/D,QAAMmL,QAAQnL,OAAO2H,SAAS,SAASzG,QAAQ,kBAAkB,GAAG,EAAEA,QAAQ,YAAY,EAAE;AAC5F,SAAOiK,QAAQ;AACjB;AAEA,eAAelE,gBAAgBJ,SAAiBrE,UAAiC;AAC/E,QAAM4I,MAAM,MAAMC,MAAMxE,OAAO;AAC/B,QAAMnE,OAAO,MAAM0I,IAAI1I,KAAAA;AACvBJ,eAAaI,MAAMF,QAAQ;AAC7B;AAEA,SAASoD,WAAW3E,GAAmB;AACrC,SAAOA,EAAEC,QAAQ,YAAaoK,CAAAA,MAAM;AAClC,YAAQA,GAAAA;AAAAA,MACN,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAOA;AAAAA,IAAAA;AAAAA,EAEb,CAAC;AACH;AAG8DC,IAAAA,eAAA,CAAA,OAAA,CAAA;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GraphRenderer.d.ts","sourceRoot":"","sources":["../../src/components/GraphRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,SAAS,EAA+C,MAAM,UAAU,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EACV,oBAAoB,EAIrB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"GraphRenderer.d.ts","sourceRoot":"","sources":["../../src/components/GraphRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,SAAS,EAA+C,MAAM,UAAU,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EACV,oBAAoB,EAIrB,MAAM,wBAAwB,CAAC;AAWhC;;;;;GAKG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAUtD;AAoBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,EAAE,oBAAoB,EACvB,SAAS,CAAC,EAAE,WAAW,GACtB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoCzB;AAqCD;;;GAGG;AACH,iBAAS,SAAS,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAoBvD;AAED,iBAAS,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAEpD;AAeD,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,WAAW,CAAC;IACvB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC;CAC7C;AAED,eAAO,MAAM,aAAa,EAAE,SAAS,CAAC,kBAAkB,CAoPvD,CAAC;AAmCF,OAAO,EAAE,SAAS,IAAI,cAAc,EAAE,MAAM,IAAI,WAAW,EAAE,CAAC"}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { delegateEvents, createComponent, getNextElement, template, getNextMarker, insert, addEventListener, runHydrationEvents, effect, className, setAttribute, style, use } from "solid-js/web";
|
|
1
|
+
import { delegateEvents, createComponent, getNextElement, template, getNextMarker, insert, addEventListener, runHydrationEvents, mergeProps, effect, className, setAttribute, style, use } from "solid-js/web";
|
|
2
2
|
import { createSignal, onMount, onCleanup, Show, For } from "solid-js";
|
|
3
3
|
import { useExpanded, ExpandableWrapper } from "./ExpandableWrapper.js";
|
|
4
4
|
import { PortalDropdownMenu } from "./PortalDropdownMenu.js";
|
|
5
|
-
|
|
5
|
+
import { DegradedFallback } from "./DegradedFallback.js";
|
|
6
|
+
import { graphToDegradedTable } from "../utils/degraded-projections.js";
|
|
7
|
+
import { useTelemetry } from "../context/MCPUITelemetryContext.js";
|
|
8
|
+
var _tmpl$ = /* @__PURE__ */ template(`<div><div class="absolute right-2 top-2 z-10"><button type=button class="px-2 py-1 text-xs bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"title="Export graph"aria-label="Export graph"aria-haspopup=menu>Export ▾</button><!$><!/></div><div></div><!$><!/>`), _tmpl$2 = /* @__PURE__ */ template(`<div class="w-full p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg"><p class="text-sm font-medium text-yellow-900 dark:text-yellow-100">Graph rendering unavailable</p><p class="text-xs text-yellow-700 dark:text-yellow-300 mt-1">Install <code>@antv/g6</code> peer dependency to render <code>type: "graph"</code> components.`), _tmpl$3 = /* @__PURE__ */ template(`<div class="w-full p-4 bg-gray-50 dark:bg-gray-800/40 border border-gray-200 dark:border-gray-700 rounded-lg animate-pulse"><div class="h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-2"></div><div class="h-3 w-48 bg-gray-200 dark:bg-gray-700 rounded">`), _tmpl$4 = /* @__PURE__ */ template(`<button type=button class="w-full text-left px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-gray-700 last:border-b-0"><div class=font-medium></div><div class="text-[10px] text-gray-500 dark:text-gray-400">`);
|
|
6
9
|
let g6ModulePromise;
|
|
7
10
|
async function isG6Available() {
|
|
8
11
|
try {
|
|
@@ -92,6 +95,7 @@ function downloadBlob(content, filename, mimeType) {
|
|
|
92
95
|
const GraphRenderer = (props) => {
|
|
93
96
|
const params = () => props.component.params;
|
|
94
97
|
const isExpanded = useExpanded();
|
|
98
|
+
const telemetry = useTelemetry();
|
|
95
99
|
const [available, setAvailable] = createSignal(null);
|
|
96
100
|
const [error, setError] = createSignal();
|
|
97
101
|
const [exportMenuOpen, setExportMenuOpen] = createSignal(false);
|
|
@@ -137,7 +141,15 @@ const GraphRenderer = (props) => {
|
|
|
137
141
|
graphInstance = new Graph(config);
|
|
138
142
|
await graphInstance.render();
|
|
139
143
|
} catch (err) {
|
|
140
|
-
|
|
144
|
+
const message = err instanceof Error ? err.message : "Failed to render graph";
|
|
145
|
+
setError(message);
|
|
146
|
+
telemetry == null ? void 0 : telemetry.dispatch({
|
|
147
|
+
type: "render:error",
|
|
148
|
+
errorMessage: message,
|
|
149
|
+
id: props.component.id ?? "",
|
|
150
|
+
componentType: "graph",
|
|
151
|
+
ts: Date.now()
|
|
152
|
+
});
|
|
141
153
|
}
|
|
142
154
|
});
|
|
143
155
|
onCleanup(() => {
|
|
@@ -188,11 +200,11 @@ const GraphRenderer = (props) => {
|
|
|
188
200
|
get fallback() {
|
|
189
201
|
return (
|
|
190
202
|
// Loading skeleton while we determine peer availability
|
|
191
|
-
getNextElement(_tmpl$
|
|
203
|
+
getNextElement(_tmpl$3)
|
|
192
204
|
);
|
|
193
205
|
},
|
|
194
206
|
get children() {
|
|
195
|
-
return getNextElement(_tmpl$
|
|
207
|
+
return getNextElement(_tmpl$2);
|
|
196
208
|
}
|
|
197
209
|
});
|
|
198
210
|
},
|
|
@@ -209,7 +221,7 @@ const GraphRenderer = (props) => {
|
|
|
209
221
|
return props.toolbarVariant;
|
|
210
222
|
},
|
|
211
223
|
get children() {
|
|
212
|
-
var _el$ = getNextElement(_tmpl$
|
|
224
|
+
var _el$ = getNextElement(_tmpl$), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling, [_el$5, _co$] = getNextMarker(_el$4.nextSibling), _el$6 = _el$2.nextSibling, _el$7 = _el$6.nextSibling, [_el$8, _co$2] = getNextMarker(_el$7.nextSibling);
|
|
213
225
|
_el$3.$$click = () => setExportMenuOpen((v) => !v);
|
|
214
226
|
var _ref$ = exportTriggerRef;
|
|
215
227
|
typeof _ref$ === "function" ? use(_ref$, _el$3) : exportTriggerRef = _el$3;
|
|
@@ -237,12 +249,12 @@ const GraphRenderer = (props) => {
|
|
|
237
249
|
hint: "raw data"
|
|
238
250
|
}],
|
|
239
251
|
children: (item) => (() => {
|
|
240
|
-
var _el$
|
|
241
|
-
addEventListener(_el$
|
|
242
|
-
insert(_el$
|
|
243
|
-
insert(_el$
|
|
252
|
+
var _el$1 = getNextElement(_tmpl$4), _el$10 = _el$1.firstChild, _el$11 = _el$10.nextSibling;
|
|
253
|
+
addEventListener(_el$1, "click", item.onClick, true);
|
|
254
|
+
insert(_el$10, () => item.label);
|
|
255
|
+
insert(_el$11, () => item.hint);
|
|
244
256
|
runHydrationEvents();
|
|
245
|
-
return _el$
|
|
257
|
+
return _el$1;
|
|
246
258
|
})()
|
|
247
259
|
});
|
|
248
260
|
}
|
|
@@ -254,13 +266,16 @@ const GraphRenderer = (props) => {
|
|
|
254
266
|
return error();
|
|
255
267
|
},
|
|
256
268
|
get children() {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
269
|
+
return createComponent(DegradedFallback, mergeProps({
|
|
270
|
+
get message() {
|
|
271
|
+
return `Graph rendering failed: ${error()}`;
|
|
272
|
+
},
|
|
273
|
+
caption: "Showing the graph data as a table — the interactive view is unavailable."
|
|
274
|
+
}, () => graphToDegradedTable(params())));
|
|
260
275
|
}
|
|
261
|
-
}), _el$
|
|
276
|
+
}), _el$8, _co$2);
|
|
262
277
|
effect((_p$) => {
|
|
263
|
-
var _v$ = `relative w-full ${params().className ?? ""} ${isExpanded() ? "flex-1 min-h-0 flex flex-col" : ""}`, _v$2 = exportMenuOpen(), _v$3 = `bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${isExpanded() ? "flex-1 min-h-0" : ""}`, _v$4 = isExpanded() ? `height: 100%; width: ${params().width ?? "100%"};` : `height: ${params().height ?? "400px"}; width: ${params().width ?? "100%"};`;
|
|
278
|
+
var _v$ = `relative w-full ${params().className ?? ""} ${isExpanded() ? "flex-1 min-h-0 flex flex-col" : ""}`, _v$2 = exportMenuOpen(), _v$3 = `bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${error() ? "hidden" : ""} ${isExpanded() ? "flex-1 min-h-0" : ""}`, _v$4 = isExpanded() ? `height: 100%; width: ${params().width ?? "100%"};` : `height: ${params().height ?? "400px"}; width: ${params().width ?? "100%"};`;
|
|
264
279
|
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
|
|
265
280
|
_v$2 !== _p$.t && setAttribute(_el$3, "aria-expanded", _p$.t = _v$2);
|
|
266
281
|
_v$3 !== _p$.a && className(_el$6, _p$.a = _v$3);
|