@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.
- package/CHANGELOG.md +167 -0
- package/README.md +154 -2
- package/dist/components/ActionGroupRenderer.cjs +6 -6
- package/dist/components/ActionGroupRenderer.cjs.map +1 -1
- package/dist/components/ActionGroupRenderer.js +6 -6
- package/dist/components/ActionGroupRenderer.js.map +1 -1
- package/dist/components/AutocompleteDropdown.cjs +6 -6
- package/dist/components/AutocompleteDropdown.cjs.map +1 -1
- package/dist/components/AutocompleteDropdown.js +6 -6
- package/dist/components/AutocompleteDropdown.js.map +1 -1
- package/dist/components/AutocompleteFormField.cjs +6 -5
- package/dist/components/AutocompleteFormField.cjs.map +1 -1
- package/dist/components/AutocompleteFormField.d.ts.map +1 -1
- package/dist/components/AutocompleteFormField.js +6 -5
- package/dist/components/AutocompleteFormField.js.map +1 -1
- package/dist/components/ChartJSRenderer.cjs +406 -70
- package/dist/components/ChartJSRenderer.cjs.map +1 -1
- package/dist/components/ChartJSRenderer.d.ts.map +1 -1
- package/dist/components/ChartJSRenderer.js +407 -71
- package/dist/components/ChartJSRenderer.js.map +1 -1
- package/dist/components/ChatPrompt.cjs +5 -5
- package/dist/components/ChatPrompt.cjs.map +1 -1
- package/dist/components/ChatPrompt.js +5 -5
- package/dist/components/ChatPrompt.js.map +1 -1
- package/dist/components/ComponentToolbar.cjs +5 -5
- package/dist/components/ComponentToolbar.cjs.map +1 -1
- package/dist/components/ComponentToolbar.js +5 -5
- package/dist/components/ComponentToolbar.js.map +1 -1
- package/dist/components/DegradedFallback.cjs +35 -3
- package/dist/components/DegradedFallback.cjs.map +1 -1
- package/dist/components/DegradedFallback.d.ts +7 -0
- package/dist/components/DegradedFallback.d.ts.map +1 -1
- package/dist/components/DegradedFallback.js +35 -3
- package/dist/components/DegradedFallback.js.map +1 -1
- package/dist/components/ExpandableWrapper.cjs +10 -7
- package/dist/components/ExpandableWrapper.cjs.map +1 -1
- package/dist/components/ExpandableWrapper.d.ts.map +1 -1
- package/dist/components/ExpandableWrapper.js +10 -7
- package/dist/components/ExpandableWrapper.js.map +1 -1
- package/dist/components/FormFieldRenderer.cjs +13 -12
- package/dist/components/FormFieldRenderer.cjs.map +1 -1
- package/dist/components/FormFieldRenderer.d.ts.map +1 -1
- package/dist/components/FormFieldRenderer.js +13 -12
- package/dist/components/FormFieldRenderer.js.map +1 -1
- package/dist/components/FormRenderer.cjs +1 -1
- package/dist/components/FormRenderer.cjs.map +1 -1
- package/dist/components/FormRenderer.js +1 -1
- package/dist/components/FormRenderer.js.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.cjs +1 -1
- package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.js +1 -1
- package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
- package/dist/components/GhostText.cjs +2 -2
- package/dist/components/GhostText.cjs.map +1 -1
- package/dist/components/GhostText.js +2 -2
- package/dist/components/GhostText.js.map +1 -1
- package/dist/components/GraphRenderer.cjs +100 -29
- package/dist/components/GraphRenderer.cjs.map +1 -1
- package/dist/components/GraphRenderer.d.ts +1 -1
- package/dist/components/GraphRenderer.d.ts.map +1 -1
- package/dist/components/GraphRenderer.js +100 -29
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/MissingValue.cjs +43 -0
- package/dist/components/MissingValue.cjs.map +1 -0
- package/dist/components/MissingValue.d.ts +27 -0
- package/dist/components/MissingValue.d.ts.map +1 -0
- package/dist/components/MissingValue.js +43 -0
- package/dist/components/MissingValue.js.map +1 -0
- package/dist/components/ModalRenderer.cjs +39 -31
- package/dist/components/ModalRenderer.cjs.map +1 -1
- package/dist/components/ModalRenderer.d.ts.map +1 -1
- package/dist/components/ModalRenderer.js +40 -32
- package/dist/components/ModalRenderer.js.map +1 -1
- package/dist/components/PortalDropdownMenu.cjs +6 -3
- package/dist/components/PortalDropdownMenu.cjs.map +1 -1
- package/dist/components/PortalDropdownMenu.d.ts.map +1 -1
- package/dist/components/PortalDropdownMenu.js +7 -4
- package/dist/components/PortalDropdownMenu.js.map +1 -1
- package/dist/components/ScratchpadPanel.cjs +38 -44
- package/dist/components/ScratchpadPanel.cjs.map +1 -1
- package/dist/components/ScratchpadPanel.d.ts.map +1 -1
- package/dist/components/ScratchpadPanel.js +38 -44
- package/dist/components/ScratchpadPanel.js.map +1 -1
- package/dist/components/UIResourceRenderer.cjs +255 -217
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +255 -217
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/components/chart-data-table.cjs +30 -5
- package/dist/components/chart-data-table.cjs.map +1 -1
- package/dist/components/chart-data-table.d.ts +18 -1
- package/dist/components/chart-data-table.d.ts.map +1 -1
- package/dist/components/chart-data-table.js +30 -5
- package/dist/components/chart-data-table.js.map +1 -1
- package/dist/components/chart-missing-plugin.cjs +194 -0
- package/dist/components/chart-missing-plugin.cjs.map +1 -0
- package/dist/components/chart-missing-plugin.d.ts +129 -0
- package/dist/components/chart-missing-plugin.d.ts.map +1 -0
- package/dist/components/chart-missing-plugin.js +194 -0
- package/dist/components/chart-missing-plugin.js.map +1 -0
- package/dist/context/MCPUIStringsContext.cjs +12 -1
- package/dist/context/MCPUIStringsContext.cjs.map +1 -1
- package/dist/context/MCPUIStringsContext.d.ts +62 -0
- package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
- package/dist/context/MCPUIStringsContext.js +12 -1
- package/dist/context/MCPUIStringsContext.js.map +1 -1
- package/dist/services/component-registry.cjs +9 -2
- package/dist/services/component-registry.cjs.map +1 -1
- package/dist/services/component-registry.d.ts.map +1 -1
- package/dist/services/component-registry.js +9 -2
- package/dist/services/component-registry.js.map +1 -1
- package/dist/services/validation.cjs +31 -8
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +31 -8
- package/dist/services/validation.js.map +1 -1
- package/dist/types/index.d.ts +14 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/degraded-projections.cjs +19 -3
- package/dist/utils/degraded-projections.cjs.map +1 -1
- package/dist/utils/degraded-projections.d.ts +22 -2
- package/dist/utils/degraded-projections.d.ts.map +1 -1
- package/dist/utils/degraded-projections.js +19 -3
- package/dist/utils/degraded-projections.js.map +1 -1
- package/dist/utils/math-markdown.cjs +1 -1
- package/dist/utils/math-markdown.cjs.map +1 -1
- package/dist/utils/math-markdown.js +1 -1
- package/dist/utils/math-markdown.js.map +1 -1
- package/dist/utils/missing-value.cjs +60 -0
- package/dist/utils/missing-value.cjs.map +1 -0
- package/dist/utils/missing-value.d.ts +79 -0
- package/dist/utils/missing-value.d.ts.map +1 -0
- package/dist/utils/missing-value.js +60 -0
- package/dist/utils/missing-value.js.map +1 -0
- package/dist/utils/theme.cjs +91 -0
- package/dist/utils/theme.cjs.map +1 -0
- package/dist/utils/theme.d.ts +11 -0
- package/dist/utils/theme.d.ts.map +1 -0
- package/dist/utils/theme.js +91 -0
- package/dist/utils/theme.js.map +1 -0
- package/docs/SECURITY-DEPENDENCIES.md +81 -0
- package/docs/THEMING.md +205 -0
- package/package.json +7 -5
- package/src/components/ActionGroupRenderer.tsx +6 -6
- package/src/components/AutocompleteDropdown.tsx +17 -17
- package/src/components/AutocompleteFormField.tsx +10 -9
- package/src/components/ChartJSRenderer.tsx +609 -212
- package/src/components/ChatPrompt.tsx +16 -16
- package/src/components/ComponentToolbar.tsx +5 -5
- package/src/components/DegradedFallback.tsx +33 -11
- package/src/components/ExpandableWrapper.tsx +15 -11
- package/src/components/FormFieldRenderer.tsx +52 -51
- package/src/components/FormRenderer.tsx +12 -12
- package/src/components/GenerativeUIErrorBoundary.tsx +6 -6
- package/src/components/GhostText.tsx +7 -7
- package/src/components/GraphRenderer.tsx +83 -67
- package/src/components/MissingValue.tsx +61 -0
- package/src/components/ModalRenderer.tsx +11 -5
- package/src/components/PortalDropdownMenu.tsx +6 -2
- package/src/components/ScratchpadPanel.tsx +144 -141
- package/src/components/UIResourceRenderer.tsx +143 -110
- package/src/components/chart-data-table.ts +50 -6
- package/src/components/chart-missing-plugin.ts +336 -0
- package/src/context/MCPUIStringsContext.tsx +76 -0
- package/src/services/component-registry.ts +10 -2
- package/src/services/validation.ts +44 -9
- package/src/styles/autocomplete.css +53 -53
- package/src/types/index.ts +13 -1
- package/src/utils/degraded-projections.ts +44 -4
- package/src/utils/math-markdown.ts +1 -1
- package/src/utils/missing-value.ts +127 -0
- package/src/utils/theme.ts +101 -0
|
@@ -20,10 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import { Component, createSignal, onCleanup, onMount, Show, For } from 'solid-js';
|
|
22
22
|
import type { UIComponent } from '../types';
|
|
23
|
-
import type {
|
|
24
|
-
GraphComponentParams,
|
|
25
|
-
GraphLayout,
|
|
26
|
-
} from '@seed-ship/mcp-ui-spec';
|
|
23
|
+
import type { GraphComponentParams, GraphLayout } from '@seed-ship/mcp-ui-spec';
|
|
27
24
|
import { ExpandableWrapper, useExpanded } from './ExpandableWrapper';
|
|
28
25
|
import { PortalDropdownMenu } from './PortalDropdownMenu';
|
|
29
26
|
import { DegradedFallback } from './DegradedFallback';
|
|
@@ -31,6 +28,40 @@ import { graphToDegradedTable } from '../utils/degraded-projections';
|
|
|
31
28
|
import { escapeHtml } from '../utils/escape-html';
|
|
32
29
|
import { useTelemetry } from '../context/MCPUITelemetryContext';
|
|
33
30
|
import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext';
|
|
31
|
+
import { observeTheme, readThemeTokens } from '../utils/theme';
|
|
32
|
+
|
|
33
|
+
const GRAPH_THEME_TOKENS = [
|
|
34
|
+
...Array.from({ length: 8 }, (_, i) => `--mcp-ui-chart-${i + 1}`),
|
|
35
|
+
'--mcp-ui-chart-grid',
|
|
36
|
+
'--mcp-ui-chart-axis',
|
|
37
|
+
'--mcp-ui-chart-label',
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
/** G6's option mappers outrank datum.style, so explicitly retain the latter. */
|
|
41
|
+
function graphThemeOptions(p: GraphComponentParams, tokens: Record<string, string | undefined>) {
|
|
42
|
+
const nodeIndices = new Map(p.nodes.map((node, index) => [node.id, index]));
|
|
43
|
+
return {
|
|
44
|
+
node: {
|
|
45
|
+
style: (datum: any) => {
|
|
46
|
+
const index = nodeIndices.get(datum.id) ?? 0;
|
|
47
|
+
const fill = tokens[`--mcp-ui-chart-${(index % 8) + 1}`];
|
|
48
|
+
return {
|
|
49
|
+
...(fill ? { fill } : {}),
|
|
50
|
+
...(tokens['--mcp-ui-chart-axis'] ? { stroke: tokens['--mcp-ui-chart-axis'] } : {}),
|
|
51
|
+
...(tokens['--mcp-ui-chart-label'] ? { labelFill: tokens['--mcp-ui-chart-label'] } : {}),
|
|
52
|
+
...datum.style,
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
edge: {
|
|
57
|
+
style: (datum: any) => ({
|
|
58
|
+
...(tokens['--mcp-ui-chart-grid'] ? { stroke: tokens['--mcp-ui-chart-grid'] } : {}),
|
|
59
|
+
...(tokens['--mcp-ui-chart-label'] ? { labelFill: tokens['--mcp-ui-chart-label'] } : {}),
|
|
60
|
+
...datum.style,
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
34
65
|
|
|
35
66
|
// Module-scoped lazy import promise — first call triggers the dynamic
|
|
36
67
|
// import, subsequent calls reuse the resolved module.
|
|
@@ -94,7 +125,8 @@ function resolveLayout(params: GraphComponentParams): { type: string; [key: stri
|
|
|
94
125
|
*/
|
|
95
126
|
export function buildGraphConfig(
|
|
96
127
|
p: GraphComponentParams,
|
|
97
|
-
container?: HTMLElement
|
|
128
|
+
container?: HTMLElement,
|
|
129
|
+
tokens: Record<string, string | undefined> = {}
|
|
98
130
|
): Record<string, unknown> {
|
|
99
131
|
const config: Record<string, unknown> = {
|
|
100
132
|
container,
|
|
@@ -102,6 +134,9 @@ export function buildGraphConfig(
|
|
|
102
134
|
layout: resolveLayout(p),
|
|
103
135
|
behaviors: resolveBehaviors(p),
|
|
104
136
|
};
|
|
137
|
+
if (GRAPH_THEME_TOKENS.some((name) => tokens[name])) {
|
|
138
|
+
Object.assign(config, graphThemeOptions(p, tokens));
|
|
139
|
+
}
|
|
105
140
|
|
|
106
141
|
if (p.rendererPref === 'svg') {
|
|
107
142
|
console.warn(
|
|
@@ -122,7 +157,14 @@ export function buildGraphConfig(
|
|
|
122
157
|
if (!item) return '';
|
|
123
158
|
const label = item.label ?? item.id ?? '';
|
|
124
159
|
const data = item.data ? JSON.stringify(item.data) : '';
|
|
125
|
-
|
|
160
|
+
// G6 may position HTML outside our subtree. Resolve effective host
|
|
161
|
+
// values at hover time, rather than relying on portal inheritance.
|
|
162
|
+
const theme = readThemeTokens(container, ['--mcp-ui-surface', '--mcp-ui-fg', '--mcp-ui-border']);
|
|
163
|
+
const styles = ['padding:4px 8px'];
|
|
164
|
+
if (theme['--mcp-ui-surface']) styles.push(`background-color:${theme['--mcp-ui-surface']}`);
|
|
165
|
+
if (theme['--mcp-ui-fg']) styles.push(`color:${theme['--mcp-ui-fg']}`);
|
|
166
|
+
if (theme['--mcp-ui-border']) styles.push(`border:1px solid ${theme['--mcp-ui-border']}`);
|
|
167
|
+
return `<div style="${escapeHtml(styles.join(';'))}"><strong>${escapeHtml(String(label))}</strong>${
|
|
126
168
|
data ? `<br><span style="font-size:11px;opacity:0.7">${escapeHtml(data)}</span>` : ''
|
|
127
169
|
}</div>`;
|
|
128
170
|
},
|
|
@@ -234,70 +276,40 @@ export const GraphRenderer: Component<GraphRendererProps> = (props) => {
|
|
|
234
276
|
// Loosely typed because G6 is a peer-optional — we don't pull its
|
|
235
277
|
// types into the bundle just to type a transient local handle.
|
|
236
278
|
let graphInstance: any | undefined;
|
|
279
|
+
let stopTheme: (() => void) | undefined;
|
|
280
|
+
let disposed = false;
|
|
237
281
|
|
|
238
282
|
onMount(async () => {
|
|
239
283
|
const g6Available = await isG6Available();
|
|
284
|
+
if (disposed) return;
|
|
240
285
|
setAvailable(g6Available);
|
|
241
286
|
if (!g6Available || !containerRef) return;
|
|
242
287
|
|
|
243
288
|
try {
|
|
244
289
|
const { Graph } = await g6ModulePromise!;
|
|
290
|
+
if (disposed || !containerRef) return;
|
|
245
291
|
const p = params();
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
data: { nodes: p.nodes, edges: p.edges ?? [] },
|
|
249
|
-
layout: resolveLayout(p),
|
|
250
|
-
behaviors: resolveBehaviors(p),
|
|
251
|
-
};
|
|
292
|
+
let tokens = readThemeTokens(containerRef, GRAPH_THEME_TOKENS);
|
|
293
|
+
const config = buildGraphConfig(p, containerRef, tokens);
|
|
252
294
|
|
|
253
|
-
// G6 v5's `renderer` is a factory `(layer) => IRenderer`, NOT a string
|
|
254
|
-
// (that was the v4 contract). Passing the string `'canvas'` / `'svg'`
|
|
255
|
-
// makes G6 throw `renderer is not a function` — and because the default
|
|
256
|
-
// path also passed the string `'canvas'`, EVERY graph crashed, not just
|
|
257
|
-
// the svg one.
|
|
258
|
-
//
|
|
259
|
-
// Fix: omit `renderer` entirely. G6 then uses its built-in canvas
|
|
260
|
-
// renderer (documented default `() => new CanvasRenderer()`), which is
|
|
261
|
-
// what we want for every graph.
|
|
262
|
-
//
|
|
263
|
-
// `rendererPref: 'svg'` is NOT wired yet: a real G6 v5 SVG renderer
|
|
264
|
-
// needs the `@antv/g-svg` factory, which is a *transitive* dep of
|
|
265
|
-
// `@antv/g6` (not a declared peer) and isn't statically resolvable at
|
|
266
|
-
// build time. Rather than ship a fragile/unresolvable import, we treat
|
|
267
|
-
// svg as "not yet supported" and fall back to the canvas default with a
|
|
268
|
-
// one-time warning. Tracked for a follow-up that wires the factory
|
|
269
|
-
// behind a proper optional-peer resolution. (Never pass a string here.)
|
|
270
|
-
if (p.rendererPref === 'svg') {
|
|
271
|
-
console.warn(
|
|
272
|
-
'[MCP-UI] GraphRenderer: rendererPref "svg" is not yet supported; using the default canvas renderer.'
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
if (p.fitView !== false) {
|
|
276
|
-
config.autoFit = 'view';
|
|
277
|
-
}
|
|
278
|
-
if (p.tooltip !== false) {
|
|
279
|
-
// Built-in tooltip plugin — shows label + a compact dump of
|
|
280
|
-
// node.data on hover. Consumers can opt out with `tooltip: false`.
|
|
281
|
-
config.plugins = [
|
|
282
|
-
{
|
|
283
|
-
type: 'tooltip',
|
|
284
|
-
getContent: (_evt: unknown, items: any[]) => {
|
|
285
|
-
const item = items?.[0];
|
|
286
|
-
if (!item) return '';
|
|
287
|
-
const label = item.label ?? item.id ?? '';
|
|
288
|
-
const data = item.data ? JSON.stringify(item.data) : '';
|
|
289
|
-
return `<div style="padding:4px 8px"><strong>${escapeHtml(String(label))}</strong>${
|
|
290
|
-
data
|
|
291
|
-
? `<br><span style="font-size:11px;opacity:0.7">${escapeHtml(data)}</span>`
|
|
292
|
-
: ''
|
|
293
|
-
}</div>`;
|
|
294
|
-
},
|
|
295
|
-
},
|
|
296
|
-
];
|
|
297
|
-
}
|
|
298
295
|
graphInstance = new (Graph as any)(config);
|
|
299
296
|
await graphInstance.render();
|
|
297
|
+
if (disposed) return;
|
|
298
|
+
const syncTheme = () => {
|
|
299
|
+
if (!graphInstance || disposed) return;
|
|
300
|
+
const next = readThemeTokens(containerRef, GRAPH_THEME_TOKENS);
|
|
301
|
+
if (GRAPH_THEME_TOKENS.every((name) => next[name] === tokens[name])) return;
|
|
302
|
+
tokens = next;
|
|
303
|
+
graphInstance.setOptions(graphThemeOptions(p, tokens));
|
|
304
|
+
// draw() updates styles without rerunning force layout or auto-fit.
|
|
305
|
+
void Promise.resolve(graphInstance.draw()).catch(() => {});
|
|
306
|
+
};
|
|
307
|
+
stopTheme = observeTheme(containerRef, syncTheme);
|
|
308
|
+
// Rendering/layout is async: a host may have switched themes while it
|
|
309
|
+
// was in flight, before the observer could be attached.
|
|
310
|
+
syncTheme();
|
|
300
311
|
} catch (err) {
|
|
312
|
+
if (disposed) return;
|
|
301
313
|
const message = err instanceof Error ? err.message : strings.graphRenderError;
|
|
302
314
|
setError(message);
|
|
303
315
|
// Fallback ladder (P2.5): the native G6 render threw — emit telemetry
|
|
@@ -314,6 +326,8 @@ export const GraphRenderer: Component<GraphRendererProps> = (props) => {
|
|
|
314
326
|
});
|
|
315
327
|
|
|
316
328
|
onCleanup(() => {
|
|
329
|
+
disposed = true;
|
|
330
|
+
stopTheme?.();
|
|
317
331
|
try {
|
|
318
332
|
graphInstance?.destroy();
|
|
319
333
|
} catch {
|
|
@@ -365,17 +379,17 @@ export const GraphRenderer: Component<GraphRendererProps> = (props) => {
|
|
|
365
379
|
when={available() === false}
|
|
366
380
|
fallback={
|
|
367
381
|
// Loading skeleton while we determine peer availability
|
|
368
|
-
<div class="w-full p-4 bg-gray-50 dark:bg-gray-800
|
|
369
|
-
<div class="h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-2" />
|
|
370
|
-
<div class="h-3 w-48 bg-gray-200 dark:bg-gray-700 rounded" />
|
|
382
|
+
<div class="w-full p-4 bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-50,#f9fafb))] dark:bg-[color:var(--mcp-ui-surface,color-mix(in_oklab,var(--color-gray-800,#1f2937)_40%,transparent))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] rounded-lg animate-pulse">
|
|
383
|
+
<div class="h-4 w-32 bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-200,#e5e7eb))] dark:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-700,#374151))] rounded mb-2" />
|
|
384
|
+
<div class="h-3 w-48 bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-200,#e5e7eb))] dark:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-700,#374151))] rounded" />
|
|
371
385
|
</div>
|
|
372
386
|
}
|
|
373
387
|
>
|
|
374
|
-
<div class="w-full p-4 bg-yellow-50 dark:bg-yellow-900
|
|
375
|
-
<p class="text-sm font-medium text-yellow-900 dark:text-yellow-100">
|
|
388
|
+
<div class="w-full p-4 bg-[color:var(--mcp-ui-warning-soft,var(--color-yellow-50))] dark:bg-[color:var(--mcp-ui-warning-soft,color-mix(in_oklab,var(--color-yellow-900)_20%,transparent))] border border-[color:var(--mcp-ui-warning-border,var(--color-yellow-200))] dark:border-[color:var(--mcp-ui-warning-border,var(--color-yellow-800))] rounded-lg">
|
|
389
|
+
<p class="text-sm font-medium text-[color:var(--mcp-ui-warning,var(--color-yellow-900))] dark:text-[color:var(--mcp-ui-warning,var(--color-yellow-100))]">
|
|
376
390
|
{strings.graphUnavailable}
|
|
377
391
|
</p>
|
|
378
|
-
<p class="text-xs text-yellow-700 dark:text-yellow-300 mt-1">
|
|
392
|
+
<p class="text-xs text-[color:var(--mcp-ui-warning,var(--color-yellow-700))] dark:text-[color:var(--mcp-ui-warning,var(--color-yellow-300))] mt-1">
|
|
379
393
|
Install <code>@antv/g6</code> peer dependency to render <code>type: "graph"</code>{' '}
|
|
380
394
|
components.
|
|
381
395
|
</p>
|
|
@@ -400,7 +414,7 @@ export const GraphRenderer: Component<GraphRendererProps> = (props) => {
|
|
|
400
414
|
ref={exportTriggerRef}
|
|
401
415
|
type="button"
|
|
402
416
|
onClick={() => setExportMenuOpen((v) => !v)}
|
|
403
|
-
class="px-2 py-1 text-xs bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
|
|
417
|
+
class="px-2 py-1 text-xs bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] rounded shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-50,#f9fafb))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))]"
|
|
404
418
|
title={strings.graphExport}
|
|
405
419
|
aria-label={strings.graphExport}
|
|
406
420
|
aria-haspopup="menu"
|
|
@@ -438,10 +452,12 @@ export const GraphRenderer: Component<GraphRendererProps> = (props) => {
|
|
|
438
452
|
<button
|
|
439
453
|
type="button"
|
|
440
454
|
onClick={item.onClick}
|
|
441
|
-
class="w-full text-left px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-gray-700 last:border-b-0"
|
|
455
|
+
class="w-full text-left px-3 py-2 hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-100,#f3f4f6))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] border-b border-[color:var(--mcp-ui-border,var(--color-gray-100,#f3f4f6))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] last:border-b-0"
|
|
442
456
|
>
|
|
443
457
|
<div class="font-medium">{item.label}</div>
|
|
444
|
-
<div class="text-[10px] text-gray-500 dark:text-gray-400">
|
|
458
|
+
<div class="text-[10px] text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]">
|
|
459
|
+
{item.hint}
|
|
460
|
+
</div>
|
|
445
461
|
</button>
|
|
446
462
|
)}
|
|
447
463
|
</For>
|
|
@@ -451,7 +467,7 @@ export const GraphRenderer: Component<GraphRendererProps> = (props) => {
|
|
|
451
467
|
{/* Native G6 canvas — hidden once a render error degrades us. */}
|
|
452
468
|
<div
|
|
453
469
|
ref={containerRef}
|
|
454
|
-
class={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${
|
|
470
|
+
class={`mcp-ui-root bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] overflow-hidden ${
|
|
455
471
|
error() ? 'hidden' : ''
|
|
456
472
|
} ${isExpanded() ? 'flex-1 min-h-0' : ''}`}
|
|
457
473
|
style={
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The visible and accessible marks of a missing value (v6.24.0).
|
|
3
|
+
*
|
|
4
|
+
* A table cell with no value has always shown `-`. On its own that glyph says
|
|
5
|
+
* nothing to a screen reader (it is read as "dash", or skipped) and it is easy
|
|
6
|
+
* to take for a zero or a minus sign. These two components give it a meaning:
|
|
7
|
+
*
|
|
8
|
+
* - {@link MissingValueMark} — the cell content: the `-` hidden from
|
|
9
|
+
* assistive technology, followed by `strings.missingValue` hidden from
|
|
10
|
+
* sight. Visually-hidden through an inline style rather than Tailwind's
|
|
11
|
+
* `sr-only`, so the text cannot leak on a host whose Tailwind build does
|
|
12
|
+
* not scan this package.
|
|
13
|
+
* - {@link MissingValueLegend} — one line under a table or data view that
|
|
14
|
+
* contains a missing value, so the convention never has to be guessed.
|
|
15
|
+
*
|
|
16
|
+
* Both are SSR-safe: no browser API, and the same markup on server and client.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { Component, JSX } from 'solid-js'
|
|
20
|
+
import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
|
|
21
|
+
import { MISSING_VALUE_MARKER } from '../utils/missing-value'
|
|
22
|
+
|
|
23
|
+
/** The classic visually-hidden recipe: in the accessibility tree, out of sight. */
|
|
24
|
+
export const VISUALLY_HIDDEN_STYLE: JSX.CSSProperties = {
|
|
25
|
+
position: 'absolute',
|
|
26
|
+
width: '1px',
|
|
27
|
+
height: '1px',
|
|
28
|
+
padding: '0',
|
|
29
|
+
margin: '-1px',
|
|
30
|
+
overflow: 'hidden',
|
|
31
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
32
|
+
'white-space': 'nowrap',
|
|
33
|
+
border: '0',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Content of a cell whose value is missing. */
|
|
37
|
+
export const MissingValueMark: Component = () => {
|
|
38
|
+
const strings = useMCPUIStrings()
|
|
39
|
+
return (
|
|
40
|
+
<span data-mcp-missing-value="">
|
|
41
|
+
<span aria-hidden="true" class="text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] dark:text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))]">
|
|
42
|
+
{MISSING_VALUE_MARKER}
|
|
43
|
+
</span>
|
|
44
|
+
<span style={VISUALLY_HIDDEN_STYLE}>{strings.missingValue}</span>
|
|
45
|
+
</span>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The legend line explaining the missing-value glyph. */
|
|
50
|
+
export const MissingValueLegend: Component<{ class?: string }> = (props) => {
|
|
51
|
+
const strings = useMCPUIStrings()
|
|
52
|
+
return (
|
|
53
|
+
<p
|
|
54
|
+
data-mcp-missing-legend=""
|
|
55
|
+
role="note"
|
|
56
|
+
class={`text-xs text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] ${props.class ?? ''}`}
|
|
57
|
+
>
|
|
58
|
+
{formatMCPUIString(strings.missingValueLegend, { marker: MISSING_VALUE_MARKER })}
|
|
59
|
+
</p>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { Component, Show, createSignal, createEffect, onCleanup, JSX } from 'solid-js'
|
|
7
7
|
import { Portal } from 'solid-js/web'
|
|
8
|
+
import { bindPortalTheme } from '../utils/theme'
|
|
8
9
|
import type { UIComponent, ModalComponentParams } from '../types'
|
|
9
10
|
import { useMCPUIStrings } from '../context/MCPUIStringsContext'
|
|
10
11
|
|
|
@@ -75,6 +76,7 @@ export interface ModalRendererProps {
|
|
|
75
76
|
export const ModalRenderer: Component<ModalRendererProps> = (props) => {
|
|
76
77
|
const strings = useMCPUIStrings()
|
|
77
78
|
const [isVisible, setIsVisible] = createSignal(false)
|
|
79
|
+
let themeAnchor: HTMLSpanElement | undefined
|
|
78
80
|
|
|
79
81
|
const params = () => props.params || (props.component?.params as ModalComponentParams) || {}
|
|
80
82
|
|
|
@@ -151,11 +153,14 @@ export const ModalRenderer: Component<ModalRendererProps> = (props) => {
|
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
return (
|
|
156
|
+
<>
|
|
157
|
+
<span hidden aria-hidden="true" ref={themeAnchor} />
|
|
154
158
|
<Show when={isVisible()}>
|
|
155
159
|
<Portal>
|
|
156
160
|
{/* Backdrop */}
|
|
157
161
|
<div
|
|
158
|
-
|
|
162
|
+
ref={(element) => bindPortalTheme(element, () => themeAnchor)}
|
|
163
|
+
class="mcp-ui-root fixed inset-0 z-50 flex items-center justify-center p-4 bg-[color:var(--mcp-ui-overlay,color-mix(in_oklab,var(--color-black,#000)_50%,transparent))] backdrop-blur-sm"
|
|
159
164
|
style={{ animation: 'modal-fade-in 0.15s ease-out' }}
|
|
160
165
|
onClick={handleBackdropClick}
|
|
161
166
|
role="dialog"
|
|
@@ -165,17 +170,17 @@ export const ModalRenderer: Component<ModalRendererProps> = (props) => {
|
|
|
165
170
|
>
|
|
166
171
|
{/* Modal container */}
|
|
167
172
|
<div
|
|
168
|
-
class={`relative w-full ${sizeClass()} bg-white dark:bg-gray-800 rounded-lg shadow-xl`}
|
|
173
|
+
class={`relative w-full ${sizeClass()} bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg shadow-[var(--mcp-ui-shadow,var(--shadow-xl))]`}
|
|
169
174
|
style={{ animation: 'modal-scale-in 0.15s ease-out' }}
|
|
170
175
|
onClick={(e) => e.stopPropagation()}
|
|
171
176
|
>
|
|
172
177
|
{/* Header */}
|
|
173
178
|
<Show when={params()?.title || params()?.showClose !== false}>
|
|
174
|
-
<div class="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
|
|
179
|
+
<div class="flex items-center justify-between p-4 border-b border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))]">
|
|
175
180
|
<Show when={params()?.title}>
|
|
176
181
|
<h2
|
|
177
182
|
id="modal-title"
|
|
178
|
-
class="text-lg font-semibold text-gray-900 dark:text-white"
|
|
183
|
+
class="text-lg font-semibold text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))]"
|
|
179
184
|
>
|
|
180
185
|
{params()!.title}
|
|
181
186
|
</h2>
|
|
@@ -186,7 +191,7 @@ export const ModalRenderer: Component<ModalRendererProps> = (props) => {
|
|
|
186
191
|
<Show when={params()?.showClose !== false}>
|
|
187
192
|
<button
|
|
188
193
|
onClick={handleClose}
|
|
189
|
-
class="p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
|
194
|
+
class="p-1 text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] hover:text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:hover:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] rounded-full hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-100,#f3f4f6))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] transition-colors"
|
|
190
195
|
aria-label={strings.modalClose}
|
|
191
196
|
>
|
|
192
197
|
<svg
|
|
@@ -230,5 +235,6 @@ export const ModalRenderer: Component<ModalRendererProps> = (props) => {
|
|
|
230
235
|
`}</style>
|
|
231
236
|
</Portal>
|
|
232
237
|
</Show>
|
|
238
|
+
</>
|
|
233
239
|
)
|
|
234
240
|
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { Component, JSX, Show, createSignal, createEffect, onCleanup } from 'solid-js'
|
|
24
24
|
import { Portal } from 'solid-js/web'
|
|
25
|
+
import { bindPortalTheme } from '../utils/theme'
|
|
25
26
|
|
|
26
27
|
export interface PortalDropdownMenuProps {
|
|
27
28
|
/**
|
|
@@ -111,7 +112,10 @@ export const PortalDropdownMenu: Component<PortalDropdownMenuProps> = (props) =>
|
|
|
111
112
|
<Show when={props.open}>
|
|
112
113
|
<Portal>
|
|
113
114
|
<div
|
|
114
|
-
ref={
|
|
115
|
+
ref={(element) => {
|
|
116
|
+
menuRef = element
|
|
117
|
+
bindPortalTheme(element, () => props.trigger)
|
|
118
|
+
}}
|
|
115
119
|
role="menu"
|
|
116
120
|
data-mcp-ui-portal="menu"
|
|
117
121
|
style={{
|
|
@@ -121,7 +125,7 @@ export const PortalDropdownMenu: Component<PortalDropdownMenuProps> = (props) =>
|
|
|
121
125
|
'z-index': 9999,
|
|
122
126
|
width: `${props.width ?? 144}px`,
|
|
123
127
|
}}
|
|
124
|
-
class={`bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1 text-sm ${props.class ?? ''}`}
|
|
128
|
+
class={`mcp-ui-root bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] rounded-lg shadow-[var(--mcp-ui-shadow,var(--shadow-lg))] py-1 text-sm ${props.class ?? ''}`}
|
|
125
129
|
>
|
|
126
130
|
{props.children}
|
|
127
131
|
</div>
|