@seed-ship/mcp-ui-solid 6.23.0 → 6.25.1

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.
Files changed (172) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/README.md +154 -2
  3. package/dist/components/ActionGroupRenderer.cjs +6 -6
  4. package/dist/components/ActionGroupRenderer.cjs.map +1 -1
  5. package/dist/components/ActionGroupRenderer.js +6 -6
  6. package/dist/components/ActionGroupRenderer.js.map +1 -1
  7. package/dist/components/AutocompleteDropdown.cjs +6 -6
  8. package/dist/components/AutocompleteDropdown.cjs.map +1 -1
  9. package/dist/components/AutocompleteDropdown.js +6 -6
  10. package/dist/components/AutocompleteDropdown.js.map +1 -1
  11. package/dist/components/AutocompleteFormField.cjs +6 -5
  12. package/dist/components/AutocompleteFormField.cjs.map +1 -1
  13. package/dist/components/AutocompleteFormField.d.ts.map +1 -1
  14. package/dist/components/AutocompleteFormField.js +6 -5
  15. package/dist/components/AutocompleteFormField.js.map +1 -1
  16. package/dist/components/ChartJSRenderer.cjs +406 -70
  17. package/dist/components/ChartJSRenderer.cjs.map +1 -1
  18. package/dist/components/ChartJSRenderer.d.ts.map +1 -1
  19. package/dist/components/ChartJSRenderer.js +407 -71
  20. package/dist/components/ChartJSRenderer.js.map +1 -1
  21. package/dist/components/ChatPrompt.cjs +5 -5
  22. package/dist/components/ChatPrompt.cjs.map +1 -1
  23. package/dist/components/ChatPrompt.js +5 -5
  24. package/dist/components/ChatPrompt.js.map +1 -1
  25. package/dist/components/ComponentToolbar.cjs +5 -5
  26. package/dist/components/ComponentToolbar.cjs.map +1 -1
  27. package/dist/components/ComponentToolbar.js +5 -5
  28. package/dist/components/ComponentToolbar.js.map +1 -1
  29. package/dist/components/DegradedFallback.cjs +35 -3
  30. package/dist/components/DegradedFallback.cjs.map +1 -1
  31. package/dist/components/DegradedFallback.d.ts +7 -0
  32. package/dist/components/DegradedFallback.d.ts.map +1 -1
  33. package/dist/components/DegradedFallback.js +35 -3
  34. package/dist/components/DegradedFallback.js.map +1 -1
  35. package/dist/components/ExpandableWrapper.cjs +10 -7
  36. package/dist/components/ExpandableWrapper.cjs.map +1 -1
  37. package/dist/components/ExpandableWrapper.d.ts.map +1 -1
  38. package/dist/components/ExpandableWrapper.js +10 -7
  39. package/dist/components/ExpandableWrapper.js.map +1 -1
  40. package/dist/components/FormFieldRenderer.cjs +13 -12
  41. package/dist/components/FormFieldRenderer.cjs.map +1 -1
  42. package/dist/components/FormFieldRenderer.d.ts.map +1 -1
  43. package/dist/components/FormFieldRenderer.js +13 -12
  44. package/dist/components/FormFieldRenderer.js.map +1 -1
  45. package/dist/components/FormRenderer.cjs +1 -1
  46. package/dist/components/FormRenderer.cjs.map +1 -1
  47. package/dist/components/FormRenderer.js +1 -1
  48. package/dist/components/FormRenderer.js.map +1 -1
  49. package/dist/components/GenerativeUIErrorBoundary.cjs +1 -1
  50. package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
  51. package/dist/components/GenerativeUIErrorBoundary.js +1 -1
  52. package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
  53. package/dist/components/GhostText.cjs +2 -2
  54. package/dist/components/GhostText.cjs.map +1 -1
  55. package/dist/components/GhostText.js +2 -2
  56. package/dist/components/GhostText.js.map +1 -1
  57. package/dist/components/GraphRenderer.cjs +100 -29
  58. package/dist/components/GraphRenderer.cjs.map +1 -1
  59. package/dist/components/GraphRenderer.d.ts +1 -1
  60. package/dist/components/GraphRenderer.d.ts.map +1 -1
  61. package/dist/components/GraphRenderer.js +100 -29
  62. package/dist/components/GraphRenderer.js.map +1 -1
  63. package/dist/components/MissingValue.cjs +43 -0
  64. package/dist/components/MissingValue.cjs.map +1 -0
  65. package/dist/components/MissingValue.d.ts +27 -0
  66. package/dist/components/MissingValue.d.ts.map +1 -0
  67. package/dist/components/MissingValue.js +43 -0
  68. package/dist/components/MissingValue.js.map +1 -0
  69. package/dist/components/ModalRenderer.cjs +39 -31
  70. package/dist/components/ModalRenderer.cjs.map +1 -1
  71. package/dist/components/ModalRenderer.d.ts.map +1 -1
  72. package/dist/components/ModalRenderer.js +40 -32
  73. package/dist/components/ModalRenderer.js.map +1 -1
  74. package/dist/components/PortalDropdownMenu.cjs +6 -3
  75. package/dist/components/PortalDropdownMenu.cjs.map +1 -1
  76. package/dist/components/PortalDropdownMenu.d.ts.map +1 -1
  77. package/dist/components/PortalDropdownMenu.js +7 -4
  78. package/dist/components/PortalDropdownMenu.js.map +1 -1
  79. package/dist/components/ScratchpadPanel.cjs +38 -44
  80. package/dist/components/ScratchpadPanel.cjs.map +1 -1
  81. package/dist/components/ScratchpadPanel.d.ts.map +1 -1
  82. package/dist/components/ScratchpadPanel.js +38 -44
  83. package/dist/components/ScratchpadPanel.js.map +1 -1
  84. package/dist/components/UIResourceRenderer.cjs +255 -217
  85. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  86. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  87. package/dist/components/UIResourceRenderer.js +255 -217
  88. package/dist/components/UIResourceRenderer.js.map +1 -1
  89. package/dist/components/chart-data-table.cjs +30 -5
  90. package/dist/components/chart-data-table.cjs.map +1 -1
  91. package/dist/components/chart-data-table.d.ts +18 -1
  92. package/dist/components/chart-data-table.d.ts.map +1 -1
  93. package/dist/components/chart-data-table.js +30 -5
  94. package/dist/components/chart-data-table.js.map +1 -1
  95. package/dist/components/chart-missing-plugin.cjs +194 -0
  96. package/dist/components/chart-missing-plugin.cjs.map +1 -0
  97. package/dist/components/chart-missing-plugin.d.ts +129 -0
  98. package/dist/components/chart-missing-plugin.d.ts.map +1 -0
  99. package/dist/components/chart-missing-plugin.js +194 -0
  100. package/dist/components/chart-missing-plugin.js.map +1 -0
  101. package/dist/context/MCPUIStringsContext.cjs +12 -1
  102. package/dist/context/MCPUIStringsContext.cjs.map +1 -1
  103. package/dist/context/MCPUIStringsContext.d.ts +62 -0
  104. package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
  105. package/dist/context/MCPUIStringsContext.js +12 -1
  106. package/dist/context/MCPUIStringsContext.js.map +1 -1
  107. package/dist/services/component-registry.cjs +9 -2
  108. package/dist/services/component-registry.cjs.map +1 -1
  109. package/dist/services/component-registry.d.ts.map +1 -1
  110. package/dist/services/component-registry.js +9 -2
  111. package/dist/services/component-registry.js.map +1 -1
  112. package/dist/services/validation.cjs +31 -8
  113. package/dist/services/validation.cjs.map +1 -1
  114. package/dist/services/validation.d.ts.map +1 -1
  115. package/dist/services/validation.js +31 -8
  116. package/dist/services/validation.js.map +1 -1
  117. package/dist/types/index.d.ts +14 -2
  118. package/dist/types/index.d.ts.map +1 -1
  119. package/dist/utils/degraded-projections.cjs +19 -3
  120. package/dist/utils/degraded-projections.cjs.map +1 -1
  121. package/dist/utils/degraded-projections.d.ts +22 -2
  122. package/dist/utils/degraded-projections.d.ts.map +1 -1
  123. package/dist/utils/degraded-projections.js +19 -3
  124. package/dist/utils/degraded-projections.js.map +1 -1
  125. package/dist/utils/math-markdown.cjs +1 -1
  126. package/dist/utils/math-markdown.cjs.map +1 -1
  127. package/dist/utils/math-markdown.js +1 -1
  128. package/dist/utils/math-markdown.js.map +1 -1
  129. package/dist/utils/missing-value.cjs +60 -0
  130. package/dist/utils/missing-value.cjs.map +1 -0
  131. package/dist/utils/missing-value.d.ts +79 -0
  132. package/dist/utils/missing-value.d.ts.map +1 -0
  133. package/dist/utils/missing-value.js +60 -0
  134. package/dist/utils/missing-value.js.map +1 -0
  135. package/dist/utils/theme.cjs +91 -0
  136. package/dist/utils/theme.cjs.map +1 -0
  137. package/dist/utils/theme.d.ts +11 -0
  138. package/dist/utils/theme.d.ts.map +1 -0
  139. package/dist/utils/theme.js +91 -0
  140. package/dist/utils/theme.js.map +1 -0
  141. package/docs/SECURITY-DEPENDENCIES.md +81 -0
  142. package/docs/THEMING.md +205 -0
  143. package/package.json +7 -5
  144. package/src/components/ActionGroupRenderer.tsx +6 -6
  145. package/src/components/AutocompleteDropdown.tsx +17 -17
  146. package/src/components/AutocompleteFormField.tsx +10 -9
  147. package/src/components/ChartJSRenderer.tsx +609 -212
  148. package/src/components/ChatPrompt.tsx +16 -16
  149. package/src/components/ComponentToolbar.tsx +5 -5
  150. package/src/components/DegradedFallback.tsx +33 -11
  151. package/src/components/ExpandableWrapper.tsx +15 -11
  152. package/src/components/FormFieldRenderer.tsx +52 -51
  153. package/src/components/FormRenderer.tsx +12 -12
  154. package/src/components/GenerativeUIErrorBoundary.tsx +6 -6
  155. package/src/components/GhostText.tsx +7 -7
  156. package/src/components/GraphRenderer.tsx +83 -67
  157. package/src/components/MissingValue.tsx +61 -0
  158. package/src/components/ModalRenderer.tsx +11 -5
  159. package/src/components/PortalDropdownMenu.tsx +6 -2
  160. package/src/components/ScratchpadPanel.tsx +144 -141
  161. package/src/components/UIResourceRenderer.tsx +143 -110
  162. package/src/components/chart-data-table.ts +50 -6
  163. package/src/components/chart-missing-plugin.ts +336 -0
  164. package/src/context/MCPUIStringsContext.tsx +76 -0
  165. package/src/services/component-registry.ts +10 -2
  166. package/src/services/validation.ts +44 -9
  167. package/src/styles/autocomplete.css +53 -53
  168. package/src/types/index.ts +13 -1
  169. package/src/utils/degraded-projections.ts +44 -4
  170. package/src/utils/math-markdown.ts +1 -1
  171. package/src/utils/missing-value.ts +127 -0
  172. package/src/utils/theme.ts +101 -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) => d.label ?? formatMCPUIString(l.series, { n: i + 1 }))
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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"}
@@ -146,7 +146,7 @@ function isCurrencyLike(raw, tex, following) {
146
146
  const closerLooksLikeLaterOpener = /^(?![\s,.;:!?)}\]])(?:\\[^\n]|[^\\\n$])+\$(?!\$)/.test(following);
