@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
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,55 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [6.9.0] - 2026-05-31
|
|
9
|
+
|
|
10
|
+
### Added — renderer fallback ladder (no silent blanks)
|
|
11
|
+
|
|
12
|
+
Every heavy renderer now follows the same three-rung contract (audit
|
|
13
|
+
`docs/briefs/AUDIT-2026-05-30-visual-renderers-g6-ontology.md`, P2.5):
|
|
14
|
+
|
|
15
|
+
1. **native** render when the peer lib is available and succeeds;
|
|
16
|
+
2. **degraded but useful** view when the native render throws — a visible
|
|
17
|
+
notice plus a plain data table, so the user still sees the underlying
|
|
18
|
+
data instead of a blank space;
|
|
19
|
+
3. a `component:error` **telemetry** event (`detail.degraded = true`) so the
|
|
20
|
+
failure is observable by hosts that mount `<MCPUITelemetryProvider>`.
|
|
21
|
+
|
|
22
|
+
Previously a render-path failure left an empty canvas / blank container
|
|
23
|
+
(graph, chart) or a bare red error string (map). Now:
|
|
24
|
+
|
|
25
|
+
- **`graph`** (`<GraphRenderer>`) — on G6 render error, degrades to an
|
|
26
|
+
edge table (Source / Target / Label) or, when there are no edges, a node
|
|
27
|
+
list. The export menu (PNG / Mermaid / JSON) stays usable.
|
|
28
|
+
- **`map`** (`<MapRenderer>`) — the Leaflet drawing block is now wrapped;
|
|
29
|
+
on failure it degrades to a coordinate table (markers + GeoJSON features
|
|
30
|
+
→ Type / Lat / Lng / Info).
|
|
31
|
+
- **`chart`** (`<ChartJSRenderer>`) — on Chart.js render error, degrades to
|
|
32
|
+
a series table (labels × datasets) instead of a blank canvas.
|
|
33
|
+
|
|
34
|
+
New exports: `DegradedFallback` component (+ `DegradedFallbackProps`) for
|
|
35
|
+
hosts that want to build consistent fallbacks of their own. New pure
|
|
36
|
+
projection helpers (`graphToDegradedTable`, `mapToDegradedTable`,
|
|
37
|
+
`chartToDegradedTable`) under `src/utils/degraded-projections`.
|
|
38
|
+
|
|
39
|
+
`source_card` is intentionally out of scope here — it is not yet a real
|
|
40
|
+
`UIResourceRenderer` type (tracked separately as audit P2.6).
|
|
41
|
+
|
|
42
|
+
## [6.8.2] - 2026-05-30
|
|
43
|
+
|
|
44
|
+
### Fixed — `type:'map'` rejected when it renders purely from GeoJSON
|
|
45
|
+
|
|
46
|
+
`validateComponent` rejected a `type:'map'` as `INVALID_MAP` unless it had
|
|
47
|
+
`center` or `markers`. But since spec@5.2.0 a map can render purely from
|
|
48
|
+
`params.geojson`, named `layers`, or a `pmtiles` source (e.g. a Cadastre /
|
|
49
|
+
choropleth map with an auto-fit viewport and no markers). Those valid maps —
|
|
50
|
+
which `<MapRenderer>` draws fine — were blocked before reaching the renderer.
|
|
51
|
+
|
|
52
|
+
The validator now accepts a map carrying **any** of `center`, `markers`,
|
|
53
|
+
`geojson`, `layers`, or `pmtiles`, aligning the Solid check with
|
|
54
|
+
`MapComponentParamsSchema`. A genuinely empty map (none of those) is still
|
|
55
|
+
rejected. Per audit `docs/briefs/AUDIT-2026-05-30-visual-renderers-g6-ontology.md` (P1.1).
|
|
56
|
+
|
|
8
57
|
## [6.8.1] - 2026-05-30
|
|
9
58
|
|
|
10
59
|
### Fixed — `type:'graph'` crashed with "renderer is not a function"
|
|
@@ -25,7 +25,10 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
25
25
|
const web = require("solid-js/web");
|
|
26
26
|
const solidJs = require("solid-js");
|
|
27
27
|
const ExpandableWrapper = require("./ExpandableWrapper.cjs");
|
|
28
|
-
|
|
28
|
+
const DegradedFallback = require("./DegradedFallback.cjs");
|
|
29
|
+
const degradedProjections = require("../utils/degraded-projections.cjs");
|
|
30
|
+
const MCPUITelemetryContext = require("../context/MCPUITelemetryContext.cjs");
|
|
31
|
+
var _tmpl$ = /* @__PURE__ */ web.template(`<h3 class="text-sm font-semibold text-gray-900 dark:text-white">`), _tmpl$2 = /* @__PURE__ */ web.template(`<button class="opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"title="Download PNG"aria-label="Download chart as PNG"><svg class="w-3 h-3 text-gray-500 dark:text-gray-400"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">`), _tmpl$3 = /* @__PURE__ */ web.template(`<div class="flex items-center justify-between mb-3 flex-shrink-0"><!$><!/><!$><!/>`), _tmpl$4 = /* @__PURE__ */ web.template(`<div class="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80"><div class="flex flex-col items-center gap-2"><div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div><span class="text-sm text-gray-500 dark:text-gray-400">Loading chart...`), _tmpl$5 = /* @__PURE__ */ web.template(`<div><!$><!/><!$><!/><!$><!/><div><canvas>`);
|
|
29
32
|
let ChartJS = null;
|
|
30
33
|
let chartJSLoadPromise = null;
|
|
31
34
|
const loadChartJS = async () => {
|
|
@@ -56,6 +59,7 @@ const ChartJSRenderer = (props) => {
|
|
|
56
59
|
let chartInstance;
|
|
57
60
|
const params = () => props.component.params;
|
|
58
61
|
const isExpanded = ExpandableWrapper.useExpanded();
|
|
62
|
+
const telemetry = MCPUITelemetryContext.useTelemetry();
|
|
59
63
|
const exportEnabled = () => params().exportable !== false;
|
|
60
64
|
const copyDataJSON = () => JSON.stringify({
|
|
61
65
|
type: params().type,
|
|
@@ -70,7 +74,7 @@ const ChartJSRenderer = (props) => {
|
|
|
70
74
|
a.click();
|
|
71
75
|
};
|
|
72
76
|
solidJs.createEffect(async () => {
|
|
73
|
-
var _a, _b, _c, _d, _e;
|
|
77
|
+
var _a, _b, _c, _d, _e, _f;
|
|
74
78
|
if (!canvasRef) return;
|
|
75
79
|
const chartParams = params();
|
|
76
80
|
setIsLoading(true);
|
|
@@ -125,7 +129,14 @@ const ChartJSRenderer = (props) => {
|
|
|
125
129
|
const error2 = err instanceof Error ? err : new Error("Chart rendering failed");
|
|
126
130
|
setError(error2.message);
|
|
127
131
|
setIsLoading(false);
|
|
128
|
-
|
|
132
|
+
telemetry == null ? void 0 : telemetry.dispatch({
|
|
133
|
+
type: "render:error",
|
|
134
|
+
errorMessage: error2.message,
|
|
135
|
+
id: ((_e = props.component) == null ? void 0 : _e.id) ?? "",
|
|
136
|
+
componentType: "chart",
|
|
137
|
+
ts: Date.now()
|
|
138
|
+
});
|
|
139
|
+
(_f = props.onError) == null ? void 0 : _f.call(props, error2);
|
|
129
140
|
}
|
|
130
141
|
});
|
|
131
142
|
solidJs.onCleanup(() => {
|
|
@@ -146,7 +157,7 @@ const ChartJSRenderer = (props) => {
|
|
|
146
157
|
return props.toolbarVariant;
|
|
147
158
|
},
|
|
148
159
|
get children() {
|
|
149
|
-
var _el$ = web.getNextElement(_tmpl$
|
|
160
|
+
var _el$ = web.getNextElement(_tmpl$5), _el$10 = _el$.firstChild, [_el$11, _co$3] = web.getNextMarker(_el$10.nextSibling), _el$12 = _el$11.nextSibling, [_el$13, _co$4] = web.getNextMarker(_el$12.nextSibling), _el$14 = _el$13.nextSibling, [_el$15, _co$5] = web.getNextMarker(_el$14.nextSibling), _el$0 = _el$15.nextSibling, _el$1 = _el$0.firstChild;
|
|
150
161
|
web.insert(_el$, web.createComponent(solidJs.Show, {
|
|
151
162
|
get when() {
|
|
152
163
|
return params().title || exportEnabled();
|
|
@@ -176,7 +187,7 @@ const ChartJSRenderer = (props) => {
|
|
|
176
187
|
}), _el$8, _co$2);
|
|
177
188
|
return _el$2;
|
|
178
189
|
}
|
|
179
|
-
}), _el$
|
|
190
|
+
}), _el$11, _co$3);
|
|
180
191
|
web.insert(_el$, web.createComponent(solidJs.Show, {
|
|
181
192
|
get when() {
|
|
182
193
|
return isLoading();
|
|
@@ -184,19 +195,22 @@ const ChartJSRenderer = (props) => {
|
|
|
184
195
|
get children() {
|
|
185
196
|
return web.getNextElement(_tmpl$4);
|
|
186
197
|
}
|
|
187
|
-
}), _el$
|
|
198
|
+
}), _el$13, _co$4);
|
|
188
199
|
web.insert(_el$, web.createComponent(solidJs.Show, {
|
|
189
200
|
get when() {
|
|
190
201
|
return error();
|
|
191
202
|
},
|
|
192
203
|
get children() {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
204
|
+
return web.createComponent(DegradedFallback.DegradedFallback, web.mergeProps({
|
|
205
|
+
get message() {
|
|
206
|
+
return `Chart rendering failed: ${error()}`;
|
|
207
|
+
},
|
|
208
|
+
caption: "Showing the chart data as a table — the interactive chart is unavailable."
|
|
209
|
+
}, () => degradedProjections.chartToDegradedTable(params() ?? {})));
|
|
196
210
|
}
|
|
197
|
-
}), _el$
|
|
211
|
+
}), _el$15, _co$5);
|
|
198
212
|
var _ref$ = canvasRef;
|
|
199
|
-
typeof _ref$ === "function" ? web.use(_ref$, _el$
|
|
213
|
+
typeof _ref$ === "function" ? web.use(_ref$, _el$1) : canvasRef = _el$1;
|
|
200
214
|
web.effect((_p$) => {
|
|
201
215
|
var _v$ = `relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${isExpanded() ? "flex-1 min-h-0 flex flex-col" : ""}`, _v$2 = `w-full ${isExpanded() ? "flex-1 min-h-0" : ""}`, _v$3 = error() ? {
|
|
202
216
|
display: "none"
|
|
@@ -208,8 +222,8 @@ const ChartJSRenderer = (props) => {
|
|
|
208
222
|
display: "block"
|
|
209
223
|
};
|
|
210
224
|
_v$ !== _p$.e && web.className(_el$, _p$.e = _v$);
|
|
211
|
-
_v$2 !== _p$.t && web.className(_el$
|
|
212
|
-
_p$.a = web.style(_el$
|
|
225
|
+
_v$2 !== _p$.t && web.className(_el$0, _p$.t = _v$2);
|
|
226
|
+
_p$.a = web.style(_el$0, _v$3, _p$.a);
|
|
213
227
|
return _p$;
|
|
214
228
|
}, {
|
|
215
229
|
e: void 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartJSRenderer.cjs","sources":["../../src/components/ChartJSRenderer.tsx"],"sourcesContent":["/**\n * ChartJSRenderer - Native Chart.js rendering\n * Sprint 4: State & Charts\n *\n * Requires chart.js peer dependency:\n * ```\n * pnpm add chart.js\n * ```\n */\n\nimport { Component, createEffect, onCleanup, createSignal, Show } from 'solid-js'\nimport type { UIComponent, ChartComponentParams } from '../types'\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper'\n\n// Lazy load Chart.js to avoid bundling if not used\nlet ChartJS: any = null\nlet chartJSLoadPromise: Promise<any> | null = null\n\nconst loadChartJS = async () => {\n if (ChartJS) return ChartJS\n\n if (!chartJSLoadPromise) {\n chartJSLoadPromise = import('chart.js/auto')\n .then((module) => {\n ChartJS = module.default || module.Chart\n return ChartJS\n })\n .catch((err) => {\n chartJSLoadPromise = null\n throw err\n })\n }\n\n return chartJSLoadPromise\n}\n\n/**\n * Check if Chart.js is available\n */\nexport async function isChartJSAvailable(): Promise<boolean> {\n try {\n await loadChartJS()\n return true\n } catch {\n return false\n }\n}\n\nexport interface ChartJSRendererProps {\n /**\n * UIComponent with chart params\n */\n component: UIComponent\n\n /**\n * Error callback\n */\n onError?: (error: Error) => void\n\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible'\n}\n\n/**\n * Native Chart.js renderer component\n *\n * @example\n * ```tsx\n * const chartComponent: UIComponent = {\n * id: 'revenue-chart',\n * type: 'chart',\n * position: { colStart: 1, colSpan: 6 },\n * params: {\n * type: 'bar',\n * title: 'Monthly Revenue',\n * data: {\n * labels: ['Jan', 'Feb', 'Mar'],\n * datasets: [{ label: 'Revenue', data: [100, 200, 150] }]\n * },\n * renderer: 'native',\n * },\n * }\n * <ChartJSRenderer component={chartComponent} />\n * ```\n */\nexport const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {\n const [isLoading, setIsLoading] = createSignal(true)\n const [error, setError] = createSignal<string>()\n let canvasRef: HTMLCanvasElement | undefined\n let chartInstance: any\n\n const params = () => props.component.params as ChartComponentParams\n const isExpanded = useExpanded()\n\n // v6.1.0 — export visibility :\n // - undefined / true → button shown (new default, was opt-in)\n // - false → button hidden (explicit opt-out, unchanged)\n const exportEnabled = () => params().exportable !== false\n\n // v6.1.0 — copy data for the ExpandableWrapper modal-header copy button.\n // Lazy-stringified each time the button is clicked.\n const copyDataJSON = () => JSON.stringify({ type: params().type, data: params().data }, null, 2)\n\n // Chart PNG export\n const handleExportPNG = () => {\n if (!canvasRef) return\n const url = canvasRef.toDataURL('image/png')\n const a = document.createElement('a')\n a.href = url\n a.download = `${(params().title || 'chart').replace(/\\s+/g, '-').toLowerCase()}.png`\n a.click()\n }\n\n // Create/update chart when params change\n createEffect(async () => {\n if (!canvasRef) return\n\n // Access params to track dependencies\n const chartParams = params()\n\n setIsLoading(true)\n setError(undefined)\n\n try {\n const Chart = await loadChartJS()\n\n // Destroy previous instance\n if (chartInstance) {\n chartInstance.destroy()\n chartInstance = null\n }\n\n // Build options, merging time-axis config if present (v3.1.0)\n const baseOptions: any = {\n responsive: true,\n maintainAspectRatio: false,\n ...chartParams.options,\n plugins: {\n ...chartParams.options?.plugins,\n legend: {\n display: true,\n position: 'bottom',\n ...chartParams.options?.plugins?.legend,\n },\n },\n }\n\n // Time-series axis (v3.1.0)\n if (chartParams.timeAxis) {\n const ta = chartParams.timeAxis\n baseOptions.scales = {\n ...baseOptions.scales,\n x: {\n ...baseOptions.scales?.x,\n type: 'time',\n time: {\n parser: ta.parser,\n unit: ta.unit,\n tooltipFormat: ta.tooltipFormat,\n },\n ...(ta.min ? { min: ta.min } : {}),\n ...(ta.max ? { max: ta.max } : {}),\n },\n }\n }\n\n // Create new chart\n chartInstance = new Chart(canvasRef, {\n type: chartParams.type,\n data: chartParams.data,\n options: baseOptions,\n })\n\n setIsLoading(false)\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Chart rendering failed')\n setError(error.message)\n setIsLoading(false)\n props.onError?.(error)\n }\n })\n\n // Cleanup on unmount\n onCleanup(() => {\n if (chartInstance) {\n chartInstance.destroy()\n chartInstance = null\n }\n })\n\n return (\n <ExpandableWrapper\n title={params().title || 'Chart'}\n copyData={copyDataJSON()}\n copyLabel=\"Copy chart data (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div class={`relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}>\n <Show when={params().title || exportEnabled()}>\n <div class=\"flex items-center justify-between mb-3 flex-shrink-0\">\n <Show when={params().title}>\n <h3 class=\"text-sm font-semibold text-gray-900 dark:text-white\">\n {params().title}\n </h3>\n </Show>\n <Show when={exportEnabled()}>\n <button\n onClick={handleExportPNG}\n class=\"opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm\"\n title=\"Download PNG\"\n aria-label=\"Download chart as PNG\"\n >\n <svg class=\"w-3 h-3 text-gray-500 dark:text-gray-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n </button>\n </Show>\n </div>\n </Show>\n\n <Show when={isLoading()}>\n <div class=\"absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80\">\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600\" />\n <span class=\"text-sm text-gray-500 dark:text-gray-400\">Loading chart...</span>\n </div>\n </div>\n </Show>\n\n <Show when={error()}>\n <div class=\"absolute inset-0 flex items-center justify-center p-4 bg-white dark:bg-gray-800\">\n <div class=\"text-center\">\n <div class=\"inline-flex items-center justify-center w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/20 mb-3\">\n <svg\n class=\"w-6 h-6 text-red-600 dark:text-red-400\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <p class=\"text-red-600 dark:text-red-400 text-sm font-medium\">Chart Error</p>\n <p class=\"text-gray-600 dark:text-gray-400 text-xs mt-1 max-w-xs\">{error()}</p>\n </div>\n </div>\n </Show>\n\n <div\n class={`w-full ${isExpanded() ? 'flex-1 min-h-0' : ''}`}\n style={\n error()\n ? { display: 'none' }\n : isExpanded()\n ? { height: '100%', display: 'block' }\n : { height: params().height || '250px', display: 'block' }\n }\n >\n <canvas ref={canvasRef} />\n </div>\n </div>\n </ExpandableWrapper>\n )\n}\n"],"names":["ChartJS","chartJSLoadPromise","loadChartJS","then","module","default","Chart","catch","err","isChartJSAvailable","ChartJSRenderer","props","isLoading","setIsLoading","createSignal","error","setError","canvasRef","chartInstance","params","component","isExpanded","useExpanded","exportEnabled","exportable","copyDataJSON","JSON","stringify","type","data","handleExportPNG","url","toDataURL","a","document","createElement","href","download","title","replace","toLowerCase","click","createEffect","chartParams","undefined","destroy","baseOptions","responsive","maintainAspectRatio","options","plugins","legend","display","position","timeAxis","ta","scales","x","time","parser","unit","tooltipFormat","min","max","Error","message","onError","onCleanup","_$createComponent","ExpandableWrapper","copyData","copyLabel","toolbarVariant","children","_el$","_$getNextElement","_tmpl$6","_el$15","firstChild","_el$16","_co$3","_$getNextMarker","nextSibling","_el$17","_el$18","_co$4","_el$19","_el$20","_co$5","_el$13","_el$14","_$insert","Show","when","_el$2","_tmpl$3","_el$5","_el$6","_co$","_el$7","_el$8","_co$2","_el$3","_tmpl$","_el$4","_tmpl$2","$$click","_$runHydrationEvents","_tmpl$4","_el$0","_tmpl$5","_el$1","_el$10","_el$11","_el$12","_ref$","_$use","_$effect","_p$","_v$","_v$2","_v$3","height","e","_$className","t","_$style","_$delegateEvents"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChartJSRenderer.cjs","sources":["../../src/components/ChartJSRenderer.tsx"],"sourcesContent":["/**\n * ChartJSRenderer - Native Chart.js rendering\n * Sprint 4: State & Charts\n *\n * Requires chart.js peer dependency:\n * ```\n * pnpm add chart.js\n * ```\n */\n\nimport { Component, createEffect, onCleanup, createSignal, Show } from 'solid-js';\nimport type { UIComponent, ChartComponentParams } from '../types';\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper';\nimport { DegradedFallback } from './DegradedFallback';\nimport { chartToDegradedTable } from '../utils/degraded-projections';\nimport { useTelemetry } from '../context/MCPUITelemetryContext';\n\n// Lazy load Chart.js to avoid bundling if not used\nlet ChartJS: any = null;\nlet chartJSLoadPromise: Promise<any> | null = null;\n\nconst loadChartJS = async () => {\n if (ChartJS) return ChartJS;\n\n if (!chartJSLoadPromise) {\n chartJSLoadPromise = import('chart.js/auto')\n .then((module) => {\n ChartJS = module.default || module.Chart;\n return ChartJS;\n })\n .catch((err) => {\n chartJSLoadPromise = null;\n throw err;\n });\n }\n\n return chartJSLoadPromise;\n};\n\n/**\n * Check if Chart.js is available\n */\nexport async function isChartJSAvailable(): Promise<boolean> {\n try {\n await loadChartJS();\n return true;\n } catch {\n return false;\n }\n}\n\nexport interface ChartJSRendererProps {\n /**\n * UIComponent with chart params\n */\n component: UIComponent;\n\n /**\n * Error callback\n */\n onError?: (error: Error) => void;\n\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible';\n}\n\n/**\n * Native Chart.js renderer component\n *\n * @example\n * ```tsx\n * const chartComponent: UIComponent = {\n * id: 'revenue-chart',\n * type: 'chart',\n * position: { colStart: 1, colSpan: 6 },\n * params: {\n * type: 'bar',\n * title: 'Monthly Revenue',\n * data: {\n * labels: ['Jan', 'Feb', 'Mar'],\n * datasets: [{ label: 'Revenue', data: [100, 200, 150] }]\n * },\n * renderer: 'native',\n * },\n * }\n * <ChartJSRenderer component={chartComponent} />\n * ```\n */\nexport const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {\n const [isLoading, setIsLoading] = createSignal(true);\n const [error, setError] = createSignal<string>();\n let canvasRef: HTMLCanvasElement | undefined;\n let chartInstance: any;\n\n const params = () => props.component.params as ChartComponentParams;\n const isExpanded = useExpanded();\n const telemetry = useTelemetry();\n\n // v6.1.0 — export visibility :\n // - undefined / true → button shown (new default, was opt-in)\n // - false → button hidden (explicit opt-out, unchanged)\n const exportEnabled = () => params().exportable !== false;\n\n // v6.1.0 — copy data for the ExpandableWrapper modal-header copy button.\n // Lazy-stringified each time the button is clicked.\n const copyDataJSON = () => JSON.stringify({ type: params().type, data: params().data }, null, 2);\n\n // Chart PNG export\n const handleExportPNG = () => {\n if (!canvasRef) return;\n const url = canvasRef.toDataURL('image/png');\n const a = document.createElement('a');\n a.href = url;\n a.download = `${(params().title || 'chart').replace(/\\s+/g, '-').toLowerCase()}.png`;\n a.click();\n };\n\n // Create/update chart when params change\n createEffect(async () => {\n if (!canvasRef) return;\n\n // Access params to track dependencies\n const chartParams = params();\n\n setIsLoading(true);\n setError(undefined);\n\n try {\n const Chart = await loadChartJS();\n\n // Destroy previous instance\n if (chartInstance) {\n chartInstance.destroy();\n chartInstance = null;\n }\n\n // Build options, merging time-axis config if present (v3.1.0)\n const baseOptions: any = {\n responsive: true,\n maintainAspectRatio: false,\n ...chartParams.options,\n plugins: {\n ...chartParams.options?.plugins,\n legend: {\n display: true,\n position: 'bottom',\n ...chartParams.options?.plugins?.legend,\n },\n },\n };\n\n // Time-series axis (v3.1.0)\n if (chartParams.timeAxis) {\n const ta = chartParams.timeAxis;\n baseOptions.scales = {\n ...baseOptions.scales,\n x: {\n ...baseOptions.scales?.x,\n type: 'time',\n time: {\n parser: ta.parser,\n unit: ta.unit,\n tooltipFormat: ta.tooltipFormat,\n },\n ...(ta.min ? { min: ta.min } : {}),\n ...(ta.max ? { max: ta.max } : {}),\n },\n };\n }\n\n // Create new chart\n chartInstance = new Chart(canvasRef, {\n type: chartParams.type,\n data: chartParams.data,\n options: baseOptions,\n });\n\n setIsLoading(false);\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Chart rendering failed');\n setError(error.message);\n setIsLoading(false);\n // Fallback ladder (P2.5): record the failure so it's observable, then\n // degrade to the series table below instead of a blank canvas.\n telemetry?.dispatch({\n type: 'render:error',\n errorMessage: error.message,\n id: props.component?.id ?? '',\n componentType: 'chart',\n ts: Date.now(),\n });\n props.onError?.(error);\n }\n });\n\n // Cleanup on unmount\n onCleanup(() => {\n if (chartInstance) {\n chartInstance.destroy();\n chartInstance = null;\n }\n });\n\n return (\n <ExpandableWrapper\n title={params().title || 'Chart'}\n copyData={copyDataJSON()}\n copyLabel=\"Copy chart data (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div\n class={`relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}\n >\n <Show when={params().title || exportEnabled()}>\n <div class=\"flex items-center justify-between mb-3 flex-shrink-0\">\n <Show when={params().title}>\n <h3 class=\"text-sm font-semibold text-gray-900 dark:text-white\">{params().title}</h3>\n </Show>\n <Show when={exportEnabled()}>\n <button\n onClick={handleExportPNG}\n class=\"opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm\"\n title=\"Download PNG\"\n aria-label=\"Download chart as PNG\"\n >\n <svg\n class=\"w-3 h-3 text-gray-500 dark:text-gray-400\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"\n />\n </svg>\n </button>\n </Show>\n </div>\n </Show>\n\n <Show when={isLoading()}>\n <div class=\"absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80\">\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600\" />\n <span class=\"text-sm text-gray-500 dark:text-gray-400\">Loading chart...</span>\n </div>\n </div>\n </Show>\n\n {/* Fallback ladder (P2.5): degrade to a series table on render error\n instead of a bare \"Chart Error\" message. */}\n <Show when={error()}>\n <DegradedFallback\n message={`Chart rendering failed: ${error()}`}\n caption=\"Showing the chart data as a table — the interactive chart is unavailable.\"\n {...chartToDegradedTable(params() ?? {})}\n />\n </Show>\n\n <div\n class={`w-full ${isExpanded() ? 'flex-1 min-h-0' : ''}`}\n style={\n error()\n ? { display: 'none' }\n : isExpanded()\n ? { height: '100%', display: 'block' }\n : { height: params().height || '250px', display: 'block' }\n }\n >\n <canvas ref={canvasRef} />\n </div>\n </div>\n </ExpandableWrapper>\n );\n};\n"],"names":["ChartJS","chartJSLoadPromise","loadChartJS","then","module","default","Chart","catch","err","isChartJSAvailable","ChartJSRenderer","props","isLoading","setIsLoading","createSignal","error","setError","canvasRef","chartInstance","params","component","isExpanded","useExpanded","telemetry","useTelemetry","exportEnabled","exportable","copyDataJSON","JSON","stringify","type","data","handleExportPNG","url","toDataURL","a","document","createElement","href","download","title","replace","toLowerCase","click","createEffect","chartParams","undefined","destroy","baseOptions","responsive","maintainAspectRatio","options","plugins","legend","display","position","timeAxis","ta","scales","x","time","parser","unit","tooltipFormat","min","max","Error","message","dispatch","errorMessage","id","componentType","ts","Date","now","onError","onCleanup","_$createComponent","ExpandableWrapper","copyData","copyLabel","toolbarVariant","children","_el$","_$getNextElement","_tmpl$5","_el$10","firstChild","_el$11","_co$3","_$getNextMarker","nextSibling","_el$12","_el$13","_co$4","_el$14","_el$15","_co$5","_el$0","_el$1","_$insert","Show","when","_el$2","_tmpl$3","_el$5","_el$6","_co$","_el$7","_el$8","_co$2","_el$3","_tmpl$","_el$4","_tmpl$2","$$click","_$runHydrationEvents","_tmpl$4","DegradedFallback","_$mergeProps","caption","chartToDegradedTable","_ref$","_$use","_$effect","_p$","_v$","_v$2","_v$3","height","e","_$className","t","_$style","_$delegateEvents"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartJSRenderer.d.ts","sourceRoot":"","sources":["../../src/components/ChartJSRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAA+C,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"ChartJSRenderer.d.ts","sourceRoot":"","sources":["../../src/components/ChartJSRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAA+C,MAAM,UAAU,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAAwB,MAAM,UAAU,CAAC;AA4BlE;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO3D;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,SAAS,EAAE,WAAW,CAAC;IAEvB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC;CAC7C;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,CAAC,oBAAoB,CA+L3D,CAAC"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { delegateEvents, createComponent, getNextElement, template, getNextMarker, insert, runHydrationEvents, effect, className, style, use } from "solid-js/web";
|
|
1
|
+
import { delegateEvents, createComponent, getNextElement, template, getNextMarker, insert, runHydrationEvents, mergeProps, effect, className, style, use } from "solid-js/web";
|
|
2
2
|
import { createSignal, createEffect, onCleanup, Show } from "solid-js";
|
|
3
3
|
import { useExpanded, ExpandableWrapper } from "./ExpandableWrapper.js";
|
|
4
|
-
|
|
4
|
+
import { DegradedFallback } from "./DegradedFallback.js";
|
|
5
|
+
import { chartToDegradedTable } from "../utils/degraded-projections.js";
|
|
6
|
+
import { useTelemetry } from "../context/MCPUITelemetryContext.js";
|
|
7
|
+
var _tmpl$ = /* @__PURE__ */ template(`<h3 class="text-sm font-semibold text-gray-900 dark:text-white">`), _tmpl$2 = /* @__PURE__ */ template(`<button class="opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"title="Download PNG"aria-label="Download chart as PNG"><svg class="w-3 h-3 text-gray-500 dark:text-gray-400"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">`), _tmpl$3 = /* @__PURE__ */ template(`<div class="flex items-center justify-between mb-3 flex-shrink-0"><!$><!/><!$><!/>`), _tmpl$4 = /* @__PURE__ */ template(`<div class="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80"><div class="flex flex-col items-center gap-2"><div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div><span class="text-sm text-gray-500 dark:text-gray-400">Loading chart...`), _tmpl$5 = /* @__PURE__ */ template(`<div><!$><!/><!$><!/><!$><!/><div><canvas>`);
|
|
5
8
|
let ChartJS = null;
|
|
6
9
|
let chartJSLoadPromise = null;
|
|
7
10
|
const loadChartJS = async () => {
|
|
@@ -32,6 +35,7 @@ const ChartJSRenderer = (props) => {
|
|
|
32
35
|
let chartInstance;
|
|
33
36
|
const params = () => props.component.params;
|
|
34
37
|
const isExpanded = useExpanded();
|
|
38
|
+
const telemetry = useTelemetry();
|
|
35
39
|
const exportEnabled = () => params().exportable !== false;
|
|
36
40
|
const copyDataJSON = () => JSON.stringify({
|
|
37
41
|
type: params().type,
|
|
@@ -46,7 +50,7 @@ const ChartJSRenderer = (props) => {
|
|
|
46
50
|
a.click();
|
|
47
51
|
};
|
|
48
52
|
createEffect(async () => {
|
|
49
|
-
var _a, _b, _c, _d, _e;
|
|
53
|
+
var _a, _b, _c, _d, _e, _f;
|
|
50
54
|
if (!canvasRef) return;
|
|
51
55
|
const chartParams = params();
|
|
52
56
|
setIsLoading(true);
|
|
@@ -101,7 +105,14 @@ const ChartJSRenderer = (props) => {
|
|
|
101
105
|
const error2 = err instanceof Error ? err : new Error("Chart rendering failed");
|
|
102
106
|
setError(error2.message);
|
|
103
107
|
setIsLoading(false);
|
|
104
|
-
|
|
108
|
+
telemetry == null ? void 0 : telemetry.dispatch({
|
|
109
|
+
type: "render:error",
|
|
110
|
+
errorMessage: error2.message,
|
|
111
|
+
id: ((_e = props.component) == null ? void 0 : _e.id) ?? "",
|
|
112
|
+
componentType: "chart",
|
|
113
|
+
ts: Date.now()
|
|
114
|
+
});
|
|
115
|
+
(_f = props.onError) == null ? void 0 : _f.call(props, error2);
|
|
105
116
|
}
|
|
106
117
|
});
|
|
107
118
|
onCleanup(() => {
|
|
@@ -122,7 +133,7 @@ const ChartJSRenderer = (props) => {
|
|
|
122
133
|
return props.toolbarVariant;
|
|
123
134
|
},
|
|
124
135
|
get children() {
|
|
125
|
-
var _el$ = getNextElement(_tmpl$
|
|
136
|
+
var _el$ = getNextElement(_tmpl$5), _el$10 = _el$.firstChild, [_el$11, _co$3] = getNextMarker(_el$10.nextSibling), _el$12 = _el$11.nextSibling, [_el$13, _co$4] = getNextMarker(_el$12.nextSibling), _el$14 = _el$13.nextSibling, [_el$15, _co$5] = getNextMarker(_el$14.nextSibling), _el$0 = _el$15.nextSibling, _el$1 = _el$0.firstChild;
|
|
126
137
|
insert(_el$, createComponent(Show, {
|
|
127
138
|
get when() {
|
|
128
139
|
return params().title || exportEnabled();
|
|
@@ -152,7 +163,7 @@ const ChartJSRenderer = (props) => {
|
|
|
152
163
|
}), _el$8, _co$2);
|
|
153
164
|
return _el$2;
|
|
154
165
|
}
|
|
155
|
-
}), _el$
|
|
166
|
+
}), _el$11, _co$3);
|
|
156
167
|
insert(_el$, createComponent(Show, {
|
|
157
168
|
get when() {
|
|
158
169
|
return isLoading();
|
|
@@ -160,19 +171,22 @@ const ChartJSRenderer = (props) => {
|
|
|
160
171
|
get children() {
|
|
161
172
|
return getNextElement(_tmpl$4);
|
|
162
173
|
}
|
|
163
|
-
}), _el$
|
|
174
|
+
}), _el$13, _co$4);
|
|
164
175
|
insert(_el$, createComponent(Show, {
|
|
165
176
|
get when() {
|
|
166
177
|
return error();
|
|
167
178
|
},
|
|
168
179
|
get children() {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
180
|
+
return createComponent(DegradedFallback, mergeProps({
|
|
181
|
+
get message() {
|
|
182
|
+
return `Chart rendering failed: ${error()}`;
|
|
183
|
+
},
|
|
184
|
+
caption: "Showing the chart data as a table — the interactive chart is unavailable."
|
|
185
|
+
}, () => chartToDegradedTable(params() ?? {})));
|
|
172
186
|
}
|
|
173
|
-
}), _el$
|
|
187
|
+
}), _el$15, _co$5);
|
|
174
188
|
var _ref$ = canvasRef;
|
|
175
|
-
typeof _ref$ === "function" ? use(_ref$, _el$
|
|
189
|
+
typeof _ref$ === "function" ? use(_ref$, _el$1) : canvasRef = _el$1;
|
|
176
190
|
effect((_p$) => {
|
|
177
191
|
var _v$ = `relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${isExpanded() ? "flex-1 min-h-0 flex flex-col" : ""}`, _v$2 = `w-full ${isExpanded() ? "flex-1 min-h-0" : ""}`, _v$3 = error() ? {
|
|
178
192
|
display: "none"
|
|
@@ -184,8 +198,8 @@ const ChartJSRenderer = (props) => {
|
|
|
184
198
|
display: "block"
|
|
185
199
|
};
|
|
186
200
|
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
|
|
187
|
-
_v$2 !== _p$.t && className(_el$
|
|
188
|
-
_p$.a = style(_el$
|
|
201
|
+
_v$2 !== _p$.t && className(_el$0, _p$.t = _v$2);
|
|
202
|
+
_p$.a = style(_el$0, _v$3, _p$.a);
|
|
189
203
|
return _p$;
|
|
190
204
|
}, {
|
|
191
205
|
e: void 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartJSRenderer.js","sources":["../../src/components/ChartJSRenderer.tsx"],"sourcesContent":["/**\n * ChartJSRenderer - Native Chart.js rendering\n * Sprint 4: State & Charts\n *\n * Requires chart.js peer dependency:\n * ```\n * pnpm add chart.js\n * ```\n */\n\nimport { Component, createEffect, onCleanup, createSignal, Show } from 'solid-js'\nimport type { UIComponent, ChartComponentParams } from '../types'\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper'\n\n// Lazy load Chart.js to avoid bundling if not used\nlet ChartJS: any = null\nlet chartJSLoadPromise: Promise<any> | null = null\n\nconst loadChartJS = async () => {\n if (ChartJS) return ChartJS\n\n if (!chartJSLoadPromise) {\n chartJSLoadPromise = import('chart.js/auto')\n .then((module) => {\n ChartJS = module.default || module.Chart\n return ChartJS\n })\n .catch((err) => {\n chartJSLoadPromise = null\n throw err\n })\n }\n\n return chartJSLoadPromise\n}\n\n/**\n * Check if Chart.js is available\n */\nexport async function isChartJSAvailable(): Promise<boolean> {\n try {\n await loadChartJS()\n return true\n } catch {\n return false\n }\n}\n\nexport interface ChartJSRendererProps {\n /**\n * UIComponent with chart params\n */\n component: UIComponent\n\n /**\n * Error callback\n */\n onError?: (error: Error) => void\n\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible'\n}\n\n/**\n * Native Chart.js renderer component\n *\n * @example\n * ```tsx\n * const chartComponent: UIComponent = {\n * id: 'revenue-chart',\n * type: 'chart',\n * position: { colStart: 1, colSpan: 6 },\n * params: {\n * type: 'bar',\n * title: 'Monthly Revenue',\n * data: {\n * labels: ['Jan', 'Feb', 'Mar'],\n * datasets: [{ label: 'Revenue', data: [100, 200, 150] }]\n * },\n * renderer: 'native',\n * },\n * }\n * <ChartJSRenderer component={chartComponent} />\n * ```\n */\nexport const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {\n const [isLoading, setIsLoading] = createSignal(true)\n const [error, setError] = createSignal<string>()\n let canvasRef: HTMLCanvasElement | undefined\n let chartInstance: any\n\n const params = () => props.component.params as ChartComponentParams\n const isExpanded = useExpanded()\n\n // v6.1.0 — export visibility :\n // - undefined / true → button shown (new default, was opt-in)\n // - false → button hidden (explicit opt-out, unchanged)\n const exportEnabled = () => params().exportable !== false\n\n // v6.1.0 — copy data for the ExpandableWrapper modal-header copy button.\n // Lazy-stringified each time the button is clicked.\n const copyDataJSON = () => JSON.stringify({ type: params().type, data: params().data }, null, 2)\n\n // Chart PNG export\n const handleExportPNG = () => {\n if (!canvasRef) return\n const url = canvasRef.toDataURL('image/png')\n const a = document.createElement('a')\n a.href = url\n a.download = `${(params().title || 'chart').replace(/\\s+/g, '-').toLowerCase()}.png`\n a.click()\n }\n\n // Create/update chart when params change\n createEffect(async () => {\n if (!canvasRef) return\n\n // Access params to track dependencies\n const chartParams = params()\n\n setIsLoading(true)\n setError(undefined)\n\n try {\n const Chart = await loadChartJS()\n\n // Destroy previous instance\n if (chartInstance) {\n chartInstance.destroy()\n chartInstance = null\n }\n\n // Build options, merging time-axis config if present (v3.1.0)\n const baseOptions: any = {\n responsive: true,\n maintainAspectRatio: false,\n ...chartParams.options,\n plugins: {\n ...chartParams.options?.plugins,\n legend: {\n display: true,\n position: 'bottom',\n ...chartParams.options?.plugins?.legend,\n },\n },\n }\n\n // Time-series axis (v3.1.0)\n if (chartParams.timeAxis) {\n const ta = chartParams.timeAxis\n baseOptions.scales = {\n ...baseOptions.scales,\n x: {\n ...baseOptions.scales?.x,\n type: 'time',\n time: {\n parser: ta.parser,\n unit: ta.unit,\n tooltipFormat: ta.tooltipFormat,\n },\n ...(ta.min ? { min: ta.min } : {}),\n ...(ta.max ? { max: ta.max } : {}),\n },\n }\n }\n\n // Create new chart\n chartInstance = new Chart(canvasRef, {\n type: chartParams.type,\n data: chartParams.data,\n options: baseOptions,\n })\n\n setIsLoading(false)\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Chart rendering failed')\n setError(error.message)\n setIsLoading(false)\n props.onError?.(error)\n }\n })\n\n // Cleanup on unmount\n onCleanup(() => {\n if (chartInstance) {\n chartInstance.destroy()\n chartInstance = null\n }\n })\n\n return (\n <ExpandableWrapper\n title={params().title || 'Chart'}\n copyData={copyDataJSON()}\n copyLabel=\"Copy chart data (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div class={`relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}>\n <Show when={params().title || exportEnabled()}>\n <div class=\"flex items-center justify-between mb-3 flex-shrink-0\">\n <Show when={params().title}>\n <h3 class=\"text-sm font-semibold text-gray-900 dark:text-white\">\n {params().title}\n </h3>\n </Show>\n <Show when={exportEnabled()}>\n <button\n onClick={handleExportPNG}\n class=\"opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm\"\n title=\"Download PNG\"\n aria-label=\"Download chart as PNG\"\n >\n <svg class=\"w-3 h-3 text-gray-500 dark:text-gray-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n </button>\n </Show>\n </div>\n </Show>\n\n <Show when={isLoading()}>\n <div class=\"absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80\">\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600\" />\n <span class=\"text-sm text-gray-500 dark:text-gray-400\">Loading chart...</span>\n </div>\n </div>\n </Show>\n\n <Show when={error()}>\n <div class=\"absolute inset-0 flex items-center justify-center p-4 bg-white dark:bg-gray-800\">\n <div class=\"text-center\">\n <div class=\"inline-flex items-center justify-center w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/20 mb-3\">\n <svg\n class=\"w-6 h-6 text-red-600 dark:text-red-400\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <p class=\"text-red-600 dark:text-red-400 text-sm font-medium\">Chart Error</p>\n <p class=\"text-gray-600 dark:text-gray-400 text-xs mt-1 max-w-xs\">{error()}</p>\n </div>\n </div>\n </Show>\n\n <div\n class={`w-full ${isExpanded() ? 'flex-1 min-h-0' : ''}`}\n style={\n error()\n ? { display: 'none' }\n : isExpanded()\n ? { height: '100%', display: 'block' }\n : { height: params().height || '250px', display: 'block' }\n }\n >\n <canvas ref={canvasRef} />\n </div>\n </div>\n </ExpandableWrapper>\n )\n}\n"],"names":["ChartJS","chartJSLoadPromise","loadChartJS","then","module","default","Chart","catch","err","isChartJSAvailable","ChartJSRenderer","props","isLoading","setIsLoading","createSignal","error","setError","canvasRef","chartInstance","params","component","isExpanded","useExpanded","exportEnabled","exportable","copyDataJSON","JSON","stringify","type","data","handleExportPNG","url","toDataURL","a","document","createElement","href","download","title","replace","toLowerCase","click","createEffect","chartParams","undefined","destroy","baseOptions","responsive","maintainAspectRatio","options","plugins","legend","display","position","timeAxis","ta","scales","x","time","parser","unit","tooltipFormat","min","max","Error","message","onError","onCleanup","_$createComponent","ExpandableWrapper","copyData","copyLabel","toolbarVariant","children","_el$","_$getNextElement","_tmpl$6","_el$15","firstChild","_el$16","_co$3","_$getNextMarker","nextSibling","_el$17","_el$18","_co$4","_el$19","_el$20","_co$5","_el$13","_el$14","_$insert","Show","when","_el$2","_tmpl$3","_el$5","_el$6","_co$","_el$7","_el$8","_co$2","_el$3","_tmpl$","_el$4","_tmpl$2","$$click","_$runHydrationEvents","_tmpl$4","_el$0","_tmpl$5","_el$1","_el$10","_el$11","_el$12","_ref$","_$use","_$effect","_p$","_v$","_v$2","_v$3","height","e","_$className","t","_$style","_$delegateEvents"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChartJSRenderer.js","sources":["../../src/components/ChartJSRenderer.tsx"],"sourcesContent":["/**\n * ChartJSRenderer - Native Chart.js rendering\n * Sprint 4: State & Charts\n *\n * Requires chart.js peer dependency:\n * ```\n * pnpm add chart.js\n * ```\n */\n\nimport { Component, createEffect, onCleanup, createSignal, Show } from 'solid-js';\nimport type { UIComponent, ChartComponentParams } from '../types';\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper';\nimport { DegradedFallback } from './DegradedFallback';\nimport { chartToDegradedTable } from '../utils/degraded-projections';\nimport { useTelemetry } from '../context/MCPUITelemetryContext';\n\n// Lazy load Chart.js to avoid bundling if not used\nlet ChartJS: any = null;\nlet chartJSLoadPromise: Promise<any> | null = null;\n\nconst loadChartJS = async () => {\n if (ChartJS) return ChartJS;\n\n if (!chartJSLoadPromise) {\n chartJSLoadPromise = import('chart.js/auto')\n .then((module) => {\n ChartJS = module.default || module.Chart;\n return ChartJS;\n })\n .catch((err) => {\n chartJSLoadPromise = null;\n throw err;\n });\n }\n\n return chartJSLoadPromise;\n};\n\n/**\n * Check if Chart.js is available\n */\nexport async function isChartJSAvailable(): Promise<boolean> {\n try {\n await loadChartJS();\n return true;\n } catch {\n return false;\n }\n}\n\nexport interface ChartJSRendererProps {\n /**\n * UIComponent with chart params\n */\n component: UIComponent;\n\n /**\n * Error callback\n */\n onError?: (error: Error) => void;\n\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible';\n}\n\n/**\n * Native Chart.js renderer component\n *\n * @example\n * ```tsx\n * const chartComponent: UIComponent = {\n * id: 'revenue-chart',\n * type: 'chart',\n * position: { colStart: 1, colSpan: 6 },\n * params: {\n * type: 'bar',\n * title: 'Monthly Revenue',\n * data: {\n * labels: ['Jan', 'Feb', 'Mar'],\n * datasets: [{ label: 'Revenue', data: [100, 200, 150] }]\n * },\n * renderer: 'native',\n * },\n * }\n * <ChartJSRenderer component={chartComponent} />\n * ```\n */\nexport const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {\n const [isLoading, setIsLoading] = createSignal(true);\n const [error, setError] = createSignal<string>();\n let canvasRef: HTMLCanvasElement | undefined;\n let chartInstance: any;\n\n const params = () => props.component.params as ChartComponentParams;\n const isExpanded = useExpanded();\n const telemetry = useTelemetry();\n\n // v6.1.0 — export visibility :\n // - undefined / true → button shown (new default, was opt-in)\n // - false → button hidden (explicit opt-out, unchanged)\n const exportEnabled = () => params().exportable !== false;\n\n // v6.1.0 — copy data for the ExpandableWrapper modal-header copy button.\n // Lazy-stringified each time the button is clicked.\n const copyDataJSON = () => JSON.stringify({ type: params().type, data: params().data }, null, 2);\n\n // Chart PNG export\n const handleExportPNG = () => {\n if (!canvasRef) return;\n const url = canvasRef.toDataURL('image/png');\n const a = document.createElement('a');\n a.href = url;\n a.download = `${(params().title || 'chart').replace(/\\s+/g, '-').toLowerCase()}.png`;\n a.click();\n };\n\n // Create/update chart when params change\n createEffect(async () => {\n if (!canvasRef) return;\n\n // Access params to track dependencies\n const chartParams = params();\n\n setIsLoading(true);\n setError(undefined);\n\n try {\n const Chart = await loadChartJS();\n\n // Destroy previous instance\n if (chartInstance) {\n chartInstance.destroy();\n chartInstance = null;\n }\n\n // Build options, merging time-axis config if present (v3.1.0)\n const baseOptions: any = {\n responsive: true,\n maintainAspectRatio: false,\n ...chartParams.options,\n plugins: {\n ...chartParams.options?.plugins,\n legend: {\n display: true,\n position: 'bottom',\n ...chartParams.options?.plugins?.legend,\n },\n },\n };\n\n // Time-series axis (v3.1.0)\n if (chartParams.timeAxis) {\n const ta = chartParams.timeAxis;\n baseOptions.scales = {\n ...baseOptions.scales,\n x: {\n ...baseOptions.scales?.x,\n type: 'time',\n time: {\n parser: ta.parser,\n unit: ta.unit,\n tooltipFormat: ta.tooltipFormat,\n },\n ...(ta.min ? { min: ta.min } : {}),\n ...(ta.max ? { max: ta.max } : {}),\n },\n };\n }\n\n // Create new chart\n chartInstance = new Chart(canvasRef, {\n type: chartParams.type,\n data: chartParams.data,\n options: baseOptions,\n });\n\n setIsLoading(false);\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Chart rendering failed');\n setError(error.message);\n setIsLoading(false);\n // Fallback ladder (P2.5): record the failure so it's observable, then\n // degrade to the series table below instead of a blank canvas.\n telemetry?.dispatch({\n type: 'render:error',\n errorMessage: error.message,\n id: props.component?.id ?? '',\n componentType: 'chart',\n ts: Date.now(),\n });\n props.onError?.(error);\n }\n });\n\n // Cleanup on unmount\n onCleanup(() => {\n if (chartInstance) {\n chartInstance.destroy();\n chartInstance = null;\n }\n });\n\n return (\n <ExpandableWrapper\n title={params().title || 'Chart'}\n copyData={copyDataJSON()}\n copyLabel=\"Copy chart data (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div\n class={`relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}\n >\n <Show when={params().title || exportEnabled()}>\n <div class=\"flex items-center justify-between mb-3 flex-shrink-0\">\n <Show when={params().title}>\n <h3 class=\"text-sm font-semibold text-gray-900 dark:text-white\">{params().title}</h3>\n </Show>\n <Show when={exportEnabled()}>\n <button\n onClick={handleExportPNG}\n class=\"opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm\"\n title=\"Download PNG\"\n aria-label=\"Download chart as PNG\"\n >\n <svg\n class=\"w-3 h-3 text-gray-500 dark:text-gray-400\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"\n />\n </svg>\n </button>\n </Show>\n </div>\n </Show>\n\n <Show when={isLoading()}>\n <div class=\"absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80\">\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600\" />\n <span class=\"text-sm text-gray-500 dark:text-gray-400\">Loading chart...</span>\n </div>\n </div>\n </Show>\n\n {/* Fallback ladder (P2.5): degrade to a series table on render error\n instead of a bare \"Chart Error\" message. */}\n <Show when={error()}>\n <DegradedFallback\n message={`Chart rendering failed: ${error()}`}\n caption=\"Showing the chart data as a table — the interactive chart is unavailable.\"\n {...chartToDegradedTable(params() ?? {})}\n />\n </Show>\n\n <div\n class={`w-full ${isExpanded() ? 'flex-1 min-h-0' : ''}`}\n style={\n error()\n ? { display: 'none' }\n : isExpanded()\n ? { height: '100%', display: 'block' }\n : { height: params().height || '250px', display: 'block' }\n }\n >\n <canvas ref={canvasRef} />\n </div>\n </div>\n </ExpandableWrapper>\n );\n};\n"],"names":["ChartJS","chartJSLoadPromise","loadChartJS","then","module","default","Chart","catch","err","isChartJSAvailable","ChartJSRenderer","props","isLoading","setIsLoading","createSignal","error","setError","canvasRef","chartInstance","params","component","isExpanded","useExpanded","telemetry","useTelemetry","exportEnabled","exportable","copyDataJSON","JSON","stringify","type","data","handleExportPNG","url","toDataURL","a","document","createElement","href","download","title","replace","toLowerCase","click","createEffect","chartParams","undefined","destroy","baseOptions","responsive","maintainAspectRatio","options","plugins","legend","display","position","timeAxis","ta","scales","x","time","parser","unit","tooltipFormat","min","max","Error","message","dispatch","errorMessage","id","componentType","ts","Date","now","onError","onCleanup","_$createComponent","ExpandableWrapper","copyData","copyLabel","toolbarVariant","children","_el$","_$getNextElement","_tmpl$5","_el$10","firstChild","_el$11","_co$3","_$getNextMarker","nextSibling","_el$12","_el$13","_co$4","_el$14","_el$15","_co$5","_el$0","_el$1","_$insert","Show","when","_el$2","_tmpl$3","_el$5","_el$6","_co$","_el$7","_el$8","_co$2","_el$3","_tmpl$","_el$4","_tmpl$2","$$click","_$runHydrationEvents","_tmpl$4","DegradedFallback","_$mergeProps","caption","chartToDegradedTable","_ref$","_$use","_$effect","_p$","_v$","_v$2","_v$3","height","e","_$className","t","_$style","_$delegateEvents"],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const web = require("solid-js/web");
|
|
4
|
+
const solidJs = require("solid-js");
|
|
5
|
+
var _tmpl$ = /* @__PURE__ */ web.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__ */ web.template(`<p class="mt-1 text-[10px] text-amber-600 dark:text-amber-400">+<!$><!/> more rows not shown.`), _tmpl$3 = /* @__PURE__ */ web.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__ */ web.template(`<th class="px-2 py-1 font-medium text-amber-900 dark:text-amber-100">`), _tmpl$5 = /* @__PURE__ */ web.template(`<tr class="border-t border-amber-100 dark:border-amber-800/60">`), _tmpl$6 = /* @__PURE__ */ web.template(`<td class="px-2 py-1 text-amber-800 dark:text-amber-200">`);
|
|
6
|
+
const DegradedFallback = (props) => {
|
|
7
|
+
const maxRows = () => props.maxRows ?? 50;
|
|
8
|
+
const allRows = () => props.rows ?? [];
|
|
9
|
+
const shownRows = () => allRows().slice(0, maxRows());
|
|
10
|
+
const hiddenCount = () => Math.max(0, allRows().length - shownRows().length);
|
|
11
|
+
const hasTable = () => {
|
|
12
|
+
var _a;
|
|
13
|
+
return (((_a = props.columns) == null ? void 0 : _a.length) ?? 0) > 0 && allRows().length > 0;
|
|
14
|
+
};
|
|
15
|
+
return (() => {
|
|
16
|
+
var _el$ = web.getNextElement(_tmpl$3), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$12 = _el$3.nextSibling, [_el$13, _co$2] = web.getNextMarker(_el$12.nextSibling);
|
|
17
|
+
web.insert(_el$2, () => props.message);
|
|
18
|
+
web.insert(_el$3, () => props.caption ?? "Showing the underlying data — the interactive view is unavailable.");
|
|
19
|
+
web.insert(_el$, web.createComponent(solidJs.Show, {
|
|
20
|
+
get when() {
|
|
21
|
+
return hasTable();
|
|
22
|
+
},
|
|
23
|
+
get children() {
|
|
24
|
+
return [(() => {
|
|
25
|
+
var _el$4 = web.getNextElement(_tmpl$), _el$5 = _el$4.firstChild, _el$6 = _el$5.firstChild, _el$7 = _el$6.firstChild, _el$8 = _el$6.nextSibling;
|
|
26
|
+
web.insert(_el$7, web.createComponent(solidJs.For, {
|
|
27
|
+
get each() {
|
|
28
|
+
return props.columns;
|
|
29
|
+
},
|
|
30
|
+
children: (col) => (() => {
|
|
31
|
+
var _el$14 = web.getNextElement(_tmpl$4);
|
|
32
|
+
web.insert(_el$14, col);
|
|
33
|
+
return _el$14;
|
|
34
|
+
})()
|
|
35
|
+
}));
|
|
36
|
+
web.insert(_el$8, web.createComponent(solidJs.For, {
|
|
37
|
+
get each() {
|
|
38
|
+
return shownRows();
|
|
39
|
+
},
|
|
40
|
+
children: (row) => (() => {
|
|
41
|
+
var _el$15 = web.getNextElement(_tmpl$5);
|
|
42
|
+
web.insert(_el$15, web.createComponent(solidJs.For, {
|
|
43
|
+
get each() {
|
|
44
|
+
return props.columns;
|
|
45
|
+
},
|
|
46
|
+
children: (_col, i) => (() => {
|
|
47
|
+
var _el$16 = web.getNextElement(_tmpl$6);
|
|
48
|
+
web.insert(_el$16, () => String(row[i()] ?? ""));
|
|
49
|
+
return _el$16;
|
|
50
|
+
})()
|
|
51
|
+
}));
|
|
52
|
+
return _el$15;
|
|
53
|
+
})()
|
|
54
|
+
}));
|
|
55
|
+
return _el$4;
|
|
56
|
+
})(), web.createComponent(solidJs.Show, {
|
|
57
|
+
get when() {
|
|
58
|
+
return hiddenCount() > 0;
|
|
59
|
+
},
|
|
60
|
+
get children() {
|
|
61
|
+
var _el$9 = web.getNextElement(_tmpl$2), _el$0 = _el$9.firstChild, _el$10 = _el$0.nextSibling, [_el$11, _co$] = web.getNextMarker(_el$10.nextSibling);
|
|
62
|
+
_el$11.nextSibling;
|
|
63
|
+
web.insert(_el$9, hiddenCount, _el$11, _co$);
|
|
64
|
+
return _el$9;
|
|
65
|
+
}
|
|
66
|
+
})];
|
|
67
|
+
}
|
|
68
|
+
}), _el$13, _co$2);
|
|
69
|
+
return _el$;
|
|
70
|
+
})();
|
|
71
|
+
};
|
|
72
|
+
exports.DegradedFallback = DegradedFallback;
|
|
73
|
+
//# sourceMappingURL=DegradedFallback.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DegradedFallback.cjs","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,IAAAA,eAAAC,OAAA,GAAAC,QAAAH,KAAAI,YAAAC,QAAAF,MAAAG,aAAAC,SAAAF,MAAAC,aAAA,CAAAE,QAAAC,KAAA,IAAAC,IAAAA,cAAAH,OAAAD,WAAA;AAAAK,QAAAA,OAAAR,OAAA,MAKuEf,MAAMwB,OAAO;AAAAD,QAAAA,OAAAN,OAAA,MAE7EjB,MAAMyB,WAAW,oEAAoE;AAAAF,eAAAX,MAAAc,IAAAA,gBAGvFC,cAAI;AAAA,MAAA,IAACC,OAAI;AAAA,eAAElB,SAAAA;AAAAA,MAAU;AAAA,MAAA,IAAAmB,WAAA;AAAA,eAAA,EAAA,MAAA;AAAA,cAAAC,QAAAjB,IAAAA,eAAAkB,MAAA,GAAAC,QAAAF,MAAAd,YAAAiB,QAAAD,MAAAhB,YAAAkB,QAAAD,MAAAjB,YAAAmB,QAAAF,MAAAf;AAAAK,qBAAAW,OAAAR,IAAAA,gBAKXU,aAAG;AAAA,YAAA,IAACC,OAAI;AAAA,qBAAErC,MAAMW;AAAAA,YAAO;AAAA,YAAAkB,UACpBS,UAAG,MAAA;AAAA,kBAAAC,SAAA1B,IAAAA,eAAA2B,OAAA;AAAAjB,kBAAAA,OAAAgB,QACmED,GAAG;AAAA,qBAAAC;AAAAA,YAAA,GAAA;AAAA,UAAA,CAC1E,CAAA;AAAAhB,qBAAAY,OAAAT,IAAAA,gBAKJU,aAAG;AAAA,YAAA,IAACC,OAAI;AAAA,qBAAEjC,UAAAA;AAAAA,YAAW;AAAA,YAAAyB,UAClBY,UAAG,MAAA;AAAA,kBAAAC,SAAA7B,IAAAA,eAAA8B,OAAA;AAAApB,yBAAAmB,QAAAhB,IAAAA,gBAEAU,aAAG;AAAA,gBAAA,IAACC,OAAI;AAAA,yBAAErC,MAAMW;AAAAA,gBAAO;AAAA,gBAAAkB,UACrBA,CAACe,MAAMC,OAAC,MAAA;AAAA,sBAAAC,SAAAjC,IAAAA,eAAAkC,OAAA;AAAAxB,sBAAAA,OAAAuB,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,IAAAA,gBAKRC,cAAI;AAAA,UAAA,IAACC,OAAI;AAAA,mBAAEtB,gBAAgB;AAAA,UAAC;AAAA,UAAA,IAAAuB,WAAA;AAAA,gBAAAoB,QAAApC,IAAAA,eAAAqC,OAAA,GAAAC,QAAAF,MAAAjC,YAAAoC,SAAAD,MAAAjC,aAAA,CAAAmC,QAAAC,IAAA,IAAAhC,IAAAA,cAAA8B,OAAAlC,WAAA;AAAAmC,mBAAAnC;AAAAK,gBAAAA,OAAA0B,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;;"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DegradedFallback — the middle rung of the renderer fallback ladder
|
|
3
|
+
* (audit 2026-05-30, P2.5).
|
|
4
|
+
*
|
|
5
|
+
* Each heavy renderer (graph / map / chart) follows the same contract:
|
|
6
|
+
* 1. native render when its peer lib is available and succeeds;
|
|
7
|
+
* 2. **degraded but useful** view when the native render throws — this
|
|
8
|
+
* component: a visible notice + a plain data table so the user still
|
|
9
|
+
* sees the underlying data instead of a blank space;
|
|
10
|
+
* 3. (the caller also emits a `component:error` telemetry event).
|
|
11
|
+
*
|
|
12
|
+
* Pure / presentational — no peer deps, no side effects — so a render-path
|
|
13
|
+
* failure in a heavy lib can never cascade into the fallback itself, and it
|
|
14
|
+
* is trivially unit-testable. Rows/cells are rendered as text; callers are
|
|
15
|
+
* responsible for stringifying complex cell values.
|
|
16
|
+
*/
|
|
17
|
+
import { Component } from 'solid-js';
|
|
18
|
+
export interface DegradedFallbackProps {
|
|
19
|
+
/** Short, human-readable reason the native render was skipped/failed. */
|
|
20
|
+
message: string;
|
|
21
|
+
/**
|
|
22
|
+
* Column headers for the degraded data table. When omitted (or empty),
|
|
23
|
+
* only the notice banner is shown.
|
|
24
|
+
*/
|
|
25
|
+
columns?: string[];
|
|
26
|
+
/** Row data — each row is an array of cells aligned to `columns`. */
|
|
27
|
+
rows?: Array<Array<string | number>>;
|
|
28
|
+
/**
|
|
29
|
+
* Caption under the table. Defaults to a generic
|
|
30
|
+
* "interactive view unavailable" line.
|
|
31
|
+
*/
|
|
32
|
+
caption?: string;
|
|
33
|
+
/** Max rows to render before truncating (default 50). */
|
|
34
|
+
maxRows?: number;
|
|
35
|
+
}
|
|
36
|
+
export declare const DegradedFallback: Component<DegradedFallbackProps>;
|
|
37
|
+
//# sourceMappingURL=DegradedFallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DegradedFallback.d.ts","sourceRoot":"","sources":["../../src/components/DegradedFallback.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,SAAS,EAAa,MAAM,UAAU,CAAC;AAEhD,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,qEAAqE;IACrE,IAAI,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC;IACrC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,gBAAgB,EAAE,SAAS,CAAC,qBAAqB,CAsD7D,CAAC"}
|