@seed-ship/mcp-ui-solid 6.23.0 → 6.24.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 +125 -0
- package/README.md +107 -2
- package/dist/components/ChartJSRenderer.cjs +192 -66
- package/dist/components/ChartJSRenderer.cjs.map +1 -1
- package/dist/components/ChartJSRenderer.d.ts.map +1 -1
- package/dist/components/ChartJSRenderer.js +192 -66
- package/dist/components/ChartJSRenderer.js.map +1 -1
- package/dist/components/DegradedFallback.cjs +34 -2
- package/dist/components/DegradedFallback.cjs.map +1 -1
- package/dist/components/DegradedFallback.d.ts +7 -0
- package/dist/components/DegradedFallback.d.ts.map +1 -1
- package/dist/components/DegradedFallback.js +34 -2
- package/dist/components/DegradedFallback.js.map +1 -1
- package/dist/components/MissingValue.cjs +43 -0
- package/dist/components/MissingValue.cjs.map +1 -0
- package/dist/components/MissingValue.d.ts +27 -0
- package/dist/components/MissingValue.d.ts.map +1 -0
- package/dist/components/MissingValue.js +43 -0
- package/dist/components/MissingValue.js.map +1 -0
- package/dist/components/UIResourceRenderer.cjs +237 -199
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +237 -199
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/components/chart-data-table.cjs +30 -5
- package/dist/components/chart-data-table.cjs.map +1 -1
- package/dist/components/chart-data-table.d.ts +18 -1
- package/dist/components/chart-data-table.d.ts.map +1 -1
- package/dist/components/chart-data-table.js +30 -5
- package/dist/components/chart-data-table.js.map +1 -1
- package/dist/components/chart-missing-plugin.cjs +194 -0
- package/dist/components/chart-missing-plugin.cjs.map +1 -0
- package/dist/components/chart-missing-plugin.d.ts +127 -0
- package/dist/components/chart-missing-plugin.d.ts.map +1 -0
- package/dist/components/chart-missing-plugin.js +194 -0
- package/dist/components/chart-missing-plugin.js.map +1 -0
- package/dist/context/MCPUIStringsContext.cjs +12 -1
- package/dist/context/MCPUIStringsContext.cjs.map +1 -1
- package/dist/context/MCPUIStringsContext.d.ts +62 -0
- package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
- package/dist/context/MCPUIStringsContext.js +12 -1
- package/dist/context/MCPUIStringsContext.js.map +1 -1
- package/dist/services/component-registry.cjs +9 -2
- package/dist/services/component-registry.cjs.map +1 -1
- package/dist/services/component-registry.d.ts.map +1 -1
- package/dist/services/component-registry.js +9 -2
- package/dist/services/component-registry.js.map +1 -1
- package/dist/services/validation.cjs +31 -8
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +31 -8
- package/dist/services/validation.js.map +1 -1
- package/dist/types/index.d.ts +14 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/degraded-projections.cjs +19 -3
- package/dist/utils/degraded-projections.cjs.map +1 -1
- package/dist/utils/degraded-projections.d.ts +22 -2
- package/dist/utils/degraded-projections.d.ts.map +1 -1
- package/dist/utils/degraded-projections.js +19 -3
- package/dist/utils/degraded-projections.js.map +1 -1
- package/dist/utils/missing-value.cjs +60 -0
- package/dist/utils/missing-value.cjs.map +1 -0
- package/dist/utils/missing-value.d.ts +79 -0
- package/dist/utils/missing-value.d.ts.map +1 -0
- package/dist/utils/missing-value.js +60 -0
- package/dist/utils/missing-value.js.map +1 -0
- package/package.json +3 -3
- package/src/components/ChartJSRenderer.tsx +159 -6
- package/src/components/DegradedFallback.tsx +24 -2
- package/src/components/MissingValue.tsx +61 -0
- package/src/components/UIResourceRenderer.tsx +42 -9
- package/src/components/chart-data-table.ts +50 -6
- package/src/components/chart-missing-plugin.ts +334 -0
- package/src/context/MCPUIStringsContext.tsx +76 -0
- package/src/services/component-registry.ts +10 -2
- package/src/services/validation.ts +44 -9
- package/src/types/index.ts +13 -1
- package/src/utils/degraded-projections.ts +44 -4
- package/src/utils/missing-value.ts +127 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatMCPUIString } from "./format-string.js";
|
|
2
|
+
import { isMissingChartValue } from "./missing-value.js";
|
|
2
3
|
const DEGRADED_PROJECTION_LABELS = {
|
|
3
4
|
source: "Source",
|
|
4
5
|
target: "Target",
|
|
@@ -10,7 +11,8 @@ const DEGRADED_PROJECTION_LABELS = {
|
|
|
10
11
|
info: "Info",
|
|
11
12
|
marker: "marker",
|
|
12
13
|
feature: "feature",
|
|
13
|
-
series: "Series {n}"
|
|
14
|
+
series: "Series {n}",
|
|
15
|
+
withUnit: "{label} ({unit})"
|
|
14
16
|
};
|
|
15
17
|
const withLabels = (labels) => ({
|
|
16
18
|
...DEGRADED_PROJECTION_LABELS,
|
|
@@ -84,6 +86,7 @@ function mapToDegradedTable(params, labels) {
|
|
|
84
86
|
function chartToDegradedTable(params, labels) {
|
|
85
87
|
var _a, _b;
|
|
86
88
|
const l = withLabels(labels);
|
|
89
|
+
const unit = typeof params.unit === "string" && params.unit !== "" ? params.unit : void 0;
|
|
87
90
|
const datasets = ((_a = params.data) == null ? void 0 : _a.datasets) ?? [];
|
|
88
91
|
const dataLabels = ((_b = params.data) == null ? void 0 : _b.labels) ?? [];
|
|
89
92
|
const rowCount = Math.max(dataLabels.length, ...datasets.map((d) => {
|
|
@@ -92,16 +95,29 @@ function chartToDegradedTable(params, labels) {
|
|
|
92
95
|
}), 0);
|
|
93
96
|
const columns = [
|
|
94
97
|
"",
|
|
95
|
-
...datasets.map((d, i) =>
|
|
98
|
+
...datasets.map((d, i) => {
|
|
99
|
+
const name = d.label ?? formatMCPUIString(l.series, { n: i + 1 });
|
|
100
|
+
return unit === void 0 ? name : formatMCPUIString(l.withUnit ?? DEGRADED_PROJECTION_LABELS.withUnit, { label: name, unit });
|
|
101
|
+
})
|
|
96
102
|
];
|
|
97
103
|
const rows = [];
|
|
104
|
+
const missing = [];
|
|
98
105
|
for (let r = 0; r < Math.min(rowCount, MAX_PROJECTED_ROWS); r++) {
|
|
99
106
|
rows.push([cell(dataLabels[r] ?? r + 1), ...datasets.map((d) => {
|
|
100
107
|
var _a2;
|
|
101
108
|
return cell((_a2 = d.data) == null ? void 0 : _a2[r]);
|
|
102
109
|
})]);
|
|
110
|
+
missing.push([
|
|
111
|
+
false,
|
|
112
|
+
...datasets.map((d) => {
|
|
113
|
+
var _a2, _b2;
|
|
114
|
+
if (r >= (((_a2 = d.data) == null ? void 0 : _a2.length) ?? 0)) return false;
|
|
115
|
+
const entry = (_b2 = d.data) == null ? void 0 : _b2[r];
|
|
116
|
+
return (entry === null || typeof entry !== "object") && isMissingChartValue(entry);
|
|
117
|
+
})
|
|
118
|
+
]);
|
|
103
119
|
}
|
|
104
|
-
return { columns, rows };
|
|
120
|
+
return { columns, rows, missing };
|
|
105
121
|
}
|
|
106
122
|
export {
|
|
107
123
|
DEGRADED_PROJECTION_LABELS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"degraded-projections.js","sources":["../../src/utils/degraded-projections.ts"],"sourcesContent":["/**\n * Degraded-fallback projections (audit 2026-05-30, P2.5).\n *\n * Pure functions that turn a heavy renderer's params into a flat\n * `{ columns, rows }` table for `<DegradedFallback>` — the middle rung of\n * the fallback ladder shown when the native render (G6 / Leaflet / Chart.js)\n * is unavailable or throws. No peer deps, no side effects, fully testable.\n *\n * These are best-effort views: they surface the underlying data so the user\n * isn't left with a blank space, not faithful reproductions of the chart/\n * map/graph.\n */\n\nimport { formatMCPUIString } from './format-string';\n\n/**\n * Column headers and series names of the degraded tables.\n *\n * The projections are pure helpers with no access to `MCPUIStrings`, so the\n * wording is injected: each function takes an optional trailing `labels`\n * partial, and the renderers feed it from `useMCPUIStrings()`. Left out, the\n * English defaults below apply — the pre-v6.20.0 behaviour.\n *\n * @since v6.20.0\n */\nexport interface DegradedProjectionLabels {\n /** Graph edge table — source column. */\n source: string;\n /** Graph edge table — target column. */\n target: string;\n /** Graph edge / node table — label column. */\n label: string;\n /** Graph node table — node id column. */\n node: string;\n /** Map table — feature type column. */\n type: string;\n /** Map table — latitude column. */\n lat: string;\n /** Map table — longitude column. */\n lng: string;\n /** Map table — property summary column. */\n info: string;\n /** Map table — `Type` cell of a marker row (v6.20.0, 6th pass). */\n marker: string;\n /** Map table — `Type` cell of a GeoJSON feature without a geometry type (v6.20.0). */\n feature: string;\n /** Chart table — fallback dataset name. Template — `{n}`. */\n series: string;\n}\n\n/** English defaults for {@link DegradedProjectionLabels}. */\nexport const DEGRADED_PROJECTION_LABELS: DegradedProjectionLabels = {\n source: 'Source',\n target: 'Target',\n label: 'Label',\n node: 'Node',\n type: 'Type',\n lat: 'Lat',\n lng: 'Lng',\n info: 'Info',\n marker: 'marker',\n feature: 'feature',\n series: 'Series {n}',\n};\n\nconst withLabels = (labels?: Partial<DegradedProjectionLabels>): DegradedProjectionLabels => ({\n ...DEGRADED_PROJECTION_LABELS,\n ...labels,\n});\n\nexport interface DegradedTable {\n columns: string[];\n rows: Array<Array<string | number>>;\n}\n\nconst MAX_PROJECTED_ROWS = 200;\n\n/** Compact a value to a single table cell string. */\nfunction cell(value: unknown): string {\n if (value == null) return '';\n if (typeof value === 'object') {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\n\n// ─── Graph ───────────────────────────────────────────────────────────────\n\n/**\n * Graph → edge table when edges exist (Source / Target / Label), else a\n * node list (Node / Label). The graph renderer can therefore degrade to a\n * readable relationship listing instead of a blank canvas.\n */\nexport function graphToDegradedTable(params: {\n nodes?: Array<{ id: string; label?: string }>;\n edges?: Array<{ source: string; target: string; label?: string; weight?: number }>;\n}, labels?: Partial<DegradedProjectionLabels>): DegradedTable {\n const l = withLabels(labels);\n const edges = params.edges ?? [];\n if (edges.length > 0) {\n return {\n columns: [l.source, l.target, l.label],\n rows: edges.slice(0, MAX_PROJECTED_ROWS).map((e) => {\n const label = [e.weight != null ? String(e.weight) : '', e.label ?? '']\n .filter(Boolean)\n .join(' · ');\n return [cell(e.source), cell(e.target), label];\n }),\n };\n }\n const nodes = params.nodes ?? [];\n return {\n columns: [l.node, l.label],\n rows: nodes.slice(0, MAX_PROJECTED_ROWS).map((n) => [cell(n.id), cell(n.label ?? n.id)]),\n };\n}\n\n// ─── Map ───────────────────────────────────────────────────────────────────\n\ninterface GeoJSONLikeFeature {\n geometry?: { type?: string; coordinates?: unknown } | null;\n properties?: Record<string, unknown> | null;\n}\n\n/** Pull a representative `[lng, lat]` pair out of a geometry's coordinates. */\nfunction firstLngLat(coords: unknown): [number, number] | null {\n let c: unknown = coords;\n // Descend nested arrays until we reach a [number, number, ...] position.\n while (Array.isArray(c) && Array.isArray(c[0])) c = c[0];\n if (Array.isArray(c) && typeof c[0] === 'number' && typeof c[1] === 'number') {\n return [c[0], c[1]];\n }\n return null;\n}\n\n/**\n * Map → a coordinate table. Markers become Lat/Lng/Label rows; GeoJSON\n * features become Type / Lat / Lng (+ a compact properties summary). So a\n * map that can't paint still lists where its points are.\n */\nexport function mapToDegradedTable(params: {\n markers?: Array<{\n position: [number, number] | { lat: number; lng: number };\n tooltip?: string;\n popup?: string;\n }>;\n geojson?: unknown;\n}, labels?: Partial<DegradedProjectionLabels>): DegradedTable {\n const l = withLabels(labels);\n const rows: Array<Array<string | number>> = [];\n\n for (const m of params.markers ?? []) {\n const lat = Array.isArray(m.position) ? m.position[0] : m.position?.lat;\n const lng = Array.isArray(m.position) ? m.position[1] : m.position?.lng;\n rows.push([l.marker, cell(lat), cell(lng), cell(m.tooltip ?? m.popup ?? '')]);\n }\n\n const fc = params.geojson as { features?: GeoJSONLikeFeature[] } | undefined;\n const features = Array.isArray(fc?.features) ? fc!.features : [];\n for (const f of features) {\n const ll = firstLngLat(f.geometry?.coordinates);\n const props = f.properties ?? {};\n const propSummary = Object.keys(props)\n .slice(0, 3)\n .map((k) => `${k}=${cell(props[k])}`)\n .join(', ');\n rows.push([\n cell(f.geometry?.type ?? l.feature),\n ll ? cell(ll[1]) : '',\n ll ? cell(ll[0]) : '',\n propSummary,\n ]);\n }\n\n return {\n columns: [l.type, l.lat, l.lng, l.info],\n rows: rows.slice(0, MAX_PROJECTED_ROWS),\n };\n}\n\n// ─── Chart ───────────────────────────────────────────────────────────────\n\n/**\n * Chart → a series table: one row per label, one column per dataset. So a\n * chart that can't draw still shows its numbers. Point/object data (scatter,\n * bubble, time series) is stringified per cell.\n */\nexport function chartToDegradedTable(params: {\n data?: {\n labels?: Array<string | number>;\n datasets?: Array<{ label?: string; data?: unknown[] }>;\n };\n}, labels?: Partial<DegradedProjectionLabels>): DegradedTable {\n const l = withLabels(labels);\n const datasets = params.data?.datasets ?? [];\n const dataLabels = params.data?.labels ?? [];\n const rowCount = Math.max(dataLabels.length, ...datasets.map((d) => d.data?.length ?? 0), 0);\n\n const columns = [\n '',\n ...datasets.map((d, i) => d.label ?? formatMCPUIString(l.series, { n: i + 1 })),\n ];\n const rows: Array<Array<string | number>> = [];\n for (let r = 0; r < Math.min(rowCount, MAX_PROJECTED_ROWS); r++) {\n rows.push([cell(dataLabels[r] ?? r + 1), ...datasets.map((d) => cell(d.data?.[r]))]);\n }\n return { columns, rows };\n}\n"],"names":["_a"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"degraded-projections.js","sources":["../../src/utils/degraded-projections.ts"],"sourcesContent":["/**\n * Degraded-fallback projections (audit 2026-05-30, P2.5).\n *\n * Pure functions that turn a heavy renderer's params into a flat\n * `{ columns, rows }` table for `<DegradedFallback>` — the middle rung of\n * the fallback ladder shown when the native render (G6 / Leaflet / Chart.js)\n * is unavailable or throws. No peer deps, no side effects, fully testable.\n *\n * These are best-effort views: they surface the underlying data so the user\n * isn't left with a blank space, not faithful reproductions of the chart/\n * map/graph.\n */\n\nimport { formatMCPUIString } from './format-string';\nimport { isMissingChartValue } from './missing-value';\n\n/**\n * Column headers and series names of the degraded tables.\n *\n * The projections are pure helpers with no access to `MCPUIStrings`, so the\n * wording is injected: each function takes an optional trailing `labels`\n * partial, and the renderers feed it from `useMCPUIStrings()`. Left out, the\n * English defaults below apply — the pre-v6.20.0 behaviour.\n *\n * @since v6.20.0\n */\nexport interface DegradedProjectionLabels {\n /** Graph edge table — source column. */\n source: string;\n /** Graph edge table — target column. */\n target: string;\n /** Graph edge / node table — label column. */\n label: string;\n /** Graph node table — node id column. */\n node: string;\n /** Map table — feature type column. */\n type: string;\n /** Map table — latitude column. */\n lat: string;\n /** Map table — longitude column. */\n lng: string;\n /** Map table — property summary column. */\n info: string;\n /** Map table — `Type` cell of a marker row (v6.20.0, 6th pass). */\n marker: string;\n /** Map table — `Type` cell of a GeoJSON feature without a geometry type (v6.20.0). */\n feature: string;\n /** Chart table — fallback dataset name. Template — `{n}`. */\n series: string;\n /**\n * Chart table — a dataset column header followed by the chart's `unit`.\n * Template — `{label}`, `{unit}`. Only applied when the chart has a unit.\n * Optional so that a complete labels table written for 6.20.0–6.23.0 still\n * type-checks. @since v6.24.0\n */\n withUnit?: string;\n}\n\n/** English defaults for {@link DegradedProjectionLabels}. */\nexport const DEGRADED_PROJECTION_LABELS: Required<DegradedProjectionLabels> = {\n source: 'Source',\n target: 'Target',\n label: 'Label',\n node: 'Node',\n type: 'Type',\n lat: 'Lat',\n lng: 'Lng',\n info: 'Info',\n marker: 'marker',\n feature: 'feature',\n series: 'Series {n}',\n withUnit: '{label} ({unit})',\n};\n\nconst withLabels = (labels?: Partial<DegradedProjectionLabels>): DegradedProjectionLabels => ({\n ...DEGRADED_PROJECTION_LABELS,\n ...labels,\n});\n\nexport interface DegradedTable {\n columns: string[];\n rows: Array<Array<string | number>>;\n}\n\n/** A degraded chart table: its cells, plus which of them are missing values. */\nexport interface DegradedChartTable extends DegradedTable {\n /** Same shape as `rows`; `true` for a dataset entry that is missing. @since v6.24.0 */\n missing: boolean[][];\n}\n\nconst MAX_PROJECTED_ROWS = 200;\n\n/** Compact a value to a single table cell string. */\nfunction cell(value: unknown): string {\n if (value == null) return '';\n if (typeof value === 'object') {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\n\n// ─── Graph ───────────────────────────────────────────────────────────────\n\n/**\n * Graph → edge table when edges exist (Source / Target / Label), else a\n * node list (Node / Label). The graph renderer can therefore degrade to a\n * readable relationship listing instead of a blank canvas.\n */\nexport function graphToDegradedTable(params: {\n nodes?: Array<{ id: string; label?: string }>;\n edges?: Array<{ source: string; target: string; label?: string; weight?: number }>;\n}, labels?: Partial<DegradedProjectionLabels>): DegradedTable {\n const l = withLabels(labels);\n const edges = params.edges ?? [];\n if (edges.length > 0) {\n return {\n columns: [l.source, l.target, l.label],\n rows: edges.slice(0, MAX_PROJECTED_ROWS).map((e) => {\n const label = [e.weight != null ? String(e.weight) : '', e.label ?? '']\n .filter(Boolean)\n .join(' · ');\n return [cell(e.source), cell(e.target), label];\n }),\n };\n }\n const nodes = params.nodes ?? [];\n return {\n columns: [l.node, l.label],\n rows: nodes.slice(0, MAX_PROJECTED_ROWS).map((n) => [cell(n.id), cell(n.label ?? n.id)]),\n };\n}\n\n// ─── Map ───────────────────────────────────────────────────────────────────\n\ninterface GeoJSONLikeFeature {\n geometry?: { type?: string; coordinates?: unknown } | null;\n properties?: Record<string, unknown> | null;\n}\n\n/** Pull a representative `[lng, lat]` pair out of a geometry's coordinates. */\nfunction firstLngLat(coords: unknown): [number, number] | null {\n let c: unknown = coords;\n // Descend nested arrays until we reach a [number, number, ...] position.\n while (Array.isArray(c) && Array.isArray(c[0])) c = c[0];\n if (Array.isArray(c) && typeof c[0] === 'number' && typeof c[1] === 'number') {\n return [c[0], c[1]];\n }\n return null;\n}\n\n/**\n * Map → a coordinate table. Markers become Lat/Lng/Label rows; GeoJSON\n * features become Type / Lat / Lng (+ a compact properties summary). So a\n * map that can't paint still lists where its points are.\n */\nexport function mapToDegradedTable(params: {\n markers?: Array<{\n position: [number, number] | { lat: number; lng: number };\n tooltip?: string;\n popup?: string;\n }>;\n geojson?: unknown;\n}, labels?: Partial<DegradedProjectionLabels>): DegradedTable {\n const l = withLabels(labels);\n const rows: Array<Array<string | number>> = [];\n\n for (const m of params.markers ?? []) {\n const lat = Array.isArray(m.position) ? m.position[0] : m.position?.lat;\n const lng = Array.isArray(m.position) ? m.position[1] : m.position?.lng;\n rows.push([l.marker, cell(lat), cell(lng), cell(m.tooltip ?? m.popup ?? '')]);\n }\n\n const fc = params.geojson as { features?: GeoJSONLikeFeature[] } | undefined;\n const features = Array.isArray(fc?.features) ? fc!.features : [];\n for (const f of features) {\n const ll = firstLngLat(f.geometry?.coordinates);\n const props = f.properties ?? {};\n const propSummary = Object.keys(props)\n .slice(0, 3)\n .map((k) => `${k}=${cell(props[k])}`)\n .join(', ');\n rows.push([\n cell(f.geometry?.type ?? l.feature),\n ll ? cell(ll[1]) : '',\n ll ? cell(ll[0]) : '',\n propSummary,\n ]);\n }\n\n return {\n columns: [l.type, l.lat, l.lng, l.info],\n rows: rows.slice(0, MAX_PROJECTED_ROWS),\n };\n}\n\n// ─── Chart ───────────────────────────────────────────────────────────────\n\n/**\n * Chart → a series table: one row per label, one column per dataset. So a\n * chart that can't draw still shows its numbers. Point/object data (scatter,\n * bubble, time series) is stringified per cell.\n *\n * `missing` flags the cells whose dataset entry is a missing value (`null`\n * or non-finite) so the table can mark them instead of showing an empty\n * cell. A slot past the end of a shorter or empty dataset has no entry and is\n * not flagged; a point object keeps its JSON text (where a `\"y\":null` reads\n * as such) and is not flagged either. With a `unit`, dataset column headers\n * become `labels.withUnit`.\n */\nexport function chartToDegradedTable(params: {\n unit?: string;\n data?: {\n labels?: Array<string | number>;\n datasets?: Array<{ label?: string; data?: unknown[] }>;\n };\n}, labels?: Partial<DegradedProjectionLabels>): DegradedChartTable {\n const l = withLabels(labels);\n const unit = typeof params.unit === 'string' && params.unit !== '' ? params.unit : undefined;\n const datasets = params.data?.datasets ?? [];\n const dataLabels = params.data?.labels ?? [];\n const rowCount = Math.max(dataLabels.length, ...datasets.map((d) => d.data?.length ?? 0), 0);\n\n const columns = [\n '',\n ...datasets.map((d, i) => {\n const name = d.label ?? formatMCPUIString(l.series, { n: i + 1 });\n return unit === undefined\n ? name\n : formatMCPUIString(l.withUnit ?? DEGRADED_PROJECTION_LABELS.withUnit, { label: name, unit });\n }),\n ];\n const rows: Array<Array<string | number>> = [];\n const missing: boolean[][] = [];\n for (let r = 0; r < Math.min(rowCount, MAX_PROJECTED_ROWS); r++) {\n rows.push([cell(dataLabels[r] ?? r + 1), ...datasets.map((d) => cell(d.data?.[r]))]);\n missing.push([\n false,\n ...datasets.map((d) => {\n // Past the end of a shorter or empty dataset there is no entry, so\n // nothing to flag (the cell stays empty, as before 6.24.0).\n if (r >= (d.data?.length ?? 0)) return false;\n const entry = d.data?.[r];\n return (entry === null || typeof entry !== 'object') && isMissingChartValue(entry);\n }),\n ]);\n }\n return { columns, rows, missing };\n}\n"],"names":["_a","_b"],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const MISSING_VALUE_MARKER = "-";
|
|
4
|
+
const PART_TO_WHOLE_CHART_TYPES = /* @__PURE__ */ new Set([
|
|
5
|
+
"pie",
|
|
6
|
+
"doughnut",
|
|
7
|
+
"polarArea"
|
|
8
|
+
]);
|
|
9
|
+
function isMissingCellValue(value) {
|
|
10
|
+
if (value === null || value === void 0) return true;
|
|
11
|
+
if (typeof value === "object") return false;
|
|
12
|
+
const cleaned = stripUndefinedDebris(String(value)).trim();
|
|
13
|
+
return cleaned === "" || cleaned === MISSING_VALUE_MARKER || cleaned.toLowerCase() === "undefined";
|
|
14
|
+
}
|
|
15
|
+
function stripUndefinedDebris(value) {
|
|
16
|
+
return value.replace(/\s*[–-]\s*undefined\s*$/gi, "").replace(/^undefined\s*[–-]\s*/gi, "");
|
|
17
|
+
}
|
|
18
|
+
function isMissingChartValue(value) {
|
|
19
|
+
if (value === null || value === void 0) return true;
|
|
20
|
+
if (typeof value === "number") return !Number.isFinite(value);
|
|
21
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
22
|
+
const y = value.y;
|
|
23
|
+
return y === null || y === void 0 || typeof y === "number" && !Number.isFinite(y);
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
function summarizeChartMissing(params) {
|
|
28
|
+
var _a;
|
|
29
|
+
const datasets = Array.isArray((_a = params == null ? void 0 : params.data) == null ? void 0 : _a.datasets) ? params.data.datasets : [];
|
|
30
|
+
const fullyMissing = [];
|
|
31
|
+
const partiallyMissing = [];
|
|
32
|
+
let anyMissing = false;
|
|
33
|
+
let anyObserved = false;
|
|
34
|
+
for (const dataset of datasets) {
|
|
35
|
+
const data = Array.isArray(dataset == null ? void 0 : dataset.data) ? dataset.data : [];
|
|
36
|
+
let observed = 0;
|
|
37
|
+
let missing = 0;
|
|
38
|
+
for (const value of data) {
|
|
39
|
+
if (isMissingChartValue(value)) missing += 1;
|
|
40
|
+
else observed += 1;
|
|
41
|
+
}
|
|
42
|
+
fullyMissing.push(missing > 0 && observed === 0);
|
|
43
|
+
partiallyMissing.push(observed > 0 && missing > 0);
|
|
44
|
+
anyMissing || (anyMissing = missing > 0);
|
|
45
|
+
anyObserved || (anyObserved = observed > 0);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
hasMissing: anyMissing,
|
|
49
|
+
fullyMissing,
|
|
50
|
+
partiallyMissing,
|
|
51
|
+
allMissing: anyMissing && !anyObserved
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
exports.MISSING_VALUE_MARKER = MISSING_VALUE_MARKER;
|
|
55
|
+
exports.PART_TO_WHOLE_CHART_TYPES = PART_TO_WHOLE_CHART_TYPES;
|
|
56
|
+
exports.isMissingCellValue = isMissingCellValue;
|
|
57
|
+
exports.isMissingChartValue = isMissingChartValue;
|
|
58
|
+
exports.stripUndefinedDebris = stripUndefinedDebris;
|
|
59
|
+
exports.summarizeChartMissing = summarizeChartMissing;
|
|
60
|
+
//# sourceMappingURL=missing-value.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing-value.cjs","sources":["../../src/utils/missing-value.ts"],"sourcesContent":["/**\n * Missing values — one definition shared by tables, chart data views,\n * degraded projections, chart validation and the chart renderer.\n *\n * @since v6.24.0\n *\n * A missing value is a value the payload says EXISTS but was not observed:\n * `null` in a chart dataset (or as a point's `y`), `null` / `undefined` in a\n * table cell. It is not a zero, and it is not the absence of a row or of a\n * label: every view keeps its slot and marks it.\n *\n * Runtime-free on purpose (no `solid-js`): the pure projections in\n * `chart-data-table.ts` and `degraded-projections.ts`, and `validation.ts`\n * (published as the `/validation` subpath), import it.\n */\n\n/** The glyph a table cell shows for a missing value — `renderCellValue`'s historical `-`. */\nexport const MISSING_VALUE_MARKER = '-'\n\n/**\n * Chart types that encode a value as a share of a whole (or, for polar area,\n * as a wedge whose emptiness reads as zero). Chart.js parses a `null` share\n * as `0`, so these types refuse missing values instead of drawing them.\n */\nexport const PART_TO_WHOLE_CHART_TYPES: ReadonlySet<string> = new Set([\n 'pie',\n 'doughnut',\n 'polarArea',\n])\n\n/**\n * Whether a raw TABLE cell value is displayed as {@link MISSING_VALUE_MARKER}.\n *\n * Mirrors the rules `renderCellValue` applies before any formatting — `null`,\n * `undefined`, and a string that is empty or the word `undefined` once the\n * backend's `\"X – undefined\"` debris is stripped — plus the literal marker\n * itself (whitespace around it aside, which a table cell does not render),\n * since it looks identical on screen and so must read identically.\n *\n * Objects are never missing here: `renderCellValue` renders them (link,\n * name, or escaped JSON).\n */\nexport function isMissingCellValue(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'object') return false\n const cleaned = stripUndefinedDebris(String(value)).trim()\n return cleaned === '' || cleaned === MISSING_VALUE_MARKER || cleaned.toLowerCase() === 'undefined'\n}\n\n/**\n * Removes the `\"Text – undefined\"` / `\"undefined – Text\"` debris some\n * backends concatenate. Shared with `renderCellValue` so both apply the very\n * same cleanup.\n */\nexport function stripUndefinedDebris(value: string): string {\n return value.replace(/\\s*[–-]\\s*undefined\\s*$/gi, '').replace(/^undefined\\s*[–-]\\s*/gi, '')\n}\n\n/**\n * Whether one CHART data entry is missing: `null` / `undefined`, a non-finite\n * number (Chart.js parses those to `null` too), or a point whose `y` is.\n */\nexport function isMissingChartValue(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'number') return !Number.isFinite(value)\n if (typeof value === 'object' && !Array.isArray(value)) {\n const y = (value as { y?: unknown }).y\n return y === null || y === undefined || (typeof y === 'number' && !Number.isFinite(y))\n }\n return false\n}\n\n/**\n * What a chart's missing values amount to.\n *\n * Only ENTRIES count: an explicit `null` (or non-finite number, or a point\n * whose `y` is one). A dataset with no entry at all (`data: []`) is not\n * \"missing\" — it is how 6.23.0 and earlier payloads said \"nothing yet\", e.g.\n * while streaming, and it keeps rendering exactly as it did then.\n */\nexport interface ChartMissingSummary {\n /** At least one entry of one dataset is missing. */\n hasMissing: boolean\n /** Per dataset, in order: it has entries and every one of them is missing. */\n fullyMissing: boolean[]\n /**\n * Per dataset, in order: at least one observed value AND at least one\n * missing entry — the series a reader actually sees broken.\n */\n partiallyMissing: boolean[]\n /** Something is missing and no dataset has a single observed value: nothing to plot. */\n allMissing: boolean\n}\n\ninterface ChartDataLike {\n data?: {\n datasets?: Array<{ data?: unknown[] } | null | undefined>\n }\n}\n\n/** Summarises the missing values of a chart payload. Never throws on a malformed payload. */\nexport function summarizeChartMissing(params: ChartDataLike | null | undefined): ChartMissingSummary {\n const datasets = Array.isArray(params?.data?.datasets) ? params.data.datasets : []\n const fullyMissing: boolean[] = []\n const partiallyMissing: boolean[] = []\n let anyMissing = false\n let anyObserved = false\n for (const dataset of datasets) {\n const data = Array.isArray(dataset?.data) ? dataset.data : []\n let observed = 0\n let missing = 0\n for (const value of data) {\n if (isMissingChartValue(value)) missing += 1\n else observed += 1\n }\n fullyMissing.push(missing > 0 && observed === 0)\n partiallyMissing.push(observed > 0 && missing > 0)\n anyMissing ||= missing > 0\n anyObserved ||= observed > 0\n }\n return {\n hasMissing: anyMissing,\n fullyMissing,\n partiallyMissing,\n allMissing: anyMissing && !anyObserved,\n }\n}\n"],"names":[],"mappings":";;AAiBO,MAAM,uBAAuB;AAO7B,MAAM,gDAAqD,IAAI;AAAA,EACpE;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAcM,SAAS,mBAAmB,OAAyB;AAC1D,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,qBAAqB,OAAO,KAAK,CAAC,EAAE,KAAA;AACpD,SAAO,YAAY,MAAM,YAAY,wBAAwB,QAAQ,kBAAkB;AACzF;AAOO,SAAS,qBAAqB,OAAuB;AAC1D,SAAO,MAAM,QAAQ,6BAA6B,EAAE,EAAE,QAAQ,0BAA0B,EAAE;AAC5F;AAMO,SAAS,oBAAoB,OAAyB;AAC3D,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,OAAO,UAAU,iBAAiB,CAAC,OAAO,SAAS,KAAK;AAC5D,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AACtD,UAAM,IAAK,MAA0B;AACrC,WAAO,MAAM,QAAQ,MAAM,UAAc,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC;AAAA,EACtF;AACA,SAAO;AACT;AA+BO,SAAS,sBAAsB,QAA+D;;AACnG,QAAM,WAAW,MAAM,SAAQ,sCAAQ,SAAR,mBAAc,QAAQ,IAAI,OAAO,KAAK,WAAW,CAAA;AAChF,QAAM,eAA0B,CAAA;AAChC,QAAM,mBAA8B,CAAA;AACpC,MAAI,aAAa;AACjB,MAAI,cAAc;AAClB,aAAW,WAAW,UAAU;AAC9B,UAAM,OAAO,MAAM,QAAQ,mCAAS,IAAI,IAAI,QAAQ,OAAO,CAAA;AAC3D,QAAI,WAAW;AACf,QAAI,UAAU;AACd,eAAW,SAAS,MAAM;AACxB,UAAI,oBAAoB,KAAK,EAAG,YAAW;AAAA,UACtC,aAAY;AAAA,IACnB;AACA,iBAAa,KAAK,UAAU,KAAK,aAAa,CAAC;AAC/C,qBAAiB,KAAK,WAAW,KAAK,UAAU,CAAC;AACjD,gCAAe,UAAU;AACzB,kCAAgB,WAAW;AAAA,EAC7B;AACA,SAAO;AAAA,IACL,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA,YAAY,cAAc,CAAC;AAAA,EAAA;AAE/B;;;;;;;"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Missing values — one definition shared by tables, chart data views,
|
|
3
|
+
* degraded projections, chart validation and the chart renderer.
|
|
4
|
+
*
|
|
5
|
+
* @since v6.24.0
|
|
6
|
+
*
|
|
7
|
+
* A missing value is a value the payload says EXISTS but was not observed:
|
|
8
|
+
* `null` in a chart dataset (or as a point's `y`), `null` / `undefined` in a
|
|
9
|
+
* table cell. It is not a zero, and it is not the absence of a row or of a
|
|
10
|
+
* label: every view keeps its slot and marks it.
|
|
11
|
+
*
|
|
12
|
+
* Runtime-free on purpose (no `solid-js`): the pure projections in
|
|
13
|
+
* `chart-data-table.ts` and `degraded-projections.ts`, and `validation.ts`
|
|
14
|
+
* (published as the `/validation` subpath), import it.
|
|
15
|
+
*/
|
|
16
|
+
/** The glyph a table cell shows for a missing value — `renderCellValue`'s historical `-`. */
|
|
17
|
+
export declare const MISSING_VALUE_MARKER = "-";
|
|
18
|
+
/**
|
|
19
|
+
* Chart types that encode a value as a share of a whole (or, for polar area,
|
|
20
|
+
* as a wedge whose emptiness reads as zero). Chart.js parses a `null` share
|
|
21
|
+
* as `0`, so these types refuse missing values instead of drawing them.
|
|
22
|
+
*/
|
|
23
|
+
export declare const PART_TO_WHOLE_CHART_TYPES: ReadonlySet<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Whether a raw TABLE cell value is displayed as {@link MISSING_VALUE_MARKER}.
|
|
26
|
+
*
|
|
27
|
+
* Mirrors the rules `renderCellValue` applies before any formatting — `null`,
|
|
28
|
+
* `undefined`, and a string that is empty or the word `undefined` once the
|
|
29
|
+
* backend's `"X – undefined"` debris is stripped — plus the literal marker
|
|
30
|
+
* itself (whitespace around it aside, which a table cell does not render),
|
|
31
|
+
* since it looks identical on screen and so must read identically.
|
|
32
|
+
*
|
|
33
|
+
* Objects are never missing here: `renderCellValue` renders them (link,
|
|
34
|
+
* name, or escaped JSON).
|
|
35
|
+
*/
|
|
36
|
+
export declare function isMissingCellValue(value: unknown): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Removes the `"Text – undefined"` / `"undefined – Text"` debris some
|
|
39
|
+
* backends concatenate. Shared with `renderCellValue` so both apply the very
|
|
40
|
+
* same cleanup.
|
|
41
|
+
*/
|
|
42
|
+
export declare function stripUndefinedDebris(value: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Whether one CHART data entry is missing: `null` / `undefined`, a non-finite
|
|
45
|
+
* number (Chart.js parses those to `null` too), or a point whose `y` is.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isMissingChartValue(value: unknown): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* What a chart's missing values amount to.
|
|
50
|
+
*
|
|
51
|
+
* Only ENTRIES count: an explicit `null` (or non-finite number, or a point
|
|
52
|
+
* whose `y` is one). A dataset with no entry at all (`data: []`) is not
|
|
53
|
+
* "missing" — it is how 6.23.0 and earlier payloads said "nothing yet", e.g.
|
|
54
|
+
* while streaming, and it keeps rendering exactly as it did then.
|
|
55
|
+
*/
|
|
56
|
+
export interface ChartMissingSummary {
|
|
57
|
+
/** At least one entry of one dataset is missing. */
|
|
58
|
+
hasMissing: boolean;
|
|
59
|
+
/** Per dataset, in order: it has entries and every one of them is missing. */
|
|
60
|
+
fullyMissing: boolean[];
|
|
61
|
+
/**
|
|
62
|
+
* Per dataset, in order: at least one observed value AND at least one
|
|
63
|
+
* missing entry — the series a reader actually sees broken.
|
|
64
|
+
*/
|
|
65
|
+
partiallyMissing: boolean[];
|
|
66
|
+
/** Something is missing and no dataset has a single observed value: nothing to plot. */
|
|
67
|
+
allMissing: boolean;
|
|
68
|
+
}
|
|
69
|
+
interface ChartDataLike {
|
|
70
|
+
data?: {
|
|
71
|
+
datasets?: Array<{
|
|
72
|
+
data?: unknown[];
|
|
73
|
+
} | null | undefined>;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Summarises the missing values of a chart payload. Never throws on a malformed payload. */
|
|
77
|
+
export declare function summarizeChartMissing(params: ChartDataLike | null | undefined): ChartMissingSummary;
|
|
78
|
+
export {};
|
|
79
|
+
//# sourceMappingURL=missing-value.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing-value.d.ts","sourceRoot":"","sources":["../../src/utils/missing-value.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,6FAA6F;AAC7F,eAAO,MAAM,oBAAoB,MAAM,CAAA;AAEvC;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAIxD,CAAA;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAK1D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAQ3D;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,oDAAoD;IACpD,UAAU,EAAE,OAAO,CAAA;IACnB,8EAA8E;IAC9E,YAAY,EAAE,OAAO,EAAE,CAAA;IACvB;;;OAGG;IACH,gBAAgB,EAAE,OAAO,EAAE,CAAA;IAC3B,wFAAwF;IACxF,UAAU,EAAE,OAAO,CAAA;CACpB;AAED,UAAU,aAAa;IACrB,IAAI,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,CAAC,EAAE,OAAO,EAAE,CAAA;SAAE,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;KAC1D,CAAA;CACF;AAED,6FAA6F;AAC7F,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GAAG,mBAAmB,CAyBnG"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const MISSING_VALUE_MARKER = "-";
|
|
2
|
+
const PART_TO_WHOLE_CHART_TYPES = /* @__PURE__ */ new Set([
|
|
3
|
+
"pie",
|
|
4
|
+
"doughnut",
|
|
5
|
+
"polarArea"
|
|
6
|
+
]);
|
|
7
|
+
function isMissingCellValue(value) {
|
|
8
|
+
if (value === null || value === void 0) return true;
|
|
9
|
+
if (typeof value === "object") return false;
|
|
10
|
+
const cleaned = stripUndefinedDebris(String(value)).trim();
|
|
11
|
+
return cleaned === "" || cleaned === MISSING_VALUE_MARKER || cleaned.toLowerCase() === "undefined";
|
|
12
|
+
}
|
|
13
|
+
function stripUndefinedDebris(value) {
|
|
14
|
+
return value.replace(/\s*[–-]\s*undefined\s*$/gi, "").replace(/^undefined\s*[–-]\s*/gi, "");
|
|
15
|
+
}
|
|
16
|
+
function isMissingChartValue(value) {
|
|
17
|
+
if (value === null || value === void 0) return true;
|
|
18
|
+
if (typeof value === "number") return !Number.isFinite(value);
|
|
19
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
20
|
+
const y = value.y;
|
|
21
|
+
return y === null || y === void 0 || typeof y === "number" && !Number.isFinite(y);
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function summarizeChartMissing(params) {
|
|
26
|
+
var _a;
|
|
27
|
+
const datasets = Array.isArray((_a = params == null ? void 0 : params.data) == null ? void 0 : _a.datasets) ? params.data.datasets : [];
|
|
28
|
+
const fullyMissing = [];
|
|
29
|
+
const partiallyMissing = [];
|
|
30
|
+
let anyMissing = false;
|
|
31
|
+
let anyObserved = false;
|
|
32
|
+
for (const dataset of datasets) {
|
|
33
|
+
const data = Array.isArray(dataset == null ? void 0 : dataset.data) ? dataset.data : [];
|
|
34
|
+
let observed = 0;
|
|
35
|
+
let missing = 0;
|
|
36
|
+
for (const value of data) {
|
|
37
|
+
if (isMissingChartValue(value)) missing += 1;
|
|
38
|
+
else observed += 1;
|
|
39
|
+
}
|
|
40
|
+
fullyMissing.push(missing > 0 && observed === 0);
|
|
41
|
+
partiallyMissing.push(observed > 0 && missing > 0);
|
|
42
|
+
anyMissing || (anyMissing = missing > 0);
|
|
43
|
+
anyObserved || (anyObserved = observed > 0);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
hasMissing: anyMissing,
|
|
47
|
+
fullyMissing,
|
|
48
|
+
partiallyMissing,
|
|
49
|
+
allMissing: anyMissing && !anyObserved
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
MISSING_VALUE_MARKER,
|
|
54
|
+
PART_TO_WHOLE_CHART_TYPES,
|
|
55
|
+
isMissingCellValue,
|
|
56
|
+
isMissingChartValue,
|
|
57
|
+
stripUndefinedDebris,
|
|
58
|
+
summarizeChartMissing
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=missing-value.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing-value.js","sources":["../../src/utils/missing-value.ts"],"sourcesContent":["/**\n * Missing values — one definition shared by tables, chart data views,\n * degraded projections, chart validation and the chart renderer.\n *\n * @since v6.24.0\n *\n * A missing value is a value the payload says EXISTS but was not observed:\n * `null` in a chart dataset (or as a point's `y`), `null` / `undefined` in a\n * table cell. It is not a zero, and it is not the absence of a row or of a\n * label: every view keeps its slot and marks it.\n *\n * Runtime-free on purpose (no `solid-js`): the pure projections in\n * `chart-data-table.ts` and `degraded-projections.ts`, and `validation.ts`\n * (published as the `/validation` subpath), import it.\n */\n\n/** The glyph a table cell shows for a missing value — `renderCellValue`'s historical `-`. */\nexport const MISSING_VALUE_MARKER = '-'\n\n/**\n * Chart types that encode a value as a share of a whole (or, for polar area,\n * as a wedge whose emptiness reads as zero). Chart.js parses a `null` share\n * as `0`, so these types refuse missing values instead of drawing them.\n */\nexport const PART_TO_WHOLE_CHART_TYPES: ReadonlySet<string> = new Set([\n 'pie',\n 'doughnut',\n 'polarArea',\n])\n\n/**\n * Whether a raw TABLE cell value is displayed as {@link MISSING_VALUE_MARKER}.\n *\n * Mirrors the rules `renderCellValue` applies before any formatting — `null`,\n * `undefined`, and a string that is empty or the word `undefined` once the\n * backend's `\"X – undefined\"` debris is stripped — plus the literal marker\n * itself (whitespace around it aside, which a table cell does not render),\n * since it looks identical on screen and so must read identically.\n *\n * Objects are never missing here: `renderCellValue` renders them (link,\n * name, or escaped JSON).\n */\nexport function isMissingCellValue(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'object') return false\n const cleaned = stripUndefinedDebris(String(value)).trim()\n return cleaned === '' || cleaned === MISSING_VALUE_MARKER || cleaned.toLowerCase() === 'undefined'\n}\n\n/**\n * Removes the `\"Text – undefined\"` / `\"undefined – Text\"` debris some\n * backends concatenate. Shared with `renderCellValue` so both apply the very\n * same cleanup.\n */\nexport function stripUndefinedDebris(value: string): string {\n return value.replace(/\\s*[–-]\\s*undefined\\s*$/gi, '').replace(/^undefined\\s*[–-]\\s*/gi, '')\n}\n\n/**\n * Whether one CHART data entry is missing: `null` / `undefined`, a non-finite\n * number (Chart.js parses those to `null` too), or a point whose `y` is.\n */\nexport function isMissingChartValue(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'number') return !Number.isFinite(value)\n if (typeof value === 'object' && !Array.isArray(value)) {\n const y = (value as { y?: unknown }).y\n return y === null || y === undefined || (typeof y === 'number' && !Number.isFinite(y))\n }\n return false\n}\n\n/**\n * What a chart's missing values amount to.\n *\n * Only ENTRIES count: an explicit `null` (or non-finite number, or a point\n * whose `y` is one). A dataset with no entry at all (`data: []`) is not\n * \"missing\" — it is how 6.23.0 and earlier payloads said \"nothing yet\", e.g.\n * while streaming, and it keeps rendering exactly as it did then.\n */\nexport interface ChartMissingSummary {\n /** At least one entry of one dataset is missing. */\n hasMissing: boolean\n /** Per dataset, in order: it has entries and every one of them is missing. */\n fullyMissing: boolean[]\n /**\n * Per dataset, in order: at least one observed value AND at least one\n * missing entry — the series a reader actually sees broken.\n */\n partiallyMissing: boolean[]\n /** Something is missing and no dataset has a single observed value: nothing to plot. */\n allMissing: boolean\n}\n\ninterface ChartDataLike {\n data?: {\n datasets?: Array<{ data?: unknown[] } | null | undefined>\n }\n}\n\n/** Summarises the missing values of a chart payload. Never throws on a malformed payload. */\nexport function summarizeChartMissing(params: ChartDataLike | null | undefined): ChartMissingSummary {\n const datasets = Array.isArray(params?.data?.datasets) ? params.data.datasets : []\n const fullyMissing: boolean[] = []\n const partiallyMissing: boolean[] = []\n let anyMissing = false\n let anyObserved = false\n for (const dataset of datasets) {\n const data = Array.isArray(dataset?.data) ? dataset.data : []\n let observed = 0\n let missing = 0\n for (const value of data) {\n if (isMissingChartValue(value)) missing += 1\n else observed += 1\n }\n fullyMissing.push(missing > 0 && observed === 0)\n partiallyMissing.push(observed > 0 && missing > 0)\n anyMissing ||= missing > 0\n anyObserved ||= observed > 0\n }\n return {\n hasMissing: anyMissing,\n fullyMissing,\n partiallyMissing,\n allMissing: anyMissing && !anyObserved,\n }\n}\n"],"names":[],"mappings":"AAiBO,MAAM,uBAAuB;AAO7B,MAAM,gDAAqD,IAAI;AAAA,EACpE;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAcM,SAAS,mBAAmB,OAAyB;AAC1D,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,qBAAqB,OAAO,KAAK,CAAC,EAAE,KAAA;AACpD,SAAO,YAAY,MAAM,YAAY,wBAAwB,QAAQ,kBAAkB;AACzF;AAOO,SAAS,qBAAqB,OAAuB;AAC1D,SAAO,MAAM,QAAQ,6BAA6B,EAAE,EAAE,QAAQ,0BAA0B,EAAE;AAC5F;AAMO,SAAS,oBAAoB,OAAyB;AAC3D,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,OAAO,UAAU,iBAAiB,CAAC,OAAO,SAAS,KAAK;AAC5D,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AACtD,UAAM,IAAK,MAA0B;AACrC,WAAO,MAAM,QAAQ,MAAM,UAAc,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC;AAAA,EACtF;AACA,SAAO;AACT;AA+BO,SAAS,sBAAsB,QAA+D;AApF9F;AAqFL,QAAM,WAAW,MAAM,SAAQ,sCAAQ,SAAR,mBAAc,QAAQ,IAAI,OAAO,KAAK,WAAW,CAAA;AAChF,QAAM,eAA0B,CAAA;AAChC,QAAM,mBAA8B,CAAA;AACpC,MAAI,aAAa;AACjB,MAAI,cAAc;AAClB,aAAW,WAAW,UAAU;AAC9B,UAAM,OAAO,MAAM,QAAQ,mCAAS,IAAI,IAAI,QAAQ,OAAO,CAAA;AAC3D,QAAI,WAAW;AACf,QAAI,UAAU;AACd,eAAW,SAAS,MAAM;AACxB,UAAI,oBAAoB,KAAK,EAAG,YAAW;AAAA,UACtC,aAAY;AAAA,IACnB;AACA,iBAAa,KAAK,UAAU,KAAK,aAAa,CAAC;AAC/C,qBAAiB,KAAK,WAAW,KAAK,UAAU,CAAC;AACjD,gCAAe,UAAU;AACzB,kCAAgB,WAAW;AAAA,EAC7B;AACA,SAAO;AAAA,IACL,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA,YAAY,cAAc,CAAC;AAAA,EAAA;AAE/B;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seed-ship/mcp-ui-solid",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.24.0",
|
|
4
4
|
"description": "SolidJS components for rendering MCP-generated UI resources",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -229,7 +229,7 @@
|
|
|
229
229
|
}
|
|
230
230
|
},
|
|
231
231
|
"dependencies": {
|
|
232
|
-
"@seed-ship/mcp-ui-spec": "^5.
|
|
232
|
+
"@seed-ship/mcp-ui-spec": "^5.7.0",
|
|
233
233
|
"@types/dompurify": "^3.0.5",
|
|
234
234
|
"dompurify": "^3.4.12",
|
|
235
235
|
"marked": "^16.3.0",
|
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
"test:watch": "vitest",
|
|
290
290
|
"test:browser": "vitest --mode browser",
|
|
291
291
|
"lint": "eslint src",
|
|
292
|
-
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.contracts.json",
|
|
292
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.contracts.json && tsc --noEmit -p tsconfig.examples.json",
|
|
293
293
|
"clean": "rm -rf dist",
|
|
294
294
|
"size": "size-limit",
|
|
295
295
|
"size:analyze": "size-limit --why"
|
|
@@ -32,7 +32,17 @@ import type { Accessor, JSX } from 'solid-js';
|
|
|
32
32
|
import type { UIComponent, ChartComponentParams } from '../types';
|
|
33
33
|
import { ExpandableWrapper, useExpanded } from './ExpandableWrapper';
|
|
34
34
|
import { DegradedFallback } from './DegradedFallback';
|
|
35
|
+
import { MissingValueLegend, MissingValueMark } from './MissingValue';
|
|
35
36
|
import { chartToDataTable } from './chart-data-table';
|
|
37
|
+
import {
|
|
38
|
+
applyUnitAxisTitle,
|
|
39
|
+
createMissingLegendGenerator,
|
|
40
|
+
createMissingValuesPlugin,
|
|
41
|
+
createTooltipLabel,
|
|
42
|
+
missingNoteKind,
|
|
43
|
+
withDatasetsNoGapSpan,
|
|
44
|
+
} from './chart-missing-plugin';
|
|
45
|
+
import { PART_TO_WHOLE_CHART_TYPES, summarizeChartMissing } from '../utils/missing-value';
|
|
36
46
|
import { useTelemetry } from '../context/MCPUITelemetryContext';
|
|
37
47
|
import { useMCPUIConfig } from '../context/MCPUIConfigContext';
|
|
38
48
|
import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext';
|
|
@@ -320,9 +330,51 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
|
|
|
320
330
|
point: strings.chartTablePoint,
|
|
321
331
|
label: strings.degradedColLabel,
|
|
322
332
|
seriesName: strings.degradedSeries,
|
|
333
|
+
withUnit: strings.chartLabelWithUnit,
|
|
323
334
|
})
|
|
324
335
|
);
|
|
336
|
+
// Absence is never zero: which entries are missing, and which series have
|
|
337
|
+
// nothing at all. Drives the notes, the plugin, the legend and the guard.
|
|
338
|
+
const missingSummary = createMemo(() => summarizeChartMissing(params()));
|
|
339
|
+
const unit = () => {
|
|
340
|
+
const value = params().unit;
|
|
341
|
+
return typeof value === 'string' && value !== '' ? value : undefined;
|
|
342
|
+
};
|
|
343
|
+
const anyMissingCell = () => tableData().missing.some((row) => row.some(Boolean));
|
|
344
|
+
// Never for a part-to-whole chart: it refuses missing values outright (the
|
|
345
|
+
// render effect turns it into the data table), so no convention applies —
|
|
346
|
+
// and the async guard must not leave a wrong note in the SSR markup.
|
|
347
|
+
const notesShown = () =>
|
|
348
|
+
missingSummary().hasMissing &&
|
|
349
|
+
!PART_TO_WHOLE_CHART_TYPES.has(params().type) &&
|
|
350
|
+
activeView() === 'chart' &&
|
|
351
|
+
!error();
|
|
352
|
+
const noteKind = () => missingNoteKind(params().type, params().options);
|
|
353
|
+
// The convention line explains a mark the reader can SEE: every missing bar
|
|
354
|
+
// gets a dashed stub, but a line or a scatter only shows an absence inside a
|
|
355
|
+
// series that still has values — a series with no value at all draws
|
|
356
|
+
// nothing, and the "no data" line alone covers it.
|
|
357
|
+
const conventionNoteShown = () =>
|
|
358
|
+
noteKind() === 'bars'
|
|
359
|
+
? missingSummary().hasMissing
|
|
360
|
+
: missingSummary().partiallyMissing.some(Boolean);
|
|
361
|
+
// The series the chart has no data for, as a locale-aware list.
|
|
362
|
+
const noDataSeries = () => {
|
|
363
|
+
const names = (params().data?.datasets ?? [])
|
|
364
|
+
.map((dataset, index) =>
|
|
365
|
+
missingSummary().fullyMissing[index]
|
|
366
|
+
? dataset?.label || formatMCPUIString(strings.degradedSeries, { n: index + 1 })
|
|
367
|
+
: null
|
|
368
|
+
)
|
|
369
|
+
.filter((name): name is string => name !== null);
|
|
370
|
+
try {
|
|
371
|
+
return new Intl.ListFormat(strings.locale, { type: 'conjunction' }).format(names);
|
|
372
|
+
} catch {
|
|
373
|
+
return names.join(', ');
|
|
374
|
+
}
|
|
375
|
+
};
|
|
325
376
|
const descriptionId = createUniqueId();
|
|
377
|
+
const notesId = createUniqueId();
|
|
326
378
|
const title = () => params().title || strings.chartView;
|
|
327
379
|
|
|
328
380
|
// v6.1.0 — export visibility :
|
|
@@ -456,6 +508,8 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
|
|
|
456
508
|
// plugin options at construction and the plugin only hooks charts created
|
|
457
509
|
// after it was registered).
|
|
458
510
|
const chartParams = params();
|
|
511
|
+
const chartMissing = missingSummary();
|
|
512
|
+
const chartUnit = unit();
|
|
459
513
|
const zoom = zoomOptions();
|
|
460
514
|
const version = ++renderVersion;
|
|
461
515
|
|
|
@@ -474,6 +528,14 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
|
|
|
474
528
|
chartInstance = null;
|
|
475
529
|
}
|
|
476
530
|
|
|
531
|
+
// A share of a whole cannot be missing: Chart.js parses `null` as 0 and
|
|
532
|
+
// would draw it as a zero slice. Validation rejects this earlier; this
|
|
533
|
+
// is the guard for a host that bypassed it. Thrown here so the catch
|
|
534
|
+
// below shows the data table (with the missing cells marked).
|
|
535
|
+
if (chartMissing.hasMissing && PART_TO_WHOLE_CHART_TYPES.has(chartParams.type)) {
|
|
536
|
+
throw new Error(strings.chartMissingUnsupported);
|
|
537
|
+
}
|
|
538
|
+
|
|
477
539
|
// Build options, merging time-axis config if present (v3.1.0)
|
|
478
540
|
const hostZoomOptions = chartParams.options?.plugins?.zoom;
|
|
479
541
|
const baseOptions: any = {
|
|
@@ -517,12 +579,69 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
|
|
|
517
579
|
};
|
|
518
580
|
}
|
|
519
581
|
|
|
520
|
-
//
|
|
521
|
-
|
|
582
|
+
// Unit (v6.24.0): value-axis title and tooltip suffix. Both are no-ops
|
|
583
|
+
// on a payload without a unit.
|
|
584
|
+
applyUnitAxisTitle(baseOptions, chartParams.type, chartUnit);
|
|
585
|
+
|
|
586
|
+
const chartConfig: any = {
|
|
522
587
|
type: chartParams.type,
|
|
523
588
|
data: chartParams.data,
|
|
524
589
|
options: baseOptions,
|
|
525
|
-
}
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
if (chartUnit !== undefined || chartMissing.hasMissing) {
|
|
593
|
+
const tooltip = baseOptions.plugins.tooltip;
|
|
594
|
+
// `tooltip: false` (or `null`) is Chart.js's per-chart plugin opt-out:
|
|
595
|
+
// the payload turned tooltips off, and adding a callback object here
|
|
596
|
+
// would turn them back on. A host-supplied label callback is the
|
|
597
|
+
// host's word too. Both are left exactly as written.
|
|
598
|
+
if (tooltip !== false && tooltip !== null && !tooltip?.callbacks?.label) {
|
|
599
|
+
baseOptions.plugins.tooltip = {
|
|
600
|
+
...tooltip,
|
|
601
|
+
callbacks: {
|
|
602
|
+
...tooltip?.callbacks,
|
|
603
|
+
label: createTooltipLabel(() => strings, chartUnit, chartParams.type),
|
|
604
|
+
},
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (chartMissing.hasMissing) {
|
|
610
|
+
// A line must break at a missing value, not join its neighbours.
|
|
611
|
+
chartConfig.data = withDatasetsNoGapSpan(chartParams.data);
|
|
612
|
+
|
|
613
|
+
// Legend of a fully-missing series: "{series} (no data)". Dataset
|
|
614
|
+
// legends only — pie-like legends list categories, not series.
|
|
615
|
+
const defaultGenerate =
|
|
616
|
+
baseOptions.plugins.legend?.labels?.generateLabels ??
|
|
617
|
+
Chart?.defaults?.plugins?.legend?.labels?.generateLabels;
|
|
618
|
+
if (typeof defaultGenerate === 'function') {
|
|
619
|
+
baseOptions.plugins.legend = {
|
|
620
|
+
...baseOptions.plugins.legend,
|
|
621
|
+
labels: {
|
|
622
|
+
...baseOptions.plugins.legend?.labels,
|
|
623
|
+
generateLabels: createMissingLegendGenerator(
|
|
624
|
+
defaultGenerate,
|
|
625
|
+
chartMissing.fullyMissing,
|
|
626
|
+
() => strings
|
|
627
|
+
),
|
|
628
|
+
},
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Per-chart plugin (never `Chart.register`): the dashed stub of a
|
|
633
|
+
// missing bar, and "no data" on a chart with nothing to plot.
|
|
634
|
+
chartConfig.plugins = [
|
|
635
|
+
createMissingValuesPlugin({
|
|
636
|
+
allMissing: chartMissing.allMissing,
|
|
637
|
+
getNoDataText: () => strings.chartNoData,
|
|
638
|
+
getFontFamily: () => Chart?.defaults?.font?.family,
|
|
639
|
+
}),
|
|
640
|
+
];
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Create new chart
|
|
644
|
+
chartInstance = new Chart(canvasRef, chartConfig);
|
|
526
645
|
|
|
527
646
|
// A freshly built chart always sits on its initial viewport, so the
|
|
528
647
|
// reset control goes away until the user zooms again.
|
|
@@ -761,12 +880,17 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
|
|
|
761
880
|
</thead>
|
|
762
881
|
<tbody>
|
|
763
882
|
<For each={tableData().rows}>
|
|
764
|
-
{(row) => (
|
|
883
|
+
{(row, rowIndex) => (
|
|
765
884
|
<tr class="border-t border-gray-100 dark:border-gray-700">
|
|
766
885
|
<For each={tableData().columns}>
|
|
767
886
|
{(_column, index) => (
|
|
768
887
|
<td class="px-3 py-2 text-gray-700 dark:text-gray-300">
|
|
769
|
-
|
|
888
|
+
<Show
|
|
889
|
+
when={tableData().missing[rowIndex()]?.[index()]}
|
|
890
|
+
fallback={String(row[index()] ?? '')}
|
|
891
|
+
>
|
|
892
|
+
<MissingValueMark />
|
|
893
|
+
</Show>
|
|
770
894
|
</td>
|
|
771
895
|
)}
|
|
772
896
|
</For>
|
|
@@ -786,6 +910,9 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
|
|
|
786
910
|
</tbody>
|
|
787
911
|
</table>
|
|
788
912
|
</div>
|
|
913
|
+
<Show when={anyMissingCell()}>
|
|
914
|
+
<MissingValueLegend class="mt-2" />
|
|
915
|
+
</Show>
|
|
789
916
|
</Show>
|
|
790
917
|
|
|
791
918
|
<div
|
|
@@ -802,7 +929,7 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
|
|
|
802
929
|
ref={canvasRef}
|
|
803
930
|
role="img"
|
|
804
931
|
aria-label={title()}
|
|
805
|
-
aria-describedby={descriptionId}
|
|
932
|
+
aria-describedby={notesShown() ? `${descriptionId} ${notesId}` : descriptionId}
|
|
806
933
|
aria-hidden={activeView() !== 'chart' || Boolean(error())}
|
|
807
934
|
/>
|
|
808
935
|
<Show when={isLoading() && activeView() === 'chart'}>
|
|
@@ -814,6 +941,32 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
|
|
|
814
941
|
</div>
|
|
815
942
|
</Show>
|
|
816
943
|
</div>
|
|
944
|
+
|
|
945
|
+
{/* Missing values (v6.24.0): how THIS chart type shows them, and which
|
|
946
|
+
series have no data at all. Linked from the canvas, so a screen
|
|
947
|
+
reader hears the convention with the chart. */}
|
|
948
|
+
<Show when={notesShown()}>
|
|
949
|
+
<div
|
|
950
|
+
data-mcp-chart-notes=""
|
|
951
|
+
id={notesId}
|
|
952
|
+
class="mt-2 space-y-0.5 text-xs text-gray-600 dark:text-gray-400"
|
|
953
|
+
>
|
|
954
|
+
<Show when={conventionNoteShown()}>
|
|
955
|
+
<p data-mcp-chart-note={noteKind()}>
|
|
956
|
+
{noteKind() === 'bars'
|
|
957
|
+
? strings.chartMissingBars
|
|
958
|
+
: noteKind() === 'omitted'
|
|
959
|
+
? strings.chartMissingPoints
|
|
960
|
+
: strings.chartMissingGaps}
|
|
961
|
+
</p>
|
|
962
|
+
</Show>
|
|
963
|
+
<Show when={missingSummary().fullyMissing.some(Boolean)}>
|
|
964
|
+
<p data-mcp-chart-note="no-data">
|
|
965
|
+
{formatMCPUIString(strings.chartSeriesNoData, { series: noDataSeries() })}
|
|
966
|
+
</p>
|
|
967
|
+
</Show>
|
|
968
|
+
</div>
|
|
969
|
+
</Show>
|
|
817
970
|
</div>
|
|
818
971
|
)}
|
|
819
972
|
/>
|