147
147
  return closerLooksLikeLaterOpener;
148
148
  }
149
- const CELL_LINK_ATTRIBUTES = 'target="_blank" rel="noopener noreferrer" class="text-blue-600 dark:text-blue-400 hover:underline"';
149
+ const CELL_LINK_ATTRIBUTES = 'target="_blank" rel="noopener noreferrer" class="text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] hover:underline"';
150
150
  function cellLinkExtension() {
151
151
  return {
152
152
  renderer: {
@@ -1 +1 @@
1
- {"version":3,"file":"math-markdown.cjs","sources":["../../src/utils/math-markdown.ts"],"sourcesContent":["import type { Config } from 'dompurify'\nimport { Marked, Renderer, type MarkedExtension, type Tokens } from 'marked'\nimport type { MathRenderer } from '../context/MCPUIMathContext'\nimport { escapeHtml } from './escape-html'\nimport { canSanitizeHtml, sanitizeHtml } from './sanitize-html'\n\nexport type MathMarkdownProfile = 'prose' | 'cellMarkdown'\nexport type MarkdownTextTransform = (escapedText: string) => string\n\nexport const MATH_MARKDOWN_LIMITS = {\n sourceLength: 100_000,\n expressionLength: 4_096,\n expressionCount: 128,\n} as const\n\nconst MATH_TAGS = [\n 'math', 'semantics', 'mrow', 'mi', 'mo', 'mn', 'mtext', 'mspace', 'ms',\n 'msup', 'msub', 'msubsup', 'mfrac', 'msqrt', 'mroot', 'mtable', 'mtr',\n 'mtd', 'mover', 'munder', 'munderover', 'mpadded', 'mphantom', 'mstyle', 'menclose',\n] as const\n\nconst MATH_ATTRS = [\n 'xmlns', 'display', 'class', 'mathvariant', 'stretchy', 'fence', 'separator',\n 'lspace', 'rspace', 'accent', 'accentunder', 'columnalign', 'rowalign',\n 'columnspacing', 'rowspacing', 'columnlines', 'rowlines', 'frame', 'notation',\n 'width', 'height', 'depth', 'voffset', 'scriptlevel', 'displaystyle',\n] as const\n\nconst STRICT_MATHML_CONFIG = {\n ALLOWED_TAGS: [...MATH_TAGS, 'span'],\n ALLOWED_ATTR: [...MATH_ATTRS],\n FORBID_TAGS: ['annotation', 'annotation-xml', 'style', 'a'],\n // DOMPurify normally keeps the text of forbidden tags. TeX annotations can\n // be large and duplicate the accessible MathML, so remove their subtree.\n ADD_FORBID_CONTENTS: ['annotation', 'annotation-xml'],\n FORBID_ATTR: ['style'],\n} as Config\n\nconst CELL_MARKDOWN_TAGS = [\n 'a', 'strong', 'em', 'b', 'i', 'code', 'span', 'br', 'button', 'svg', 'path',\n 'p', 'ul', 'ol', 'li', 'pre', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n ...MATH_TAGS,\n]\n\nconst FINAL_PROFILES: Record<MathMarkdownProfile, Config> = {\n prose: {\n ADD_TAGS: [...MATH_TAGS],\n ADD_ATTR: [...MATH_ATTRS, 'target', 'rel'],\n FORBID_TAGS: ['annotation', 'annotation-xml', 'style', 'form', 'textarea', 'select', 'button', 'iframe', 'object', 'embed'],\n ADD_FORBID_CONTENTS: ['annotation', 'annotation-xml'],\n FORBID_ATTR: ['style'],\n } as Config,\n cellMarkdown: {\n ALLOWED_TAGS: CELL_MARKDOWN_TAGS,\n ALLOWED_ATTR: [\n 'href', 'target', 'rel', 'class', 'data-citation-page', 'data-citation-source',\n 'data-citation-doc', 'data-citation-verified', 'title', 'fill', 'stroke',\n 'viewBox', 'stroke-linecap', 'stroke-linejoin', 'stroke-width', 'd',\n ...MATH_ATTRS,\n ],\n FORBID_TAGS: ['annotation', 'annotation-xml', 'style'],\n ADD_FORBID_CONTENTS: ['annotation', 'annotation-xml'],\n FORBID_ATTR: ['style'],\n } as Config,\n}\n\ninterface MathToken extends Tokens.Generic {\n type: 'mcpDisplayMath' | 'mcpInlineMath'\n raw: string\n tex: string\n displayMode: boolean\n render: boolean\n}\n\nfunction isCurrencyLike(raw: string, tex: string, following: string): boolean {\n if (/^\\$\\s|\\s\\$$/.test(raw)) return true\n if (/^\\d+(?:[.,]\\d+)?-\\s*$/.test(tex)) return true\n if (/^\\d/.test(tex) && /^\\d/.test(following)) return true\n\n // A currency opener can otherwise pair with the opening delimiter of a\n // later expression (\"$5 then ($x$)\"). Reinterpret that first candidate\n // only when it starts with a bounded numeric amount and its apparent closer\n // is immediately followed by a complete later inline body. This deliberately\n // leaves inherently ambiguous standalone forms ($5$, $2 + 3$, $2\\pi$) as\n // math and does not depend on the prose between the two delimiters.\n const amount = /^\\d+(?:[.,]\\d{1,2})?(?=$|[\\s,;:!?()[\\]+*/-])/.exec(tex)?.[0]\n if (!amount) return false\n\n // A standalone numeric token is valid math even when followed by prose or\n // another expression (\"$5$, then $x$\"). Reassignment requires content\n // beyond the amount itself.\n const trailing = tex.slice(amount.length)\n if (!trailing) return false\n\n // During streaming, the later formula may not have closed yet. An unmatched\n // opening parenthesis before its dollar is sufficient structural evidence\n // that the numeric dollar was currency; otherwise a permissive renderer can\n // briefly flash \"5 then (\" as math.\n const hasUnmatchedOpeningParen = (trailing.match(/\\(/g)?.length ?? 0) > (trailing.match(/\\)/g)?.length ?? 0)\n const containsTexCommand = /\\\\[A-Za-z]+/.test(trailing)\n if (hasUnmatchedOpeningParen && !containsTexCommand) return true\n\n const closerLooksLikeLaterOpener = /^(?![\\s,.;:!?)}\\]])(?:\\\\[^\\n]|[^\\\\\\n$])+\\$(?!\\$)/.test(following)\n return closerLooksLikeLaterOpener\n}\n\nconst CELL_LINK_ATTRIBUTES = 'target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-blue-600 dark:text-blue-400 hover:underline\"'\n\nfunction cellLinkExtension(): MarkedExtension {\n return {\n renderer: {\n link(this: Renderer, token: Tokens.Link): string {\n // Marked owns escaping of the destination, title and nested label\n // tokens. Only inject fixed attributes into its already-safe anchor.\n const anchor = Renderer.prototype.link.call(this, token) as string\n return anchor.replace('<a ', `<a ${CELL_LINK_ATTRIBUTES} `)\n },\n },\n }\n}\n\nfunction mathExtension(renderer: MathRenderer): MarkedExtension {\n let count = 0\n const token = (raw: string, tex: string, displayMode: boolean): MathToken => ({\n type: displayMode ? 'mcpDisplayMath' : 'mcpInlineMath',\n raw,\n tex,\n displayMode,\n render: ++count <= MATH_MARKDOWN_LIMITS.expressionCount && tex.length <= MATH_MARKDOWN_LIMITS.expressionLength,\n })\n\n return {\n extensions: [\n {\n name: 'mcpDisplayMath',\n level: 'block',\n start(src) {\n const index = src.indexOf('$$')\n return index < 0 ? undefined : index\n },\n tokenizer(src) {\n const match = /^\\$\\$\\s*\\n?([\\s\\S]+?)\\n?\\s*\\$\\$(?:\\n|$)/.exec(src)\n if (!match) return undefined\n return token(match[0], match[1], true)\n },\n renderer(tokenValue) {\n return renderToken(tokenValue as MathToken, renderer)\n },\n },\n {\n name: 'mcpInlineMath',\n level: 'inline',\n start(src) {\n const index = src.indexOf('$')\n return index < 0 ? undefined : index\n },\n tokenizer(src) {\n const match = /^\\$(?!\\$)((?:\\\\[^\\n]|[^\\\\\\n$])+?)\\$(?!\\$)/.exec(src)\n if (!match || isCurrencyLike(match[0], match[1], src.slice(match[0].length))) return undefined\n return token(match[0], match[1], false)\n },\n renderer(tokenValue) {\n return renderToken(tokenValue as MathToken, renderer)\n },\n },\n ],\n }\n}\n\nfunction renderToken(token: MathToken, renderer: MathRenderer): string {\n if (!token.render) return escapeHtml(token.raw)\n try {\n const rendered = renderer(token.tex, { displayMode: token.displayMode })\n if (!rendered) return escapeHtml(token.raw)\n return sanitizeHtml(rendered, STRICT_MATHML_CONFIG)\n } catch {\n return escapeHtml(token.raw)\n }\n}\n\n/**\n * Parse Markdown with isolated, bounded math support and sanitize the complete\n * result for its final sink. The optional transform sees escaped ordinary text\n * tokens only. Math, code, raw HTML and link destinations never pass through\n * it, so citation syntax cannot corrupt TeX optional arguments or attributes.\n */\nexport function parseMathMarkdown(\n source: string,\n renderer: MathRenderer,\n profile: MathMarkdownProfile,\n transformText?: MarkdownTextTransform,\n): string {\n const input = source === null || source === undefined ? '' : String(source)\n if (!canSanitizeHtml()) return escapeHtml(input)\n if (input.length > MATH_MARKDOWN_LIMITS.sourceLength) return escapeHtml(input)\n\n const textRenderer = transformText\n ? {\n renderer: {\n text(this: Renderer, token: Tokens.Text | Tokens.Escape): string {\n // Block text tokens (notably list-item text) can own already\n // tokenized inline children. Recurse first so math and code keep\n // their own renderers; leaf text callbacks run as parsing descends.\n if ('tokens' in token && token.tokens) {\n return this.parser.parseInline(token.tokens)\n }\n // Delegate entity handling and nested token rendering to Marked's\n // own renderer. `escapeHtml` is deliberately not equivalent: it\n // turns source `&amp;` into visible `&amp;` by double-escaping it.\n const safeText = Renderer.prototype.text.call(this, token)\n return transformText(safeText as string)\n },\n },\n }\n : undefined\n const extensions = [mathExtension(renderer)]\n if (profile === 'cellMarkdown') extensions.push(cellLinkExtension())\n if (textRenderer) extensions.push(textRenderer)\n const parser = new Marked({ gfm: true }, ...extensions)\n const parsed = parser.parse(input, { async: false }) as string\n return sanitizeHtml(parsed, FINAL_PROFILES[profile])\n}\n"],"names":["Renderer","escapeHtml","sanitizeHtml","canSanitizeHtml","Marked"],"mappings":"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"}
1
+ {"version":3,"file":"math-markdown.cjs","sources":["../../src/utils/math-markdown.ts"],"sourcesContent":["import type { Config } from 'dompurify'\nimport { Marked, Renderer, type MarkedExtension, type Tokens } from 'marked'\nimport type { MathRenderer } from '../context/MCPUIMathContext'\nimport { escapeHtml } from './escape-html'\nimport { canSanitizeHtml, sanitizeHtml } from './sanitize-html'\n\nexport type MathMarkdownProfile = 'prose' | 'cellMarkdown'\nexport type MarkdownTextTransform = (escapedText: string) => string\n\nexport const MATH_MARKDOWN_LIMITS = {\n sourceLength: 100_000,\n expressionLength: 4_096,\n expressionCount: 128,\n} as const\n\nconst MATH_TAGS = [\n 'math', 'semantics', 'mrow', 'mi', 'mo', 'mn', 'mtext', 'mspace', 'ms',\n 'msup', 'msub', 'msubsup', 'mfrac', 'msqrt', 'mroot', 'mtable', 'mtr',\n 'mtd', 'mover', 'munder', 'munderover', 'mpadded', 'mphantom', 'mstyle', 'menclose',\n] as const\n\nconst MATH_ATTRS = [\n 'xmlns', 'display', 'class', 'mathvariant', 'stretchy', 'fence', 'separator',\n 'lspace', 'rspace', 'accent', 'accentunder', 'columnalign', 'rowalign',\n 'columnspacing', 'rowspacing', 'columnlines', 'rowlines', 'frame', 'notation',\n 'width', 'height', 'depth', 'voffset', 'scriptlevel', 'displaystyle',\n] as const\n\nconst STRICT_MATHML_CONFIG = {\n ALLOWED_TAGS: [...MATH_TAGS, 'span'],\n ALLOWED_ATTR: [...MATH_ATTRS],\n FORBID_TAGS: ['annotation', 'annotation-xml', 'style', 'a'],\n // DOMPurify normally keeps the text of forbidden tags. TeX annotations can\n // be large and duplicate the accessible MathML, so remove their subtree.\n ADD_FORBID_CONTENTS: ['annotation', 'annotation-xml'],\n FORBID_ATTR: ['style'],\n} as Config\n\nconst CELL_MARKDOWN_TAGS = [\n 'a', 'strong', 'em', 'b', 'i', 'code', 'span', 'br', 'button', 'svg', 'path',\n 'p', 'ul', 'ol', 'li', 'pre', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n ...MATH_TAGS,\n]\n\nconst FINAL_PROFILES: Record<MathMarkdownProfile, Config> = {\n prose: {\n ADD_TAGS: [...MATH_TAGS],\n ADD_ATTR: [...MATH_ATTRS, 'target', 'rel'],\n FORBID_TAGS: ['annotation', 'annotation-xml', 'style', 'form', 'textarea', 'select', 'button', 'iframe', 'object', 'embed'],\n ADD_FORBID_CONTENTS: ['annotation', 'annotation-xml'],\n FORBID_ATTR: ['style'],\n } as Config,\n cellMarkdown: {\n ALLOWED_TAGS: CELL_MARKDOWN_TAGS,\n ALLOWED_ATTR: [\n 'href', 'target', 'rel', 'class', 'data-citation-page', 'data-citation-source',\n 'data-citation-doc', 'data-citation-verified', 'title', 'fill', 'stroke',\n 'viewBox', 'stroke-linecap', 'stroke-linejoin', 'stroke-width', 'd',\n ...MATH_ATTRS,\n ],\n FORBID_TAGS: ['annotation', 'annotation-xml', 'style'],\n ADD_FORBID_CONTENTS: ['annotation', 'annotation-xml'],\n FORBID_ATTR: ['style'],\n } as Config,\n}\n\ninterface MathToken extends Tokens.Generic {\n type: 'mcpDisplayMath' | 'mcpInlineMath'\n raw: string\n tex: string\n displayMode: boolean\n render: boolean\n}\n\nfunction isCurrencyLike(raw: string, tex: string, following: string): boolean {\n if (/^\\$\\s|\\s\\$$/.test(raw)) return true\n if (/^\\d+(?:[.,]\\d+)?-\\s*$/.test(tex)) return true\n if (/^\\d/.test(tex) && /^\\d/.test(following)) return true\n\n // A currency opener can otherwise pair with the opening delimiter of a\n // later expression (\"$5 then ($x$)\"). Reinterpret that first candidate\n // only when it starts with a bounded numeric amount and its apparent closer\n // is immediately followed by a complete later inline body. This deliberately\n // leaves inherently ambiguous standalone forms ($5$, $2 + 3$, $2\\pi$) as\n // math and does not depend on the prose between the two delimiters.\n const amount = /^\\d+(?:[.,]\\d{1,2})?(?=$|[\\s,;:!?()[\\]+*/-])/.exec(tex)?.[0]\n if (!amount) return false\n\n // A standalone numeric token is valid math even when followed by prose or\n // another expression (\"$5$, then $x$\"). Reassignment requires content\n // beyond the amount itself.\n const trailing = tex.slice(amount.length)\n if (!trailing) return false\n\n // During streaming, the later formula may not have closed yet. An unmatched\n // opening parenthesis before its dollar is sufficient structural evidence\n // that the numeric dollar was currency; otherwise a permissive renderer can\n // briefly flash \"5 then (\" as math.\n const hasUnmatchedOpeningParen = (trailing.match(/\\(/g)?.length ?? 0) > (trailing.match(/\\)/g)?.length ?? 0)\n const containsTexCommand = /\\\\[A-Za-z]+/.test(trailing)\n if (hasUnmatchedOpeningParen && !containsTexCommand) return true\n\n const closerLooksLikeLaterOpener = /^(?![\\s,.;:!?)}\\]])(?:\\\\[^\\n]|[^\\\\\\n$])+\\$(?!\\$)/.test(following)\n return closerLooksLikeLaterOpener\n}\n\nconst CELL_LINK_ATTRIBUTES = 'target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] hover:underline\"'\n\nfunction cellLinkExtension(): MarkedExtension {\n return {\n renderer: {\n link(this: Renderer, token: Tokens.Link): string {\n // Marked owns escaping of the destination, title and nested label\n // tokens. Only inject fixed attributes into its already-safe anchor.\n const anchor = Renderer.prototype.link.call(this, token) as string\n return anchor.replace('<a ', `<a ${CELL_LINK_ATTRIBUTES} `)\n },\n },\n }\n}\n\nfunction mathExtension(renderer: MathRenderer): MarkedExtension {\n let count = 0\n const token = (raw: string, tex: string, displayMode: boolean): MathToken => ({\n type: displayMode ? 'mcpDisplayMath' : 'mcpInlineMath',\n raw,\n tex,\n displayMode,\n render: ++count <= MATH_MARKDOWN_LIMITS.expressionCount && tex.length <= MATH_MARKDOWN_LIMITS.expressionLength,\n })\n\n return {\n extensions: [\n {\n name: 'mcpDisplayMath',\n level: 'block',\n start(src) {\n const index = src.indexOf('$$')\n return index < 0 ? undefined : index\n },\n tokenizer(src) {\n const match = /^\\$\\$\\s*\\n?([\\s\\S]+?)\\n?\\s*\\$\\$(?:\\n|$)/.exec(src)\n if (!match) return undefined\n return token(match[0], match[1], true)\n },\n renderer(tokenValue) {\n return renderToken(tokenValue as MathToken, renderer)\n },\n },\n {\n name: 'mcpInlineMath',\n level: 'inline',\n start(src) {\n const index = src.indexOf('$')\n return index < 0 ? undefined : index\n },\n tokenizer(src) {\n const match = /^\\$(?!\\$)((?:\\\\[^\\n]|[^\\\\\\n$])+?)\\$(?!\\$)/.exec(src)\n if (!match || isCurrencyLike(match[0], match[1], src.slice(match[0].length))) return undefined\n return token(match[0], match[1], false)\n },\n renderer(tokenValue) {\n return renderToken(tokenValue as MathToken, renderer)\n },\n },\n ],\n }\n}\n\nfunction renderToken(token: MathToken, renderer: MathRenderer): string {\n if (!token.render) return escapeHtml(token.raw)\n try {\n const rendered = renderer(token.tex, { displayMode: token.displayMode })\n if (!rendered) return escapeHtml(token.raw)\n return sanitizeHtml(rendered, STRICT_MATHML_CONFIG)\n } catch {\n return escapeHtml(token.raw)\n }\n}\n\n/**\n * Parse Markdown with isolated, bounded math support and sanitize the complete\n * result for its final sink. The optional transform sees escaped ordinary text\n * tokens only. Math, code, raw HTML and link destinations never pass through\n * it, so citation syntax cannot corrupt TeX optional arguments or attributes.\n */\nexport function parseMathMarkdown(\n source: string,\n renderer: MathRenderer,\n profile: MathMarkdownProfile,\n transformText?: MarkdownTextTransform,\n): string {\n const input = source === null || source === undefined ? '' : String(source)\n if (!canSanitizeHtml()) return escapeHtml(input)\n if (input.length > MATH_MARKDOWN_LIMITS.sourceLength) return escapeHtml(input)\n\n const textRenderer = transformText\n ? {\n renderer: {\n text(this: Renderer, token: Tokens.Text | Tokens.Escape): string {\n // Block text tokens (notably list-item text) can own already\n // tokenized inline children. Recurse first so math and code keep\n // their own renderers; leaf text callbacks run as parsing descends.\n if ('tokens' in token && token.tokens) {\n return this.parser.parseInline(token.tokens)\n }\n // Delegate entity handling and nested token rendering to Marked's\n // own renderer. `escapeHtml` is deliberately not equivalent: it\n // turns source `&amp;` into visible `&amp;` by double-escaping it.\n const safeText = Renderer.prototype.text.call(this, token)\n return transformText(safeText as string)\n },\n },\n }\n : undefined\n const extensions = [mathExtension(renderer)]\n if (profile === 'cellMarkdown') extensions.push(cellLinkExtension())\n if (textRenderer) extensions.push(textRenderer)\n const parser = new Marked({ gfm: true }, ...extensions)\n const parsed = parser.parse(input, { async: false }) as string\n return sanitizeHtml(parsed, FINAL_PROFILES[profile])\n}\n"],"names":["Renderer","escapeHtml","sanitizeHtml","canSanitizeHtml","Marked"],"mappings":";;;;;AASO,MAAM,uBAAuB;AAAA,EAClC,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,iBAAiB;AACnB;AAEA,MAAM,YAAY;AAAA,EAChB;AAAA,EAAQ;AAAA,EAAa;AAAA,EAAQ;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAS;AAAA,EAAU;AAAA,EAClE;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAW;AAAA,EAAS;AAAA,EAAS;AAAA,EAAS;AAAA,EAAU;AAAA,EAChE;AAAA,EAAO;AAAA,EAAS;AAAA,EAAU;AAAA,EAAc;AAAA,EAAW;AAAA,EAAY;AAAA,EAAU;AAC3E;AAEA,MAAM,aAAa;AAAA,EACjB;AAAA,EAAS;AAAA,EAAW;AAAA,EAAS;AAAA,EAAe;AAAA,EAAY;AAAA,EAAS;AAAA,EACjE;AAAA,EAAU;AAAA,EAAU;AAAA,EAAU;AAAA,EAAe;AAAA,EAAe;AAAA,EAC5D;AAAA,EAAiB;AAAA,EAAc;AAAA,EAAe;AAAA,EAAY;AAAA,EAAS;AAAA,EACnE;AAAA,EAAS;AAAA,EAAU;AAAA,EAAS;AAAA,EAAW;AAAA,EAAe;AACxD;AAEA,MAAM,uBAAuB;AAAA,EAC3B,cAAc,CAAC,GAAG,WAAW,MAAM;AAAA,EACnC,cAAc,CAAC,GAAG,UAAU;AAAA,EAC5B,aAAa,CAAC,cAAc,kBAAkB,SAAS,GAAG;AAAA;AAAA;AAAA,EAG1D,qBAAqB,CAAC,cAAc,gBAAgB;AAAA,EACpD,aAAa,CAAC,OAAO;AACvB;AAEA,MAAM,qBAAqB;AAAA,EACzB;AAAA,EAAK;AAAA,EAAU;AAAA,EAAM;AAAA,EAAK;AAAA,EAAK;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAM;AAAA,EAAU;AAAA,EAAO;AAAA,EACtE;AAAA,EAAK;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAO;AAAA,EAAc;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1E,GAAG;AACL;AAEA,MAAM,iBAAsD;AAAA,EAC1D,OAAO;AAAA,IACL,UAAU,CAAC,GAAG,SAAS;AAAA,IACvB,UAAU,CAAC,GAAG,YAAY,UAAU,KAAK;AAAA,IACzC,aAAa,CAAC,cAAc,kBAAkB,SAAS,QAAQ,YAAY,UAAU,UAAU,UAAU,UAAU,OAAO;AAAA,IAC1H,qBAAqB,CAAC,cAAc,gBAAgB;AAAA,IACpD,aAAa,CAAC,OAAO;AAAA,EAAA;AAAA,EAEvB,cAAc;AAAA,IACZ,cAAc;AAAA,IACd,cAAc;AAAA,MACZ;AAAA,MAAQ;AAAA,MAAU;AAAA,MAAO;AAAA,MAAS;AAAA,MAAsB;AAAA,MACxD;AAAA,MAAqB;AAAA,MAA0B;AAAA,MAAS;AAAA,MAAQ;AAAA,MAChE;AAAA,MAAW;AAAA,MAAkB;AAAA,MAAmB;AAAA,MAAgB;AAAA,MAChE,GAAG;AAAA,IAAA;AAAA,IAEL,aAAa,CAAC,cAAc,kBAAkB,OAAO;AAAA,IACrD,qBAAqB,CAAC,cAAc,gBAAgB;AAAA,IACpD,aAAa,CAAC,OAAO;AAAA,EAAA;AAEzB;AAUA,SAAS,eAAe,KAAa,KAAa,WAA4B;;AAC5E,MAAI,cAAc,KAAK,GAAG,EAAG,QAAO;AACpC,MAAI,wBAAwB,KAAK,GAAG,EAAG,QAAO;AAC9C,MAAI,MAAM,KAAK,GAAG,KAAK,MAAM,KAAK,SAAS,EAAG,QAAO;AAQrD,QAAM,UAAS,oDAA+C,KAAK,GAAG,MAAvD,mBAA2D;AAC1E,MAAI,CAAC,OAAQ,QAAO;AAKpB,QAAM,WAAW,IAAI,MAAM,OAAO,MAAM;AACxC,MAAI,CAAC,SAAU,QAAO;AAMtB,QAAM,8BAA4B,cAAS,MAAM,KAAK,MAApB,mBAAuB,WAAU,QAAM,cAAS,MAAM,KAAK,MAApB,mBAAuB,WAAU;AAC1G,QAAM,qBAAqB,cAAc,KAAK,QAAQ;AACtD,MAAI,4BAA4B,CAAC,mBAAoB,QAAO;AAE5D,QAAM,6BAA6B,mDAAmD,KAAK,SAAS;AACpG,SAAO;AACT;AAEA,MAAM,uBAAuB;AAE7B,SAAS,oBAAqC;AAC5C,SAAO;AAAA,IACL,UAAU;AAAA,MACR,KAAqB,OAA4B;AAG/C,cAAM,SAASA,OAAAA,SAAS,UAAU,KAAK,KAAK,MAAM,KAAK;AACvD,eAAO,OAAO,QAAQ,OAAO,MAAM,oBAAoB,GAAG;AAAA,MAC5D;AAAA,IAAA;AAAA,EACF;AAEJ;AAEA,SAAS,cAAc,UAAyC;AAC9D,MAAI,QAAQ;AACZ,QAAM,QAAQ,CAAC,KAAa,KAAa,iBAAqC;AAAA,IAC5E,MAAM,cAAc,mBAAmB;AAAA,IACvC;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,EAAE,SAAS,qBAAqB,mBAAmB,IAAI,UAAU,qBAAqB;AAAA,EAAA;AAGhG,SAAO;AAAA,IACL,YAAY;AAAA,MACV;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,QACP,MAAM,KAAK;AACT,gBAAM,QAAQ,IAAI,QAAQ,IAAI;AAC9B,iBAAO,QAAQ,IAAI,SAAY;AAAA,QACjC;AAAA,QACA,UAAU,KAAK;AACb,gBAAM,QAAQ,0CAA0C,KAAK,GAAG;AAChE,cAAI,CAAC,MAAO,QAAO;AACnB,iBAAO,MAAM,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI;AAAA,QACvC;AAAA,QACA,SAAS,YAAY;AACnB,iBAAO,YAAY,YAAyB,QAAQ;AAAA,QACtD;AAAA,MAAA;AAAA,MAEF;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,QACP,MAAM,KAAK;AACT,gBAAM,QAAQ,IAAI,QAAQ,GAAG;AAC7B,iBAAO,QAAQ,IAAI,SAAY;AAAA,QACjC;AAAA,QACA,UAAU,KAAK;AACb,gBAAM,QAAQ,4CAA4C,KAAK,GAAG;AAClE,cAAI,CAAC,SAAS,eAAe,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,MAAM,MAAM,CAAC,EAAE,MAAM,CAAC,EAAG,QAAO;AACrF,iBAAO,MAAM,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,KAAK;AAAA,QACxC;AAAA,QACA,SAAS,YAAY;AACnB,iBAAO,YAAY,YAAyB,QAAQ;AAAA,QACtD;AAAA,MAAA;AAAA,IACF;AAAA,EACF;AAEJ;AAEA,SAAS,YAAY,OAAkB,UAAgC;AACrE,MAAI,CAAC,MAAM,OAAQ,QAAOC,WAAAA,WAAW,MAAM,GAAG;AAC9C,MAAI;AACF,UAAM,WAAW,SAAS,MAAM,KAAK,EAAE,aAAa,MAAM,aAAa;AACvE,QAAI,CAAC,SAAU,QAAOA,sBAAW,MAAM,GAAG;AAC1C,WAAOC,aAAAA,aAAa,UAAU,oBAAoB;AAAA,EACpD,QAAQ;AACN,WAAOD,WAAAA,WAAW,MAAM,GAAG;AAAA,EAC7B;AACF;AAQO,SAAS,kBACd,QACA,UACA,SACA,eACQ;AACR,QAAM,QAAQ,WAAW,QAAQ,WAAW,SAAY,KAAK,OAAO,MAAM;AAC1E,MAAI,CAACE,aAAAA,gBAAA,EAAmB,QAAOF,WAAAA,WAAW,KAAK;AAC/C,MAAI,MAAM,SAAS,qBAAqB,aAAc,QAAOA,WAAAA,WAAW,KAAK;AAE7E,QAAM,eAAe,gBACjB;AAAA,IACE,UAAU;AAAA,MACR,KAAqB,OAA4C;AAI/D,YAAI,YAAY,SAAS,MAAM,QAAQ;AACrC,iBAAO,KAAK,OAAO,YAAY,MAAM,MAAM;AAAA,QAC7C;AAIA,cAAM,WAAWD,OAAAA,SAAS,UAAU,KAAK,KAAK,MAAM,KAAK;AACzD,eAAO,cAAc,QAAkB;AAAA,MACzC;AAAA,IAAA;AAAA,EACF,IAEF;AACJ,QAAM,aAAa,CAAC,cAAc,QAAQ,CAAC;AAC3C,MAAI,YAAY,eAAgB,YAAW,KAAK,mBAAmB;AACnE,MAAI,aAAc,YAAW,KAAK,YAAY;AAC9C,QAAM,SAAS,IAAII,cAAO,EAAE,KAAK,KAAA,GAAQ,GAAG,UAAU;AACtD,QAAM,SAAS,OAAO,MAAM,OAAO,EAAE,OAAO,OAAO;AACnD,SAAOF,0BAAa,QAAQ,eAAe,OAAO,CAAC;AACrD;;;"}
@@ -144,7 +144,7 @@ function isCurrencyLike(raw, tex, following) {
144
144
  const closerLooksLikeLaterOpener = /^(?![\s,.;:!?)}\]])(?:\\[^\n]|[^\\\n$])+\$(?!\$)/.test(following);
145
145
  return closerLooksLikeLaterOpener;
146
146
  }
