@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
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { Component, For, Show } from 'solid-js';
|
|
19
19
|
import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext';
|
|
20
|
+
import { MissingValueLegend, MissingValueMark } from './MissingValue';
|
|
20
21
|
|
|
21
22
|
export interface DegradedFallbackProps {
|
|
22
23
|
/** Short, human-readable reason the native render was skipped/failed. */
|
|
@@ -35,6 +36,13 @@ export interface DegradedFallbackProps {
|
|
|
35
36
|
caption?: string;
|
|
36
37
|
/** Max rows to render before truncating (default 50). */
|
|
37
38
|
maxRows?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Same shape as `rows`: `true` marks a cell whose value is missing, which
|
|
41
|
+
* renders as `<MissingValueMark/>` instead of its text. A
|
|
42
|
+
* `<MissingValueLegend/>` follows the table when a SHOWN cell is missing.
|
|
43
|
+
* Omitted, the output is unchanged. @since v6.24.0
|
|
44
|
+
*/
|
|
45
|
+
missing?: boolean[][];
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
|
|
@@ -43,6 +51,15 @@ export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
|
|
|
43
51
|
const allRows = () => props.rows ?? [];
|
|
44
52
|
const shownRows = () => allRows().slice(0, maxRows());
|
|
45
53
|
const hiddenCount = () => Math.max(0, allRows().length - shownRows().length);
|
|
54
|
+
const isMissing = (rowIndex: number, colIndex: number) =>
|
|
55
|
+
props.missing?.[rowIndex]?.[colIndex] === true;
|
|
56
|
+
const anyShownMissing = () => {
|
|
57
|
+
const columnCount = props.columns?.length ?? 0;
|
|
58
|
+
return shownRows().some((_row, r) => {
|
|
59
|
+
for (let c = 0; c < columnCount; c += 1) if (isMissing(r, c)) return true;
|
|
60
|
+
return false;
|
|
61
|
+
});
|
|
62
|
+
};
|
|
46
63
|
const hasTable = () => (props.columns?.length ?? 0) > 0 && allRows().length > 0;
|
|
47
64
|
|
|
48
65
|
return (
|
|
@@ -69,12 +86,14 @@ export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
|
|
|
69
86
|
</thead>
|
|
70
87
|
<tbody>
|
|
71
88
|
<For each={shownRows()}>
|
|
72
|
-
{(row) => (
|
|
89
|
+
{(row, rowIndex) => (
|
|
73
90
|
<tr class="border-t border-amber-100 dark:border-amber-800/60">
|
|
74
91
|
<For each={props.columns}>
|
|
75
92
|
{(_col, i) => (
|
|
76
93
|
<td class="px-2 py-1 text-amber-800 dark:text-amber-200">
|
|
77
|
-
{String(row[i()] ?? '')}
|
|
94
|
+
<Show when={isMissing(rowIndex(), i())} fallback={String(row[i()] ?? '')}>
|
|
95
|
+
<MissingValueMark />
|
|
96
|
+
</Show>
|
|
78
97
|
</td>
|
|
79
98
|
)}
|
|
80
99
|
</For>
|
|
@@ -84,6 +103,9 @@ export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
|
|
|
84
103
|
</tbody>
|
|
85
104
|
</table>
|
|
86
105
|
</div>
|
|
106
|
+
<Show when={anyShownMissing()}>
|
|
107
|
+
<MissingValueLegend class="mt-2" />
|
|
108
|
+
</Show>
|
|
87
109
|
<Show when={hiddenCount() > 0}>
|
|
88
110
|
<p class="mt-1 text-[10px] text-amber-600 dark:text-amber-400">
|
|
89
111
|
{formatMCPUIString(strings.degradedMoreRows, { count: hiddenCount() })}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The visible and accessible marks of a missing value (v6.24.0).
|
|
3
|
+
*
|
|
4
|
+
* A table cell with no value has always shown `-`. On its own that glyph says
|
|
5
|
+
* nothing to a screen reader (it is read as "dash", or skipped) and it is easy
|
|
6
|
+
* to take for a zero or a minus sign. These two components give it a meaning:
|
|
7
|
+
*
|
|
8
|
+
* - {@link MissingValueMark} — the cell content: the `-` hidden from
|
|
9
|
+
* assistive technology, followed by `strings.missingValue` hidden from
|
|
10
|
+
* sight. Visually-hidden through an inline style rather than Tailwind's
|
|
11
|
+
* `sr-only`, so the text cannot leak on a host whose Tailwind build does
|
|
12
|
+
* not scan this package.
|
|
13
|
+
* - {@link MissingValueLegend} — one line under a table or data view that
|
|
14
|
+
* contains a missing value, so the convention never has to be guessed.
|
|
15
|
+
*
|
|
16
|
+
* Both are SSR-safe: no browser API, and the same markup on server and client.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { Component, JSX } from 'solid-js'
|
|
20
|
+
import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
|
|
21
|
+
import { MISSING_VALUE_MARKER } from '../utils/missing-value'
|
|
22
|
+
|
|
23
|
+
/** The classic visually-hidden recipe: in the accessibility tree, out of sight. */
|
|
24
|
+
export const VISUALLY_HIDDEN_STYLE: JSX.CSSProperties = {
|
|
25
|
+
position: 'absolute',
|
|
26
|
+
width: '1px',
|
|
27
|
+
height: '1px',
|
|
28
|
+
padding: '0',
|
|
29
|
+
margin: '-1px',
|
|
30
|
+
overflow: 'hidden',
|
|
31
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
32
|
+
'white-space': 'nowrap',
|
|
33
|
+
border: '0',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Content of a cell whose value is missing. */
|
|
37
|
+
export const MissingValueMark: Component = () => {
|
|
38
|
+
const strings = useMCPUIStrings()
|
|
39
|
+
return (
|
|
40
|
+
<span data-mcp-missing-value="">
|
|
41
|
+
<span aria-hidden="true" class="text-gray-400 dark:text-gray-500">
|
|
42
|
+
{MISSING_VALUE_MARKER}
|
|
43
|
+
</span>
|
|
44
|
+
<span style={VISUALLY_HIDDEN_STYLE}>{strings.missingValue}</span>
|
|
45
|
+
</span>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The legend line explaining the missing-value glyph. */
|
|
50
|
+
export const MissingValueLegend: Component<{ class?: string }> = (props) => {
|
|
51
|
+
const strings = useMCPUIStrings()
|
|
52
|
+
return (
|
|
53
|
+
<p
|
|
54
|
+
data-mcp-missing-legend=""
|
|
55
|
+
role="note"
|
|
56
|
+
class={`text-xs text-gray-500 dark:text-gray-400 ${props.class ?? ''}`}
|
|
57
|
+
>
|
|
58
|
+
{formatMCPUIString(strings.missingValueLegend, { marker: MISSING_VALUE_MARKER })}
|
|
59
|
+
</p>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
useMCPUIStrings,
|
|
29
29
|
} from '../context/MCPUIStringsContext'
|
|
30
30
|
import { useMCPUIConfig } from '../context/MCPUIConfigContext'
|
|
31
|
+
import { isMissingCellValue, stripUndefinedDebris } from '../utils/missing-value'
|
|
32
|
+
import { MissingValueLegend, MissingValueMark } from './MissingValue'
|
|
31
33
|
import { shouldSetCredentialless } from '../utils/iframe-coep'
|
|
32
34
|
import { IframeFallbackLink } from './IframeFallbackLink'
|
|
33
35
|
|
|
@@ -347,6 +349,7 @@ function ChartRenderer(props: {
|
|
|
347
349
|
caption={strings.chartQuickchartCaption}
|
|
348
350
|
{...chartToDegradedTable(params() ?? {}, {
|
|
349
351
|
series: strings.degradedSeries,
|
|
352
|
+
withUnit: strings.chartLabelWithUnit,
|
|
350
353
|
})}
|
|
351
354
|
/>
|
|
352
355
|
</div>
|
|
@@ -639,9 +642,10 @@ export function renderCellValue(value: any, citationCtx?: CitationCtx, mathRende
|
|
|
639
642
|
|
|
640
643
|
// Clean up "undefined" patterns from backend data
|
|
641
644
|
// Pattern 1: "Text – undefined" or "Text - undefined" → "Text"
|
|
642
|
-
strValue = strValue.replace(/\s*[–-]\s*undefined\s*$/gi, '')
|
|
643
645
|
// Pattern 2: "undefined – Text" or "undefined - Text" → "Text"
|
|
644
|
-
|
|
646
|
+
// (shared with `isMissingCellValue`, so the cells shown as `-` and the cells
|
|
647
|
+
// the table marks and explains as missing can never drift apart)
|
|
648
|
+
strValue = stripUndefinedDebris(strValue)
|
|
645
649
|
// Pattern 3: standalone "undefined" → "-"
|
|
646
650
|
if (strValue.trim().toLowerCase() === 'undefined') {
|
|
647
651
|
return '-'
|
|
@@ -766,12 +770,18 @@ function TableRenderer(props: {
|
|
|
766
770
|
const dir = sortDir()
|
|
767
771
|
if (!key || !dir) return r
|
|
768
772
|
const col = columns().find((c: any) => c.key === key)
|
|
769
|
-
|
|
773
|
+
// v6.24.0 — numeric mode is decided from the first NON-missing value, so a
|
|
774
|
+
// missing first row no longer flips a numeric column to string order.
|
|
775
|
+
const firstObserved = r.find((row: any) => !isMissingCellValue(row?.[key]))?.[key]
|
|
776
|
+
const isNum = col?.type === 'number' || typeof firstObserved === 'number'
|
|
770
777
|
return [...r].sort((a: any, b: any) => {
|
|
771
778
|
const va = a[key], vb = b[key]
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
779
|
+
// Missing values (null, undefined, '', 'undefined', '-') sort last in
|
|
780
|
+
// both directions — never as 0 and never first.
|
|
781
|
+
const missA = isMissingCellValue(va), missB = isMissingCellValue(vb)
|
|
782
|
+
if (missA && missB) return 0
|
|
783
|
+
if (missA) return 1
|
|
784
|
+
if (missB) return -1
|
|
775
785
|
let cmp: number
|
|
776
786
|
if (isNum) {
|
|
777
787
|
cmp = (Number(va) || 0) - (Number(vb) || 0)
|
|
@@ -820,7 +830,7 @@ function TableRenderer(props: {
|
|
|
820
830
|
return sortedRows().filter((row: any) =>
|
|
821
831
|
cols.some((col: any) => {
|
|
822
832
|
const val = row[col.key]
|
|
823
|
-
if (val
|
|
833
|
+
if (isMissingCellValue(val)) return false
|
|
824
834
|
return normalize(String(val)).includes(q)
|
|
825
835
|
})
|
|
826
836
|
)
|
|
@@ -1015,6 +1025,14 @@ function TableRenderer(props: {
|
|
|
1015
1025
|
|
|
1016
1026
|
const tableId = `table-${Math.random().toString(36).slice(2, 9)}`
|
|
1017
1027
|
|
|
1028
|
+
// v6.24.0 — whether ANY declared cell of ANY row is missing. Deliberately
|
|
1029
|
+
// computed on all rows (not the current page / filter) so the legend does not
|
|
1030
|
+
// flicker while paging, sorting or searching.
|
|
1031
|
+
const hasMissingCells = createMemo(() => {
|
|
1032
|
+
const cols = columns()
|
|
1033
|
+
return allRows().some((row: any) => cols.some((col: any) => isMissingCellValue(row?.[col.key])))
|
|
1034
|
+
})
|
|
1035
|
+
|
|
1018
1036
|
// Standard table body (non-virtualized) — uses client pagination when active
|
|
1019
1037
|
const StandardTableBody = () => (
|
|
1020
1038
|
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
@@ -1024,7 +1042,12 @@ function TableRenderer(props: {
|
|
|
1024
1042
|
<For each={tableParams.columns}>
|
|
1025
1043
|
{(column: any) => (
|
|
1026
1044
|
<td class="px-6 py-4 text-sm text-gray-700 dark:text-gray-200 whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
|
|
1027
|
-
<
|
|
1045
|
+
<Show
|
|
1046
|
+
when={!isMissingCellValue(row[column.key])}
|
|
1047
|
+
fallback={<MissingValueMark />}
|
|
1048
|
+
>
|
|
1049
|
+
<SafeHtml html={() => highlightQuery(renderCellValue(row[column.key], citationCtx, mathRenderer()), debouncedQuery())} />
|
|
1050
|
+
</Show>
|
|
1028
1051
|
</td>
|
|
1029
1052
|
)}
|
|
1030
1053
|
</For>
|
|
@@ -1063,7 +1086,12 @@ function TableRenderer(props: {
|
|
|
1063
1086
|
<For each={tableParams.columns}>
|
|
1064
1087
|
{(column: any) => (
|
|
1065
1088
|
<td class="px-6 py-4 text-sm text-gray-700 dark:text-gray-200 whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
|
|
1066
|
-
<
|
|
1089
|
+
<Show
|
|
1090
|
+
when={!isMissingCellValue(row[column.key])}
|
|
1091
|
+
fallback={<MissingValueMark />}
|
|
1092
|
+
>
|
|
1093
|
+
<SafeHtml html={() => highlightQuery(renderCellValue(row[column.key], citationCtx, mathRenderer()), debouncedQuery())} />
|
|
1094
|
+
</Show>
|
|
1067
1095
|
</td>
|
|
1068
1096
|
)}
|
|
1069
1097
|
</For>
|
|
@@ -1222,6 +1250,11 @@ function TableRenderer(props: {
|
|
|
1222
1250
|
</table>
|
|
1223
1251
|
</div>
|
|
1224
1252
|
|
|
1253
|
+
{/* Missing-value legend (v6.24.0) — outside the scroll region, before pagination */}
|
|
1254
|
+
<Show when={hasMissingCells()}>
|
|
1255
|
+
<MissingValueLegend class="mt-2 px-1" />
|
|
1256
|
+
</Show>
|
|
1257
|
+
|
|
1225
1258
|
{/* Server-side pagination (legacy) */}
|
|
1226
1259
|
<Show when={tableParams.pagination}>
|
|
1227
1260
|
<div class="mt-3 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatMCPUIString } from '../utils/format-string';
|
|
2
|
+
import { isMissingChartValue } from '../utils/missing-value';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Column headers and series names of the accessible chart data table.
|
|
@@ -26,10 +27,17 @@ export interface ChartDataTableLabels {
|
|
|
26
27
|
x: string;
|
|
27
28
|
y: string;
|
|
28
29
|
r: string;
|
|
30
|
+
/**
|
|
31
|
+
* A value column header followed by the chart's `unit`. Template — `{label}`,
|
|
32
|
+
* `{unit}`. Only applied when the chart has a unit. Optional so that a
|
|
33
|
+
* complete labels table written for 6.20.0–6.23.0 still type-checks.
|
|
34
|
+
* @since v6.24.0
|
|
35
|
+
*/
|
|
36
|
+
withUnit?: string;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
/** English defaults for {@link ChartDataTableLabels}. */
|
|
32
|
-
export const CHART_DATA_TABLE_LABELS: ChartDataTableLabels = {
|
|
40
|
+
export const CHART_DATA_TABLE_LABELS: Required<ChartDataTableLabels> = {
|
|
33
41
|
series: 'Series',
|
|
34
42
|
point: 'Point',
|
|
35
43
|
label: 'Label',
|
|
@@ -37,11 +45,20 @@ export const CHART_DATA_TABLE_LABELS: ChartDataTableLabels = {
|
|
|
37
45
|
x: 'x',
|
|
38
46
|
y: 'y',
|
|
39
47
|
r: 'r',
|
|
48
|
+
withUnit: '{label} ({unit})',
|
|
40
49
|
};
|
|
41
50
|
|
|
42
51
|
export interface ChartDataTable {
|
|
43
52
|
columns: string[];
|
|
44
53
|
rows: Array<Array<string | number>>;
|
|
54
|
+
/**
|
|
55
|
+
* Same shape as `rows`: `true` for a VALUE cell whose source entry is
|
|
56
|
+
* missing (`null` / non-finite, or a point whose `y` is). A slot past the
|
|
57
|
+
* end of a shorter or empty dataset has no entry and is not flagged. Label,
|
|
58
|
+
* series, point-index, `x` and `r` cells are never missing. `rows` itself
|
|
59
|
+
* still holds `''` there. @since v6.24.0
|
|
60
|
+
*/
|
|
61
|
+
missing: boolean[][];
|
|
45
62
|
}
|
|
46
63
|
|
|
47
64
|
interface ChartDatasetLike {
|
|
@@ -51,6 +68,8 @@ interface ChartDatasetLike {
|
|
|
51
68
|
|
|
52
69
|
export interface ChartDataLike {
|
|
53
70
|
type?: string;
|
|
71
|
+
/** Unit of the values: appended to the value column headers. @since v6.24.0 */
|
|
72
|
+
unit?: string;
|
|
54
73
|
data?: {
|
|
55
74
|
labels?: Array<string | number>;
|
|
56
75
|
datasets?: ChartDatasetLike[];
|
|
@@ -87,6 +106,10 @@ export function chartToDataTable(
|
|
|
87
106
|
labels?: Partial<ChartDataTableLabels>
|
|
88
107
|
): ChartDataTable {
|
|
89
108
|
const l: ChartDataTableLabels = { ...CHART_DATA_TABLE_LABELS, ...labels };
|
|
109
|
+
const unit = typeof params.unit === 'string' && params.unit !== '' ? params.unit : undefined;
|
|
110
|
+
const unitTemplate = l.withUnit ?? CHART_DATA_TABLE_LABELS.withUnit;
|
|
111
|
+
const withUnit = (label: string) =>
|
|
112
|
+
unit === undefined ? label : formatMCPUIString(unitTemplate, { label, unit });
|
|
90
113
|
const datasets = params.data?.datasets ?? [];
|
|
91
114
|
const dataLabels = params.data?.labels ?? [];
|
|
92
115
|
const pointData =
|
|
@@ -104,10 +127,11 @@ export function chartToDataTable(
|
|
|
104
127
|
l.point,
|
|
105
128
|
...(hasLabels ? [l.label] : []),
|
|
106
129
|
l.x,
|
|
107
|
-
l.y,
|
|
130
|
+
withUnit(l.y),
|
|
108
131
|
...(hasRadius ? [l.r] : []),
|
|
109
132
|
];
|
|
110
133
|
const rows: Array<Array<string | number>> = [];
|
|
134
|
+
const missing: boolean[][] = [];
|
|
111
135
|
|
|
112
136
|
datasets.forEach((dataset, datasetIndex) => {
|
|
113
137
|
const series = dataset.label || formatMCPUIString(l.seriesName, { n: datasetIndex + 1 });
|
|
@@ -121,10 +145,20 @@ export function chartToDataTable(
|
|
|
121
145
|
tableCell(point.y),
|
|
122
146
|
...(hasRadius ? [tableCell(point.r)] : []),
|
|
123
147
|
]);
|
|
148
|
+
// Only the y cell can be missing; a bare entry is its own y.
|
|
149
|
+
const yMissing = isMissingChartValue(isPoint(value) ? point.y : value);
|
|
150
|
+
missing.push([
|
|
151
|
+
false,
|
|
152
|
+
false,
|
|
153
|
+
...(hasLabels ? [false] : []),
|
|
154
|
+
false,
|
|
155
|
+
yMissing,
|
|
156
|
+
...(hasRadius ? [false] : []),
|
|
157
|
+
]);
|
|
124
158
|
});
|
|
125
159
|
});
|
|
126
160
|
|
|
127
|
-
return { columns, rows };
|
|
161
|
+
return { columns, rows, missing };
|
|
128
162
|
}
|
|
129
163
|
|
|
130
164
|
const rowCount = Math.max(
|
|
@@ -134,18 +168,28 @@ export function chartToDataTable(
|
|
|
134
168
|
);
|
|
135
169
|
const columns = [
|
|
136
170
|
l.label,
|
|
137
|
-
...datasets.map(
|
|
138
|
-
(dataset
|
|
171
|
+
...datasets.map((dataset, index) =>
|
|
172
|
+
withUnit(dataset.label || formatMCPUIString(l.seriesName, { n: index + 1 }))
|
|
139
173
|
),
|
|
140
174
|
];
|
|
141
175
|
const rows: Array<Array<string | number>> = [];
|
|
176
|
+
const missing: boolean[][] = [];
|
|
142
177
|
|
|
143
178
|
for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
|
|
144
179
|
rows.push([
|
|
145
180
|
tableCell(dataLabels[rowIndex] ?? rowIndex + 1),
|
|
146
181
|
...datasets.map((dataset) => tableCell(dataset.data?.[rowIndex])),
|
|
147
182
|
]);
|
|
183
|
+
// Only an ENTRY can be missing: a slot past the end of a shorter (or
|
|
184
|
+
// empty) dataset has no entry, and renders as before (an empty cell).
|
|
185
|
+
missing.push([
|
|
186
|
+
false,
|
|
187
|
+
...datasets.map(
|
|
188
|
+
(dataset) =>
|
|
189
|
+
rowIndex < (dataset.data?.length ?? 0) && isMissingChartValue(dataset.data?.[rowIndex])
|
|
190
|
+
),
|
|
191
|
+
]);
|
|
148
192
|
}
|
|
149
193
|
|
|
150
|
-
return { columns, rows };
|
|
194
|
+
return { columns, rows, missing };
|
|
151
195
|
}
|