147
- const CELL_LINK_ATTRIBUTES = 'target="_blank" rel="noopener noreferrer" class="text-blue-600 dark:text-blue-400 hover:underline"';
147
+ const CELL_LINK_ATTRIBUTES = 'target="_blank" rel="noopener noreferrer" class="text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] hover:underline"';
148
148
  function cellLinkExtension() {
149
149
  return {
150
150
  renderer: {
@@ -1 +1 @@
1
- {"version":3,"file":"math-markdown.js","sources":["../../src/utils/math-markdown.ts"],"sourcesContent":["import type { Config } from 'dompurify'\nimport { Marked, Renderer, type MarkedExtension, type Tokens } from 'marked'\nimport type { MathRenderer } from '../context/MCPUIMathContext'\nimport { escapeHtml } from './escape-html'\nimport { canSanitizeHtml, sanitizeHtml } from './sanitize-html'\n\nexport type MathMarkdownProfile = 'prose' | 'cellMarkdown'\nexport type MarkdownTextTransform = (escapedText: string) => string\n\nexport const MATH_MARKDOWN_LIMITS = {\n sourceLength: 100_000,\n expressionLength: 4_096,\n expressionCount: 128,\n} as const\n\nconst MATH_TAGS = [\n 'math', 'semantics', 'mrow', 'mi', 'mo', 'mn', 'mtext', 'mspace', 'ms',\n 'msup', 'msub', 'msubsup', 'mfrac', 'msqrt', 'mroot', 'mtable', 'mtr',\n 'mtd', 'mover', 'munder', 'munderover', 'mpadded', 'mphantom', 'mstyle', 'menclose',\n] as const\n\nconst MATH_ATTRS = [\n 'xmlns', 'display', 'class', 'mathvariant', 'stretchy', 'fence', 'separator',\n 'lspace', 'rspace', 'accent', 'accentunder', 'columnalign', 'rowalign',\n 'columnspacing', 'rowspacing', 'columnlines', 'rowlines', 'frame', 'notation',\n 'width', 'height', 'depth', 'voffset', 'scriptlevel', 'displaystyle',\n] as const\n\nconst STRICT_MATHML_CONFIG = {\n ALLOWED_TAGS: [...MATH_TAGS, 'span'],\n ALLOWED_ATTR: [...MATH_ATTRS],\n FORBID_TAGS: ['annotation', 'annotation-xml', 'style', 'a'],\n // DOMPurify normally keeps the text of forbidden tags. TeX annotations can\n // be large and duplicate the accessible MathML, so remove their subtree.\n ADD_FORBID_CONTENTS: ['annotation', 'annotation-xml'],\n FORBID_ATTR: ['style'],\n} as Config\n\nconst CELL_MARKDOWN_TAGS = [\n 'a', 'strong', 'em', 'b', 'i', 'code', 'span', 'br', 'button', 'svg', 'path',\n 'p', 'ul', 'ol', 'li', 'pre', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n ...MATH_TAGS,\n]\n\nconst FINAL_PROFILES: Record<MathMarkdownProfile, Config> = {\n prose: {\n ADD_TAGS: [...MATH_TAGS],\n ADD_ATTR: [...MATH_ATTRS, 'target', 'rel'],\n FORBID_TAGS: ['annotation', 'annotation-xml', 'style', 'form', 'textarea', 'select', 'button', 'iframe', 'object', 'embed'],\n ADD_FORBID_CONTENTS: ['annotation', 'annotation-xml'],\n FORBID_ATTR: ['style'],\n } as Config,\n cellMarkdown: {\n ALLOWED_TAGS: CELL_MARKDOWN_TAGS,\n ALLOWED_ATTR: [\n 'href', 'target', 'rel', 'class', 'data-citation-page', 'data-citation-source',\n 'data-citation-doc', 'data-citation-verified', 'title', 'fill', 'stroke',\n 'viewBox', 'stroke-linecap', 'stroke-linejoin', 'stroke-width', 'd',\n ...MATH_ATTRS,\n ],\n FORBID_TAGS: ['annotation', 'annotation-xml', 'style'],\n ADD_FORBID_CONTENTS: ['annotation', 'annotation-xml'],\n FORBID_ATTR: ['style'],\n } as Config,\n}\n\ninterface MathToken extends Tokens.Generic {\n type: 'mcpDisplayMath' | 'mcpInlineMath'\n raw: string\n tex: string\n displayMode: boolean\n render: boolean\n}\n\nfunction isCurrencyLike(raw: string, tex: string, following: string): boolean {\n if (/^\\$\\s|\\s\\$$/.test(raw)) return true\n if (/^\\d+(?:[.,]\\d+)?-\\s*$/.test(tex)) return true\n if (/^\\d/.test(tex) && /^\\d/.test(following)) return true\n\n // A currency opener can otherwise pair with the opening delimiter of a\n // later expression (\"$5 then ($x$)\"). Reinterpret that first candidate\n // only when it starts with a bounded numeric amount and its apparent closer\n // is immediately followed by a complete later inline body. This deliberately\n // leaves inherently ambiguous standalone forms ($5$, $2 + 3$, $2\\pi$) as\n // math and does not depend on the prose between the two delimiters.\n const amount = /^\\d+(?:[.,]\\d{1,2})?(?=$|[\\s,;:!?()[\\]+*/-])/.exec(tex)?.[0]\n if (!amount) return false\n\n // A standalone numeric token is valid math even when followed by prose or\n // another expression (\"$5$, then $x$\"). Reassignment requires content\n // beyond the amount itself.\n const trailing = tex.slice(amount.length)\n if (!trailing) return false\n\n // During streaming, the later formula may not have closed yet. An unmatched\n // opening parenthesis before its dollar is sufficient structural evidence\n // that the numeric dollar was currency; otherwise a permissive renderer can\n // briefly flash \"5 then (\" as math.\n const hasUnmatchedOpeningParen = (trailing.match(/\\(/g)?.length ?? 0) > (trailing.match(/\\)/g)?.length ?? 0)\n const containsTexCommand = /\\\\[A-Za-z]+/.test(trailing)\n if (hasUnmatchedOpeningParen && !containsTexCommand) return true\n\n const closerLooksLikeLaterOpener = /^(?![\\s,.;:!?)}\\]])(?:\\\\[^\\n]|[^\\\\\\n$])+\\$(?!\\$)/.test(following)\n return closerLooksLikeLaterOpener\n}\n\nconst CELL_LINK_ATTRIBUTES = 'target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-blue-600 dark:text-blue-400 hover:underline\"'\n\nfunction cellLinkExtension(): MarkedExtension {\n return {\n renderer: {\n link(this: Renderer, token: Tokens.Link): string {\n // Marked owns escaping of the destination, title and nested label\n // tokens. Only inject fixed attributes into its already-safe anchor.\n const anchor = Renderer.prototype.link.call(this, token) as string\n return anchor.replace('<a ', `<a ${CELL_LINK_ATTRIBUTES} `)\n },\n },\n }\n}\n\nfunction mathExtension(renderer: MathRenderer): MarkedExtension {\n let count = 0\n const token = (raw: string, tex: string, displayMode: boolean): MathToken => ({\n type: displayMode ? 'mcpDisplayMath' : 'mcpInlineMath',\n raw,\n tex,\n displayMode,\n render: ++count <= MATH_MARKDOWN_LIMITS.expressionCount && tex.length <= MATH_MARKDOWN_LIMITS.expressionLength,\n })\n\n return {\n extensions: [\n {\n name: 'mcpDisplayMath',\n level: 'block',\n start(src) {\n const index = src.indexOf('$$')\n return index < 0 ? undefined : index\n },\n tokenizer(src) {\n const match = /^\\$\\$\\s*\\n?([\\s\\S]+?)\\n?\\s*\\$\\$(?:\\n|$)/.exec(src)\n if (!match) return undefined\n return token(match[0], match[1], true)\n },\n renderer(tokenValue) {\n return renderToken(tokenValue as MathToken, renderer)\n },\n },\n {\n name: 'mcpInlineMath',\n level: 'inline',\n start(src) {\n const index = src.indexOf('$')\n return index < 0 ? undefined : index\n },\n tokenizer(src) {\n const match = /^\\$(?!\\$)((?:\\\\[^\\n]|[^\\\\\\n$])+?)\\$(?!\\$)/.exec(src)\n if (!match || isCurrencyLike(match[0], match[1], src.slice(match[0].length))) return undefined\n return token(match[0], match[1], false)\n },\n renderer(tokenValue) {\n return renderToken(tokenValue as MathToken, renderer)\n },\n },\n ],\n }\n}\n\nfunction renderToken(token: MathToken, renderer: MathRenderer): string {\n if (!token.render) return escapeHtml(token.raw)\n try {\n const rendered = renderer(token.tex, { displayMode: token.displayMode })\n if (!rendered) return escapeHtml(token.raw)\n return sanitizeHtml(rendered, STRICT_MATHML_CONFIG)\n } catch {\n return escapeHtml(token.raw)\n }\n}\n\n/**\n * Parse Markdown with isolated, bounded math support and sanitize the complete\n * result for its final sink. The optional transform sees escaped ordinary text\n * tokens only. Math, code, raw HTML and link destinations never pass through\n * it, so citation syntax cannot corrupt TeX optional arguments or attributes.\n */\nexport function parseMathMarkdown(\n source: string,\n renderer: MathRenderer,\n profile: MathMarkdownProfile,\n transformText?: MarkdownTextTransform,\n): string {\n const input = source === null || source === undefined ? '' : String(source)\n if (!canSanitizeHtml()) return escapeHtml(input)\n if (input.length > MATH_MARKDOWN_LIMITS.sourceLength) return escapeHtml(input)\n\n const textRenderer = transformText\n ? {\n renderer: {\n text(this: Renderer, token: Tokens.Text | Tokens.Escape): string {\n // Block text tokens (notably list-item text) can own already\n // tokenized inline children. Recurse first so math and code keep\n // their own renderers; leaf text callbacks run as parsing descends.\n if ('tokens' in token && token.tokens) {\n return this.parser.parseInline(token.tokens)\n }\n // Delegate entity handling and nested token rendering to Marked's\n // own renderer. `escapeHtml` is deliberately not equivalent: it\n // turns source `&amp;` into visible `&amp;` by double-escaping it.\n const safeText = Renderer.prototype.text.call(this, token)\n return transformText(safeText as string)\n },\n },\n }\n : undefined\n const extensions = [mathExtension(renderer)]\n if (profile === 'cellMarkdown') extensions.push(cellLinkExtension())\n if (textRenderer) extensions.push(textRenderer)\n const parser = new Marked({ gfm: true }, ...extensions)\n const parsed = parser.parse(input, { async: false }) as string\n return sanitizeHtml(parsed, FINAL_PROFILES[profile])\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"math-markdown.js","sources":["../../src/utils/math-markdown.ts"],"sourcesContent":["import type { Config } from 'dompurify'\nimport { Marked, Renderer, type MarkedExtension, type Tokens } from 'marked'\nimport type { MathRenderer } from '../context/MCPUIMathContext'\nimport { escapeHtml } from './escape-html'\nimport { canSanitizeHtml, sanitizeHtml } from './sanitize-html'\n\nexport type MathMarkdownProfile = 'prose' | 'cellMarkdown'\nexport type MarkdownTextTransform = (escapedText: string) => string\n\nexport const MATH_MARKDOWN_LIMITS = {\n sourceLength: 100_000,\n expressionLength: 4_096,\n expressionCount: 128,\n} as const\n\nconst MATH_TAGS = [\n 'math', 'semantics', 'mrow', 'mi', 'mo', 'mn', 'mtext', 'mspace', 'ms',\n 'msup', 'msub', 'msubsup', 'mfrac', 'msqrt', 'mroot', 'mtable', 'mtr',\n 'mtd', 'mover', 'munder', 'munderover', 'mpadded', 'mphantom', 'mstyle', 'menclose',\n] as const\n\nconst MATH_ATTRS = [\n 'xmlns', 'display', 'class', 'mathvariant', 'stretchy', 'fence', 'separator',\n 'lspace', 'rspace', 'accent', 'accentunder', 'columnalign', 'rowalign',\n 'columnspacing', 'rowspacing', 'columnlines', 'rowlines', 'frame', 'notation',\n 'width', 'height', 'depth', 'voffset', 'scriptlevel', 'displaystyle',\n] as const\n\nconst STRICT_MATHML_CONFIG = {\n ALLOWED_TAGS: [...MATH_TAGS, 'span'],\n ALLOWED_ATTR: [...MATH_ATTRS],\n FORBID_TAGS: ['annotation', 'annotation-xml', 'style', 'a'],\n // DOMPurify normally keeps the text of forbidden tags. TeX annotations can\n // be large and duplicate the accessible MathML, so remove their subtree.\n ADD_FORBID_CONTENTS: ['annotation', 'annotation-xml'],\n FORBID_ATTR: ['style'],\n} as Config\n\nconst CELL_MARKDOWN_TAGS = [\n 'a', 'strong', 'em', 'b', 'i', 'code', 'span', 'br', 'button', 'svg', 'path',\n 'p', 'ul', 'ol', 'li', 'pre', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n ...MATH_TAGS,\n]\n\nconst FINAL_PROFILES: Record<MathMarkdownProfile, Config> = {\n prose: {\n ADD_TAGS: [...MATH_TAGS],\n ADD_ATTR: [...MATH_ATTRS, 'target', 'rel'],\n FORBID_TAGS: ['annotation', 'annotation-xml', 'style', 'form', 'textarea', 'select', 'button', 'iframe', 'object', 'embed'],\n ADD_FORBID_CONTENTS: ['annotation', 'annotation-xml'],\n FORBID_ATTR: ['style'],\n } as Config,\n cellMarkdown: {\n ALLOWED_TAGS: CELL_MARKDOWN_TAGS,\n ALLOWED_ATTR: [\n 'href', 'target', 'rel', 'class', 'data-citation-page', 'data-citation-source',\n 'data-citation-doc', 'data-citation-verified', 'title', 'fill', 'stroke',\n 'viewBox', 'stroke-linecap', 'stroke-linejoin', 'stroke-width', 'd',\n ...MATH_ATTRS,\n ],\n FORBID_TAGS: ['annotation', 'annotation-xml', 'style'],\n ADD_FORBID_CONTENTS: ['annotation', 'annotation-xml'],\n FORBID_ATTR: ['style'],\n } as Config,\n}\n\ninterface MathToken extends Tokens.Generic {\n type: 'mcpDisplayMath' | 'mcpInlineMath'\n raw: string\n tex: string\n displayMode: boolean\n render: boolean\n}\n\nfunction isCurrencyLike(raw: string, tex: string, following: string): boolean {\n if (/^\\$\\s|\\s\\$$/.test(raw)) return true\n if (/^\\d+(?:[.,]\\d+)?-\\s*$/.test(tex)) return true\n if (/^\\d/.test(tex) && /^\\d/.test(following)) return true\n\n // A currency opener can otherwise pair with the opening delimiter of a\n // later expression (\"$5 then ($x$)\"). Reinterpret that first candidate\n // only when it starts with a bounded numeric amount and its apparent closer\n // is immediately followed by a complete later inline body. This deliberately\n // leaves inherently ambiguous standalone forms ($5$, $2 + 3$, $2\\pi$) as\n // math and does not depend on the prose between the two delimiters.\n const amount = /^\\d+(?:[.,]\\d{1,2})?(?=$|[\\s,;:!?()[\\]+*/-])/.exec(tex)?.[0]\n if (!amount) return false\n\n // A standalone numeric token is valid math even when followed by prose or\n // another expression (\"$5$, then $x$\"). Reassignment requires content\n // beyond the amount itself.\n const trailing = tex.slice(amount.length)\n if (!trailing) return false\n\n // During streaming, the later formula may not have closed yet. An unmatched\n // opening parenthesis before its dollar is sufficient structural evidence\n // that the numeric dollar was currency; otherwise a permissive renderer can\n // briefly flash \"5 then (\" as math.\n const hasUnmatchedOpeningParen = (trailing.match(/\\(/g)?.length ?? 0) > (trailing.match(/\\)/g)?.length ?? 0)\n const containsTexCommand = /\\\\[A-Za-z]+/.test(trailing)\n if (hasUnmatchedOpeningParen && !containsTexCommand) return true\n\n const closerLooksLikeLaterOpener = /^(?![\\s,.;:!?)}\\]])(?:\\\\[^\\n]|[^\\\\\\n$])+\\$(?!\\$)/.test(following)\n return closerLooksLikeLaterOpener\n}\n\nconst CELL_LINK_ATTRIBUTES = 'target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] hover:underline\"'\n\nfunction cellLinkExtension(): MarkedExtension {\n return {\n renderer: {\n link(this: Renderer, token: Tokens.Link): string {\n // Marked owns escaping of the destination, title and nested label\n // tokens. Only inject fixed attributes into its already-safe anchor.\n const anchor = Renderer.prototype.link.call(this, token) as string\n return anchor.replace('<a ', `<a ${CELL_LINK_ATTRIBUTES} `)\n },\n },\n }\n}\n\nfunction mathExtension(renderer: MathRenderer): MarkedExtension {\n let count = 0\n const token = (raw: string, tex: string, displayMode: boolean): MathToken => ({\n type: displayMode ? 'mcpDisplayMath' : 'mcpInlineMath',\n raw,\n tex,\n displayMode,\n render: ++count <= MATH_MARKDOWN_LIMITS.expressionCount && tex.length <= MATH_MARKDOWN_LIMITS.expressionLength,\n })\n\n return {\n extensions: [\n {\n name: 'mcpDisplayMath',\n level: 'block',\n start(src) {\n const index = src.indexOf('$$')\n return index < 0 ? undefined : index\n },\n tokenizer(src) {\n const match = /^\\$\\$\\s*\\n?([\\s\\S]+?)\\n?\\s*\\$\\$(?:\\n|$)/.exec(src)\n if (!match) return undefined\n return token(match[0], match[1], true)\n },\n renderer(tokenValue) {\n return renderToken(tokenValue as MathToken, renderer)\n },\n },\n {\n name: 'mcpInlineMath',\n level: 'inline',\n start(src) {\n const index = src.indexOf('$')\n return index < 0 ? undefined : index\n },\n tokenizer(src) {\n const match = /^\\$(?!\\$)((?:\\\\[^\\n]|[^\\\\\\n$])+?)\\$(?!\\$)/.exec(src)\n if (!match || isCurrencyLike(match[0], match[1], src.slice(match[0].length))) return undefined\n return token(match[0], match[1], false)\n },\n renderer(tokenValue) {\n return renderToken(tokenValue as MathToken, renderer)\n },\n },\n ],\n }\n}\n\nfunction renderToken(token: MathToken, renderer: MathRenderer): string {\n if (!token.render) return escapeHtml(token.raw)\n try {\n const rendered = renderer(token.tex, { displayMode: token.displayMode })\n if (!rendered) return escapeHtml(token.raw)\n return sanitizeHtml(rendered, STRICT_MATHML_CONFIG)\n } catch {\n return escapeHtml(token.raw)\n }\n}\n\n/**\n * Parse Markdown with isolated, bounded math support and sanitize the complete\n * result for its final sink. The optional transform sees escaped ordinary text\n * tokens only. Math, code, raw HTML and link destinations never pass through\n * it, so citation syntax cannot corrupt TeX optional arguments or attributes.\n */\nexport function parseMathMarkdown(\n source: string,\n renderer: MathRenderer,\n profile: MathMarkdownProfile,\n transformText?: MarkdownTextTransform,\n): string {\n const input = source === null || source === undefined ? '' : String(source)\n if (!canSanitizeHtml()) return escapeHtml(input)\n if (input.length > MATH_MARKDOWN_LIMITS.sourceLength) return escapeHtml(input)\n\n const textRenderer = transformText\n ? {\n renderer: {\n text(this: Renderer, token: Tokens.Text | Tokens.Escape): string {\n // Block text tokens (notably list-item text) can own already\n // tokenized inline children. Recurse first so math and code keep\n // their own renderers; leaf text callbacks run as parsing descends.\n if ('tokens' in token && token.tokens) {\n return this.parser.parseInline(token.tokens)\n }\n // Delegate entity handling and nested token rendering to Marked's\n // own renderer. `escapeHtml` is deliberately not equivalent: it\n // turns source `&amp;` into visible `&amp;` by double-escaping it.\n const safeText = Renderer.prototype.text.call(this, token)\n return transformText(safeText as string)\n },\n },\n }\n : undefined\n const extensions = [mathExtension(renderer)]\n if (profile === 'cellMarkdown') extensions.push(cellLinkExtension())\n if (textRenderer) extensions.push(textRenderer)\n const parser = new Marked({ gfm: true }, ...extensions)\n const parsed = parser.parse(input, { async: false }) as string\n return sanitizeHtml(parsed, FINAL_PROFILES[profile])\n}\n"],"names":[],"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;"}
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const solidJs = require("solid-js");
4
+ const THEME_PREFIX = "--mcp-ui-";
5
+ function readThemeTokens(element, names) {
6
+ const result = {};
7
+ const view = element == null ? void 0 : element.ownerDocument.defaultView;
8
+ const style = element && view ? view.getComputedStyle(element) : void 0;
9
+ for (const name of names) result[name] = (style == null ? void 0 : style.getPropertyValue(name).trim()) || void 0;
10
+ return result;
11
+ }
12
+ function observeTheme(element, callback) {
13
+ const view = element.ownerDocument.defaultView;
14
+ if (!(view == null ? void 0 : view.MutationObserver)) return () => {
15
+ };
16
+ let stopped = false;
17
+ let pending = false;
18
+ const schedule = () => {
19
+ if (pending || stopped) return;
20
+ pending = true;
21
+ queueMicrotask(() => {
22
+ pending = false;
23
+ if (!stopped) callback();
24
+ });
25
+ };
26
+ const observer = new view.MutationObserver((records) => {
27
+ if (records.some((record) => {
28
+ if (record.type === "attributes") {
29
+ return record.target instanceof view.Element && record.target.contains(element);
30
+ }
31
+ return [...record.addedNodes, ...record.removedNodes].some(
32
+ (node) => node === element || node.contains(element)
33
+ );
34
+ })) schedule();
35
+ });
36
+ observer.observe(element.ownerDocument.documentElement, {
37
+ attributes: true,
38
+ childList: true,
39
+ subtree: true
40
+ });
41
+ return () => {
42
+ stopped = true;
43
+ observer.disconnect();
44
+ };
45
+ }
46
+ function bindPortalTheme(portal, source) {
47
+ solidJs.createEffect(() => {
48
+ const anchor = source();
49
+ const view = anchor == null ? void 0 : anchor.ownerDocument.defaultView;
50
+ if (!anchor || !view) return;
51
+ const copied = /* @__PURE__ */ new Set();
52
+ const sync = () => {
53
+ const style = view.getComputedStyle(anchor);
54
+ const inherited = portal.parentElement ? view.getComputedStyle(portal.parentElement) : void 0;
55
+ const names = new Set(copied);
56
+ for (const computed of [style, inherited]) {
57
+ if (!computed) continue;
58
+ for (let index = 0; index < computed.length; index++) {
59
+ const name = computed.item(index);
60
+ if (name.startsWith(THEME_PREFIX)) names.add(name);
61
+ }
62
+ }
63
+ const values = /* @__PURE__ */ new Map();
64
+ for (const name of names) {
65
+ const value = style.getPropertyValue(name).trim();
66
+ if (value) values.set(name, value);
67
+ else if (inherited == null ? void 0 : inherited.getPropertyValue(name).trim()) values.set(name, "initial");
68
+ }
69
+ for (const name of copied) {
70
+ if (!values.has(name)) portal.style.removeProperty(name);
71
+ }
72
+ copied.clear();
73
+ for (const [name, value] of values) {
74
+ if (portal.style.getPropertyValue(name) !== value) portal.style.setProperty(name, value);
75
+ copied.add(name);
76
+ }
77
+ portal.classList.toggle("dark", anchor.closest(".dark") !== null);
78
+ };
79
+ sync();
80
+ const stop = observeTheme(anchor, sync);
81
+ solidJs.onCleanup(() => {
82
+ stop();
83
+ for (const name of copied) portal.style.removeProperty(name);
84
+ portal.classList.remove("dark");
85
+ });
86
+ });
87
+ }
88
+ exports.bindPortalTheme = bindPortalTheme;
89
+ exports.observeTheme = observeTheme;
90
+ exports.readThemeTokens = readThemeTokens;
91
+ //# sourceMappingURL=theme.cjs.map