@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
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
useMCPUIStrings,
|
|
29
29
|
} from '../context/MCPUIStringsContext'
|
|
30
30
|
import { useMCPUIConfig } from '../context/MCPUIConfigContext'
|
|
31
|
+
import { isMissingCellValue, stripUndefinedDebris } from '../utils/missing-value'
|
|
32
|
+
import { MissingValueLegend, MissingValueMark } from './MissingValue'
|
|
31
33
|
import { shouldSetCredentialless } from '../utils/iframe-coep'
|
|
32
34
|
import { IframeFallbackLink } from './IframeFallbackLink'
|
|
33
35
|
|
|
@@ -96,7 +98,7 @@ function CopyButton(props: { getText: () => string; title?: string; position?: '
|
|
|
96
98
|
return (
|
|
97
99
|
<button
|
|
98
100
|
onClick={handleCopy}
|
|
99
|
-
class={`${positionClasses()} opacity-60 hover:opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm z-10`}
|
|
101
|
+
class={`${positionClasses()} opacity-60 hover:opacity-100 px-2 py-1 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-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-all shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] z-10`}
|
|
100
102
|
title={props.title || strings.copy}
|
|
101
103
|
aria-label={props.title || strings.copy}
|
|
102
104
|
data-mcp-ui-action="copy"
|
|
@@ -105,12 +107,12 @@ function CopyButton(props: { getText: () => string; title?: string; position?: '
|
|
|
105
107
|
<Show
|
|
106
108
|
when={!copied()}
|
|
107
109
|
fallback={
|
|
108
|
-
<svg class="w-3 h-3 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
110
|
+
<svg class="w-3 h-3 text-[color:var(--mcp-ui-success,var(--color-green-500))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
109
111
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
110
112
|
</svg>
|
|
111
113
|
}
|
|
112
114
|
>
|
|
113
|
-
<svg class="w-3 h-3 text-gray-500 dark:text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
115
|
+
<svg class="w-3 h-3 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))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
114
116
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
|
115
117
|
</svg>
|
|
116
118
|
</Show>
|
|
@@ -257,8 +259,8 @@ function ChartRenderer(props: {
|
|
|
257
259
|
// Guard: if data or datasets missing, show error instead of crashing Chart.js
|
|
258
260
|
if (!params()?.data?.datasets) {
|
|
259
261
|
return (
|
|
260
|
-
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
|
261
|
-
<p class="text-red-500 dark:text-red-400 text-sm">{strings.chartInvalidData}</p>
|
|
262
|
+
<div class="p-4 bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))]">
|
|
263
|
+
<p class="text-[color:var(--mcp-ui-danger,var(--color-red-500))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-400))] text-sm">{strings.chartInvalidData}</p>
|
|
262
264
|
</div>
|
|
263
265
|
)
|
|
264
266
|
}
|
|
@@ -336,7 +338,7 @@ function ChartRenderer(props: {
|
|
|
336
338
|
<Show
|
|
337
339
|
when={useNative()}
|
|
338
340
|
fallback={
|
|
339
|
-
<div class="relative w-full h-full min-h-[300px] bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
341
|
+
<div class="relative w-full h-full min-h-[300px] 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">
|
|
340
342
|
{/* P1.7 — Chart.js missing and quickchart fallback not allowed:
|
|
341
343
|
show the chart data as a local table instead of an implicit
|
|
342
344
|
call to quickchart.io. */}
|
|
@@ -347,6 +349,7 @@ function ChartRenderer(props: {
|
|
|
347
349
|
caption={strings.chartQuickchartCaption}
|
|
348
350
|
{...chartToDegradedTable(params() ?? {}, {
|
|
349
351
|
series: strings.degradedSeries,
|
|
352
|
+
withUnit: strings.chartLabelWithUnit,
|
|
350
353
|
})}
|
|
351
354
|
/>
|
|
352
355
|
</div>
|
|
@@ -354,15 +357,15 @@ function ChartRenderer(props: {
|
|
|
354
357
|
|
|
355
358
|
<Show when={isLoading()}>
|
|
356
359
|
<div class="absolute inset-0 flex items-center justify-center">
|
|
357
|
-
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" />
|
|
360
|
+
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-[color:var(--mcp-ui-accent-border,var(--color-blue-600))]" />
|
|
358
361
|
</div>
|
|
359
362
|
</Show>
|
|
360
363
|
|
|
361
364
|
<Show when={error()}>
|
|
362
365
|
<div class="absolute inset-0 flex items-center justify-center p-4">
|
|
363
366
|
<div class="text-center">
|
|
364
|
-
<p class="text-red-600 dark:text-red-400 text-sm font-medium">{strings.chartError}</p>
|
|
365
|
-
<p class="text-gray-600 dark:text-gray-400 text-xs mt-1">{error()}</p>
|
|
367
|
+
<p class="text-[color:var(--mcp-ui-danger,var(--color-red-600))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-400))] text-sm font-medium">{strings.chartError}</p>
|
|
368
|
+
<p class="text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] text-xs mt-1">{error()}</p>
|
|
366
369
|
</div>
|
|
367
370
|
</div>
|
|
368
371
|
</Show>
|
|
@@ -370,7 +373,7 @@ function ChartRenderer(props: {
|
|
|
370
373
|
<Show when={iframeUrl() && !error()}>
|
|
371
374
|
<div class="w-full h-full p-4">
|
|
372
375
|
<Show when={params()?.title}>
|
|
373
|
-
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-3">
|
|
376
|
+
<h3 class="text-sm font-semibold text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] mb-3">
|
|
374
377
|
{params()?.title}
|
|
375
378
|
</h3>
|
|
376
379
|
</Show>
|
|
@@ -411,7 +414,7 @@ function ChartRenderer(props: {
|
|
|
411
414
|
* Smart cell value renderer that handles markdown links and other formats
|
|
412
415
|
*/
|
|
413
416
|
/** Class list carried by every `<mark>` that `highlightQuery` injects. */
|
|
414
|
-
const HIGHLIGHT_MARK_CLASS = 'bg-yellow-200 dark:bg-[
|
|
417
|
+
const HIGHLIGHT_MARK_CLASS = 'bg-[color:var(--mcp-ui-highlight,var(--color-yellow-200))] dark:bg-[color:var(--mcp-ui-highlight,#222F49)] text-inherit rounded px-0.5'
|
|
415
418
|
|
|
416
419
|
/**
|
|
417
420
|
* Wrap matches of `query` in `<mark>` tags within an HTML string.
|
|
@@ -550,12 +553,12 @@ function defaultCitationChip(
|
|
|
550
553
|
const safeDocName = encodeURIComponent(fileName || '')
|
|
551
554
|
const label = fileName ? `${fileName} - ${pageNum}` : `${pageNum}`
|
|
552
555
|
if (!verified) {
|
|
553
|
-
return `<span class="citation-ref inline-flex items-center gap-0.5 align-middle opacity-60"><span class="text-gray-500 line-through">[${label}]</span></span>`
|
|
556
|
+
return `<span class="citation-ref inline-flex items-center gap-0.5 align-middle opacity-60"><span class="text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] line-through">[${label}]</span></span>`
|
|
554
557
|
}
|
|
555
558
|
return [
|
|
556
559
|
'<span class="citation-ref inline-flex items-center gap-0.5 align-middle">',
|
|
557
|
-
`<span class="text-gray-500">[${label}]</span>`,
|
|
558
|
-
'<button class="inline-flex items-center ml-0.5 px-1 py-0.5 text-xs bg-gray-800 hover:bg-gray-700 border border-gray-600 hover:border-teal-500 rounded cursor-pointer transition-colors align-middle"',
|
|
560
|
+
`<span class="text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))]">[${label}]</span>`,
|
|
561
|
+
'<button class="inline-flex items-center ml-0.5 px-1 py-0.5 text-xs bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] border border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] hover:border-[color:var(--mcp-ui-info-border,var(--color-teal-500))] rounded cursor-pointer transition-colors align-middle"',
|
|
559
562
|
` data-citation-page="${pageNum}"`,
|
|
560
563
|
` data-citation-doc="${safeDocName}"`,
|
|
561
564
|
' data-citation-verified="true"',
|
|
@@ -613,7 +616,7 @@ export function renderCellValue(value: any, citationCtx?: CitationCtx, mathRende
|
|
|
613
616
|
const label = value.name || value.label || value.title || value.url
|
|
614
617
|
const sanitizedLabel = sanitizeHtml(String(label))
|
|
615
618
|
const sanitizedUrl = sanitizeHtml(String(value.url))
|
|
616
|
-
const anchor = `<a href="${sanitizedUrl}" target="_blank" rel="noopener noreferrer" class="text-blue-600 dark:text-blue-400 hover:underline">${sanitizedLabel}</a>`
|
|
619
|
+
const anchor = `<a href="${sanitizedUrl}" 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">${sanitizedLabel}</a>`
|
|
617
620
|
// Re-sanitize the composed anchor: sanitizing the bare `url` string
|
|
618
621
|
// leaves quotes and `javascript:` untouched, so a crafted value could
|
|
619
622
|
// otherwise break out of the attribute or smuggle a scriptable href.
|
|
@@ -639,9 +642,10 @@ export function renderCellValue(value: any, citationCtx?: CitationCtx, mathRende
|
|
|
639
642
|
|
|
640
643
|
// Clean up "undefined" patterns from backend data
|
|
641
644
|
// Pattern 1: "Text – undefined" or "Text - undefined" → "Text"
|
|
642
|
-
strValue = strValue.replace(/\s*[–-]\s*undefined\s*$/gi, '')
|
|
643
645
|
// Pattern 2: "undefined – Text" or "undefined - Text" → "Text"
|
|
644
|
-
|
|
646
|
+
// (shared with `isMissingCellValue`, so the cells shown as `-` and the cells
|
|
647
|
+
// the table marks and explains as missing can never drift apart)
|
|
648
|
+
strValue = stripUndefinedDebris(strValue)
|
|
645
649
|
// Pattern 3: standalone "undefined" → "-"
|
|
646
650
|
if (strValue.trim().toLowerCase() === 'undefined') {
|
|
647
651
|
return '-'
|
|
@@ -671,7 +675,7 @@ export function renderCellValue(value: any, citationCtx?: CitationCtx, mathRende
|
|
|
671
675
|
// Replace all markdown links with HTML links
|
|
672
676
|
const htmlValue = strValue.replace(
|
|
673
677
|
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
674
|
-
'<a href="$2" target="_blank" rel="noopener noreferrer" class="text-blue-600 dark:text-blue-400 hover:underline">$1</a>'
|
|
678
|
+
'<a href="$2" 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">$1</a>'
|
|
675
679
|
)
|
|
676
680
|
return sanitizeHtml(htmlValue, SANITIZE_PROFILES.cellLink)
|
|
677
681
|
}
|
|
@@ -766,12 +770,18 @@ function TableRenderer(props: {
|
|
|
766
770
|
const dir = sortDir()
|
|
767
771
|
if (!key || !dir) return r
|
|
768
772
|
const col = columns().find((c: any) => c.key === key)
|
|
769
|
-
|
|
773
|
+
// v6.24.0 — numeric mode is decided from the first NON-missing value, so a
|
|
774
|
+
// missing first row no longer flips a numeric column to string order.
|
|
775
|
+
const firstObserved = r.find((row: any) => !isMissingCellValue(row?.[key]))?.[key]
|
|
776
|
+
const isNum = col?.type === 'number' || typeof firstObserved === 'number'
|
|
770
777
|
return [...r].sort((a: any, b: any) => {
|
|
771
778
|
const va = a[key], vb = b[key]
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
779
|
+
// Missing values (null, undefined, '', 'undefined', '-') sort last in
|
|
780
|
+
// both directions — never as 0 and never first.
|
|
781
|
+
const missA = isMissingCellValue(va), missB = isMissingCellValue(vb)
|
|
782
|
+
if (missA && missB) return 0
|
|
783
|
+
if (missA) return 1
|
|
784
|
+
if (missB) return -1
|
|
775
785
|
let cmp: number
|
|
776
786
|
if (isNum) {
|
|
777
787
|
cmp = (Number(va) || 0) - (Number(vb) || 0)
|
|
@@ -820,7 +830,7 @@ function TableRenderer(props: {
|
|
|
820
830
|
return sortedRows().filter((row: any) =>
|
|
821
831
|
cols.some((col: any) => {
|
|
822
832
|
const val = row[col.key]
|
|
823
|
-
if (val
|
|
833
|
+
if (isMissingCellValue(val)) return false
|
|
824
834
|
return normalize(String(val)).includes(q)
|
|
825
835
|
})
|
|
826
836
|
)
|
|
@@ -1015,16 +1025,29 @@ function TableRenderer(props: {
|
|
|
1015
1025
|
|
|
1016
1026
|
const tableId = `table-${Math.random().toString(36).slice(2, 9)}`
|
|
1017
1027
|
|
|
1028
|
+
// v6.24.0 — whether ANY declared cell of ANY row is missing. Deliberately
|
|
1029
|
+
// computed on all rows (not the current page / filter) so the legend does not
|
|
1030
|
+
// flicker while paging, sorting or searching.
|
|
1031
|
+
const hasMissingCells = createMemo(() => {
|
|
1032
|
+
const cols = columns()
|
|
1033
|
+
return allRows().some((row: any) => cols.some((col: any) => isMissingCellValue(row?.[col.key])))
|
|
1034
|
+
})
|
|
1035
|
+
|
|
1018
1036
|
// Standard table body (non-virtualized) — uses client pagination when active
|
|
1019
1037
|
const StandardTableBody = () => (
|
|
1020
|
-
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
1038
|
+
<tbody class="bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] divide-y divide-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:divide-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))]">
|
|
1021
1039
|
<For each={clientVisibleRows().slice(0, DEFAULT_RESOURCE_LIMITS.maxTableRows)}>
|
|
1022
1040
|
{(row: any, i) => (
|
|
1023
|
-
<tr class={`hover:bg-gray-50 dark:hover:bg-gray-700
|
|
1041
|
+
<tr class={`hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-50,#f9fafb))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,color-mix(in_oklab,var(--color-gray-700,#374151)_50%,transparent))] transition-colors ${i() % 2 === 0 ? 'bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))]' : 'bg-[color:var(--mcp-ui-surface-muted,color-mix(in_oklab,var(--color-gray-50,#f9fafb)_30%,transparent))] dark:bg-[color:var(--mcp-ui-surface,color-mix(in_oklab,var(--color-gray-800,#1f2937)_50%,transparent))]'}`}>
|
|
1024
1042
|
<For each={tableParams.columns}>
|
|
1025
1043
|
{(column: any) => (
|
|
1026
|
-
<td class="px-6 py-4 text-sm text-gray-700 dark:text-gray-200 whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
|
|
1027
|
-
<
|
|
1044
|
+
<td class="px-6 py-4 text-sm text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-200,#e5e7eb))] whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
|
|
1045
|
+
<Show
|
|
1046
|
+
when={!isMissingCellValue(row[column.key])}
|
|
1047
|
+
fallback={<MissingValueMark />}
|
|
1048
|
+
>
|
|
1049
|
+
<SafeHtml html={() => highlightQuery(renderCellValue(row[column.key], citationCtx, mathRenderer()), debouncedQuery())} />
|
|
1050
|
+
</Show>
|
|
1028
1051
|
</td>
|
|
1029
1052
|
)}
|
|
1030
1053
|
</For>
|
|
@@ -1046,7 +1069,7 @@ function TableRenderer(props: {
|
|
|
1046
1069
|
|
|
1047
1070
|
return (
|
|
1048
1071
|
<tbody
|
|
1049
|
-
class="bg-white dark:bg-gray-800 relative"
|
|
1072
|
+
class="bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] relative"
|
|
1050
1073
|
style={{ height: `${totalSize}px` }}
|
|
1051
1074
|
>
|
|
1052
1075
|
<For each={items}>
|
|
@@ -1054,7 +1077,7 @@ function TableRenderer(props: {
|
|
|
1054
1077
|
const row = tableParams.rows[virtualRow.index]
|
|
1055
1078
|
return (
|
|
1056
1079
|
<tr
|
|
1057
|
-
class={`hover:bg-gray-50 dark:hover:bg-gray-700
|
|
1080
|
+
class={`hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-50,#f9fafb))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,color-mix(in_oklab,var(--color-gray-700,#374151)_50%,transparent))] transition-colors absolute left-0 right-0 ${virtualRow.index % 2 === 0 ? 'bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))]' : 'bg-[color:var(--mcp-ui-surface-muted,color-mix(in_oklab,var(--color-gray-50,#f9fafb)_30%,transparent))] dark:bg-[color:var(--mcp-ui-surface,color-mix(in_oklab,var(--color-gray-800,#1f2937)_50%,transparent))]'}`}
|
|
1058
1081
|
style={{
|
|
1059
1082
|
height: `${rowHeight}px`,
|
|
1060
1083
|
transform: `translateY(${virtualRow.start}px)`
|
|
@@ -1062,8 +1085,13 @@ function TableRenderer(props: {
|
|
|
1062
1085
|
>
|
|
1063
1086
|
<For each={tableParams.columns}>
|
|
1064
1087
|
{(column: any) => (
|
|
1065
|
-
<td class="px-6 py-4 text-sm text-gray-700 dark:text-gray-200 whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
|
|
1066
|
-
<
|
|
1088
|
+
<td class="px-6 py-4 text-sm text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-200,#e5e7eb))] whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
|
|
1089
|
+
<Show
|
|
1090
|
+
when={!isMissingCellValue(row[column.key])}
|
|
1091
|
+
fallback={<MissingValueMark />}
|
|
1092
|
+
>
|
|
1093
|
+
<SafeHtml html={() => highlightQuery(renderCellValue(row[column.key], citationCtx, mathRenderer()), debouncedQuery())} />
|
|
1094
|
+
</Show>
|
|
1067
1095
|
</td>
|
|
1068
1096
|
)}
|
|
1069
1097
|
</For>
|
|
@@ -1077,7 +1105,7 @@ function TableRenderer(props: {
|
|
|
1077
1105
|
|
|
1078
1106
|
return (
|
|
1079
1107
|
<ExpandableWrapper title={tableParams.title || strings.tableTitle} copyData={getTableCSV()} copyLabel={strings.tableCopyCsv} toolbarVariant={props.toolbarVariant} onExpandedChange={setSelfExpanded}>
|
|
1080
|
-
<div class={`relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden group ${
|
|
1108
|
+
<div class={`relative w-full 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 group ${
|
|
1081
1109
|
isExpanded() ? 'flex-1 min-h-0 flex flex-col' : 'h-full'
|
|
1082
1110
|
} ${tableParams.className || ''}`}>
|
|
1083
1111
|
<Show when={exportable} fallback={<CopyButton getText={getTableCSV} title={strings.tableCopyCsv} position="top-right" />}>
|
|
@@ -1085,13 +1113,13 @@ function TableRenderer(props: {
|
|
|
1085
1113
|
<button
|
|
1086
1114
|
ref={exportTriggerRef}
|
|
1087
1115
|
onClick={() => setShowExportMenu(!showExportMenu())}
|
|
1088
|
-
class="opacity-60 hover:opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"
|
|
1116
|
+
class="opacity-60 hover:opacity-100 px-2 py-1 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-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-all shadow-[var(--mcp-ui-shadow,var(--shadow-sm))]"
|
|
1089
1117
|
title={strings.tableExport}
|
|
1090
1118
|
aria-label={strings.tableExport}
|
|
1091
1119
|
aria-haspopup="menu"
|
|
1092
1120
|
aria-expanded={showExportMenu()}
|
|
1093
1121
|
>
|
|
1094
|
-
<svg class="w-3 h-3 text-gray-500 dark:text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1122
|
+
<svg class="w-3 h-3 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))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1095
1123
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
1096
1124
|
</svg>
|
|
1097
1125
|
</button>
|
|
@@ -1102,23 +1130,23 @@ function TableRenderer(props: {
|
|
|
1102
1130
|
width={144}
|
|
1103
1131
|
>
|
|
1104
1132
|
<Show when={(exportFormats as string[]).includes('tsv')}>
|
|
1105
|
-
<button onClick={() => handleExport('tsv')} class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">{strings.tableCopyTsv}</button>
|
|
1133
|
+
<button onClick={() => handleExport('tsv')} class="w-full text-left px-3 py-1.5 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))]">{strings.tableCopyTsv}</button>
|
|
1106
1134
|
</Show>
|
|
1107
1135
|
<Show when={(exportFormats as string[]).includes('csv')}>
|
|
1108
|
-
<button onClick={() => handleExport('csv')} class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">{strings.tableDownloadCsv}</button>
|
|
1136
|
+
<button onClick={() => handleExport('csv')} class="w-full text-left px-3 py-1.5 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))]">{strings.tableDownloadCsv}</button>
|
|
1109
1137
|
</Show>
|
|
1110
1138
|
<Show when={(exportFormats as string[]).includes('json')}>
|
|
1111
|
-
<button onClick={() => handleExport('json')} class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">{strings.tableDownloadJson}</button>
|
|
1139
|
+
<button onClick={() => handleExport('json')} class="w-full text-left px-3 py-1.5 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))]">{strings.tableDownloadJson}</button>
|
|
1112
1140
|
</Show>
|
|
1113
1141
|
</PortalDropdownMenu>
|
|
1114
1142
|
</div>
|
|
1115
1143
|
</Show>
|
|
1116
1144
|
<div class={`p-4 ${isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''}`}>
|
|
1117
1145
|
<Show when={tableParams.title}>
|
|
1118
|
-
<h3 id={`${tableId}-title`} class="text-sm font-semibold text-gray-900 dark:text-white mb-3 flex-shrink-0">
|
|
1146
|
+
<h3 id={`${tableId}-title`} class="text-sm font-semibold text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] mb-3 flex-shrink-0">
|
|
1119
1147
|
{tableParams.title}
|
|
1120
1148
|
<Show when={isVirtualizing()}>
|
|
1121
|
-
<span class="ml-2 text-xs font-normal text-gray-400">
|
|
1149
|
+
<span class="ml-2 text-xs font-normal text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]">
|
|
1122
1150
|
{formatMCPUIString(strings.tableVirtualizedRows, { count: tableParams.rows?.length ?? 0 })}
|
|
1123
1151
|
</span>
|
|
1124
1152
|
</Show>
|
|
@@ -1128,25 +1156,25 @@ function TableRenderer(props: {
|
|
|
1128
1156
|
{/* Search input (v4.3.3) */}
|
|
1129
1157
|
<Show when={isSearchable()}>
|
|
1130
1158
|
<div class="relative mb-3">
|
|
1131
|
-
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none text-sm">{'\uD83D\uDD0D'}</span>
|
|
1159
|
+
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] pointer-events-none text-sm">{'\uD83D\uDD0D'}</span>
|
|
1132
1160
|
<input
|
|
1133
1161
|
type="text"
|
|
1134
1162
|
value={searchQuery()}
|
|
1135
1163
|
onInput={(e) => handleSearch(e.currentTarget.value)}
|
|
1136
1164
|
placeholder={searchPlaceholder()}
|
|
1137
|
-
class="w-full max-w-xs min-w-[200px] pl-8 pr-8 py-1.5 text-sm border border-gray-200 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none"
|
|
1165
|
+
class="w-full max-w-xs min-w-[200px] pl-8 pr-8 py-1.5 text-sm 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-md bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-700,#374151))] text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] placeholder-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] focus:border-[color:var(--mcp-ui-focus,var(--color-blue-400))] focus:ring-1 focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-400))] outline-none"
|
|
1138
1166
|
/>
|
|
1139
1167
|
<Show when={searchQuery()}>
|
|
1140
1168
|
<button
|
|
1141
1169
|
type="button"
|
|
1142
1170
|
onClick={() => { handleSearch(''); setSearchQuery(''); setDebouncedQuery('') }}
|
|
1143
|
-
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 text-sm"
|
|
1171
|
+
class="absolute right-2 top-1/2 -translate-y-1/2 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))] text-sm"
|
|
1144
1172
|
aria-label={strings.tableClearSearch}
|
|
1145
1173
|
>×</button>
|
|
1146
1174
|
</Show>
|
|
1147
1175
|
</div>
|
|
1148
1176
|
<Show when={debouncedQuery() && filteredRows().length !== sortedRows().length}>
|
|
1149
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
1177
|
+
<p 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))] mb-2">
|
|
1150
1178
|
{formatMCPUIString(
|
|
1151
1179
|
filteredRows().length === 1 ? strings.tableSearchResultsOne : strings.tableSearchResultsMany,
|
|
1152
1180
|
{ count: filteredRows().length, total: sortedRows().length }
|
|
@@ -1185,16 +1213,16 @@ function TableRenderer(props: {
|
|
|
1185
1213
|
tabindex="0"
|
|
1186
1214
|
>
|
|
1187
1215
|
<table
|
|
1188
|
-
class="min-w-full divide-y divide-gray-200 dark:divide-gray-700 border-separate border-spacing-0"
|
|
1216
|
+
class="min-w-full divide-y divide-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:divide-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] border-separate border-spacing-0"
|
|
1189
1217
|
aria-labelledby={tableParams.title ? `${tableId}-title` : undefined}
|
|
1190
1218
|
>
|
|
1191
|
-
<thead class="bg-gray-100 dark:bg-gray-900 sticky top-0 z-10">
|
|
1219
|
+
<thead class="bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-100,#f3f4f6))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-900,#111827))] sticky top-0 z-10">
|
|
1192
1220
|
<tr>
|
|
1193
1221
|
<For each={tableParams.columns}>
|
|
1194
1222
|
{(column: any) => (
|
|
1195
1223
|
<th
|
|
1196
1224
|
scope="col"
|
|
1197
|
-
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider border-b border-gray-200 dark:border-gray-700 first:pl-6 last:pr-6 bg-gray-100 dark:bg-gray-900 cursor-pointer select-none hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors"
|
|
1225
|
+
class="px-6 py-3 text-left text-xs font-semibold 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))] uppercase tracking-wider 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))] first:pl-6 last:pr-6 bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-100,#f3f4f6))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-900,#111827))] cursor-pointer select-none hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-200,#e5e7eb))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-800,#1f2937))] transition-colors"
|
|
1198
1226
|
style={column.width ? { width: column.width } : {}}
|
|
1199
1227
|
on:click={() => handleSort(column.key)}
|
|
1200
1228
|
title={formatMCPUIString(strings.sortBy, { column: column.label })}
|
|
@@ -1205,7 +1233,7 @@ function TableRenderer(props: {
|
|
|
1205
1233
|
class="text-[10px] leading-none"
|
|
1206
1234
|
classList={{
|
|
1207
1235
|
'opacity-30': sortKey() !== column.key,
|
|
1208
|
-
'opacity-100 text-blue-600 dark:text-blue-400': sortKey() === column.key,
|
|
1236
|
+
'opacity-100 text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))]': sortKey() === column.key,
|
|
1209
1237
|
}}
|
|
1210
1238
|
>
|
|
1211
1239
|
{sortIndicator(column.key)}
|
|
@@ -1222,9 +1250,14 @@ function TableRenderer(props: {
|
|
|
1222
1250
|
</table>
|
|
1223
1251
|
</div>
|
|
1224
1252
|
|
|
1253
|
+
{/* Missing-value legend (v6.24.0) — outside the scroll region, before pagination */}
|
|
1254
|
+
<Show when={hasMissingCells()}>
|
|
1255
|
+
<MissingValueLegend class="mt-2 px-1" />
|
|
1256
|
+
</Show>
|
|
1257
|
+
|
|
1225
1258
|
{/* Server-side pagination (legacy) */}
|
|
1226
1259
|
<Show when={tableParams.pagination}>
|
|
1227
|
-
<div class="mt-3 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
|
|
1260
|
+
<div class="mt-3 flex items-center justify-between 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))]">
|
|
1228
1261
|
<span>
|
|
1229
1262
|
{formatMCPUIString(strings.tableServerPageRange, {
|
|
1230
1263
|
start: tableParams.pagination.currentPage * tableParams.pagination.pageSize + 1,
|
|
@@ -1240,14 +1273,14 @@ function TableRenderer(props: {
|
|
|
1240
1273
|
|
|
1241
1274
|
{/* Client-side pagination (v4.3.7) */}
|
|
1242
1275
|
<Show when={needsClientPagination()}>
|
|
1243
|
-
<div class="mt-3 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
|
|
1276
|
+
<div class="mt-3 flex items-center justify-between 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))]">
|
|
1244
1277
|
<span>
|
|
1245
1278
|
{clientRangeStart()}–{clientRangeEnd()} / {filteredRows().length.toLocaleString(strings.locale)}
|
|
1246
1279
|
</span>
|
|
1247
1280
|
<div class="flex items-center gap-2">
|
|
1248
1281
|
<button
|
|
1249
1282
|
type="button"
|
|
1250
|
-
class="px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
1283
|
+
class="px-2 py-1 rounded 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))] disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
1251
1284
|
disabled={clientPage() === 0}
|
|
1252
1285
|
onClick={() => setClientPage(p => p - 1)}
|
|
1253
1286
|
aria-label={strings.paginationPrevious}
|
|
@@ -1258,7 +1291,7 @@ function TableRenderer(props: {
|
|
|
1258
1291
|
<span aria-live="polite">{clientPage() + 1} / {clientTotalPages()}</span>
|
|
1259
1292
|
<button
|
|
1260
1293
|
type="button"
|
|
1261
|
-
class="px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
1294
|
+
class="px-2 py-1 rounded 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))] disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
1262
1295
|
disabled={clientPage() >= clientTotalPages() - 1}
|
|
1263
1296
|
onClick={() => setClientPage(p => p + 1)}
|
|
1264
1297
|
aria-label={strings.paginationNext}
|
|
@@ -1269,7 +1302,7 @@ function TableRenderer(props: {
|
|
|
1269
1302
|
{/* Page size selector — fullscreen only */}
|
|
1270
1303
|
<Show when={isExpanded() && filteredRows().length > 10}>
|
|
1271
1304
|
<select
|
|
1272
|
-
class="ml-2 px-1 py-0.5 text-xs border border-gray-200 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300"
|
|
1305
|
+
class="ml-2 px-1 py-0.5 text-xs 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 bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface-muted,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))]"
|
|
1273
1306
|
value={clientPageSize()}
|
|
1274
1307
|
onChange={(e) => handlePageSizeChange(Number(e.currentTarget.value))}
|
|
1275
1308
|
aria-label={strings.paginationPageSize}
|
|
@@ -1279,7 +1312,7 @@ function TableRenderer(props: {
|
|
|
1279
1312
|
{(opt) => <option value={opt.value}>{opt.label}</option>}
|
|
1280
1313
|
</For>
|
|
1281
1314
|
</select>
|
|
1282
|
-
<span class="text-gray-400">{strings.perPageSuffix}</span>
|
|
1315
|
+
<span class="text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]">{strings.perPageSuffix}</span>
|
|
1283
1316
|
</Show>
|
|
1284
1317
|
</div>
|
|
1285
1318
|
</div>
|
|
@@ -1306,17 +1339,17 @@ function MetricRenderer(props: { component: UIComponent }) {
|
|
|
1306
1339
|
}
|
|
1307
1340
|
|
|
1308
1341
|
return (
|
|
1309
|
-
<div class="relative w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 group">
|
|
1342
|
+
<div class="relative w-full h-full 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))] p-4 group">
|
|
1310
1343
|
<CopyButton getText={getMetricText} title={strings.copyMetric} position="top-right" />
|
|
1311
1344
|
<div class="flex flex-col h-full justify-between">
|
|
1312
1345
|
<div>
|
|
1313
|
-
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">
|
|
1346
|
+
<p class="text-xs font-medium 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))] uppercase tracking-wide">
|
|
1314
1347
|
{metricParams.title}
|
|
1315
1348
|
</p>
|
|
1316
1349
|
<div class="mt-2 flex items-baseline">
|
|
1317
|
-
<p class="text-2xl font-semibold text-gray-900 dark:text-white">{metricParams.value}</p>
|
|
1350
|
+
<p class="text-2xl font-semibold text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))]">{metricParams.value}</p>
|
|
1318
1351
|
<Show when={metricParams.unit}>
|
|
1319
|
-
<span class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400">
|
|
1352
|
+
<span class="ml-2 text-sm font-medium 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))]">
|
|
1320
1353
|
{metricParams.unit}
|
|
1321
1354
|
</span>
|
|
1322
1355
|
</Show>
|
|
@@ -1327,10 +1360,10 @@ function MetricRenderer(props: { component: UIComponent }) {
|
|
|
1327
1360
|
<div class="mt-3 flex items-center">
|
|
1328
1361
|
<span
|
|
1329
1362
|
class={`text-sm font-medium ${metricParams.trend.direction === 'up'
|
|
1330
|
-
? 'text-green-600 dark:text-green-400'
|
|
1363
|
+
? 'text-[color:var(--mcp-ui-success,var(--color-green-600))] dark:text-[color:var(--mcp-ui-success,var(--color-green-400))]'
|
|
1331
1364
|
: metricParams.trend.direction === 'down'
|
|
1332
|
-
? 'text-red-600 dark:text-red-400'
|
|
1333
|
-
: 'text-gray-600 dark:text-gray-400'
|
|
1365
|
+
? 'text-[color:var(--mcp-ui-danger,var(--color-red-600))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-400))]'
|
|
1366
|
+
: 'text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]'
|
|
1334
1367
|
}`}
|
|
1335
1368
|
>
|
|
1336
1369
|
{metricParams.trend.direction === 'up'
|
|
@@ -1344,7 +1377,7 @@ function MetricRenderer(props: { component: UIComponent }) {
|
|
|
1344
1377
|
</Show>
|
|
1345
1378
|
|
|
1346
1379
|
<Show when={metricParams.subtitle}>
|
|
1347
|
-
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">{metricParams.subtitle}</p>
|
|
1380
|
+
<p class="mt-2 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))]">{metricParams.subtitle}</p>
|
|
1348
1381
|
</Show>
|
|
1349
1382
|
</div>
|
|
1350
1383
|
</div>
|
|
@@ -1435,7 +1468,7 @@ function TextRenderer(props: { component: UIComponent }) {
|
|
|
1435
1468
|
<Show
|
|
1436
1469
|
when={imageData()}
|
|
1437
1470
|
fallback={
|
|
1438
|
-
<div class="relative w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 group">
|
|
1471
|
+
<div class="relative w-full h-full 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))] p-4 group">
|
|
1439
1472
|
<CopyButton getText={getTextContent} title={strings.copyText} position="top-right" />
|
|
1440
1473
|
<SafeHtml
|
|
1441
1474
|
class={`prose prose-sm dark:prose-invert max-w-none ${textParams.className || ''}`}
|
|
@@ -1450,13 +1483,13 @@ function TextRenderer(props: { component: UIComponent }) {
|
|
|
1450
1483
|
<img
|
|
1451
1484
|
src={data().imageUrl}
|
|
1452
1485
|
alt={data().alt}
|
|
1453
|
-
class="max-w-full max-h-[400px] object-contain rounded shadow-sm hover:opacity-90 transition-opacity"
|
|
1486
|
+
class="max-w-full max-h-[400px] object-contain rounded shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] hover:opacity-90 transition-opacity"
|
|
1454
1487
|
loading="lazy"
|
|
1455
1488
|
/>
|
|
1456
1489
|
)
|
|
1457
1490
|
return (
|
|
1458
|
-
<div class="w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col">
|
|
1459
|
-
<div class="flex-1 flex items-center justify-center p-4 bg-gray-50 dark:bg-gray-900 min-h-[200px]">
|
|
1491
|
+
<div class="w-full h-full 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 flex flex-col">
|
|
1492
|
+
<div class="flex-1 flex items-center justify-center p-4 bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-50,#f9fafb))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-900,#111827))] min-h-[200px]">
|
|
1460
1493
|
{/* The anchor only exists when the link URL passed `safeUrl` — a
|
|
1461
1494
|
`javascript:` link degrades to a plain, unlinked image. */}
|
|
1462
1495
|
<Show when={data().linkUrl} fallback={image()}>
|
|
@@ -1467,8 +1500,8 @@ function TextRenderer(props: { component: UIComponent }) {
|
|
|
1467
1500
|
)}
|
|
1468
1501
|
</Show>
|
|
1469
1502
|
</div>
|
|
1470
|
-
<div class="p-3 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
|
1471
|
-
<p class="text-sm text-gray-600 dark:text-gray-400 text-center italic">{data().credit}</p>
|
|
1503
|
+
<div class="p-3 border-t border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))]">
|
|
1504
|
+
<p class="text-sm text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] text-center italic">{data().credit}</p>
|
|
1472
1505
|
</div>
|
|
1473
1506
|
</div>
|
|
1474
1507
|
)
|
|
@@ -1489,10 +1522,10 @@ function IframeRenderer(props: { component: UIComponent }) {
|
|
|
1489
1522
|
// the `credentialless` decision.
|
|
1490
1523
|
const trustOptions = () => ({ customTrustedDomains: config.customTrustedIframeDomains })
|
|
1491
1524
|
return (
|
|
1492
|
-
<div class="w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col">
|
|
1525
|
+
<div class="w-full h-full 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 flex flex-col">
|
|
1493
1526
|
<Show when={params.title}>
|
|
1494
|
-
<div class="px-4 py-2 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
|
|
1495
|
-
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">{params.title}</h3>
|
|
1527
|
+
<div class="px-4 py-2 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))] bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-50,#f9fafb))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-900,#111827))]">
|
|
1528
|
+
<h3 class="text-sm font-semibold text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))]">{params.title}</h3>
|
|
1496
1529
|
</div>
|
|
1497
1530
|
</Show>
|
|
1498
1531
|
<iframe
|
|
@@ -1515,7 +1548,7 @@ function IframeRenderer(props: { component: UIComponent }) {
|
|
|
1515
1548
|
}
|
|
1516
1549
|
loading="lazy"
|
|
1517
1550
|
/>
|
|
1518
|
-
<IframeFallbackLink url={params.url} class="border-t border-gray-200 dark:border-gray-700" />
|
|
1551
|
+
<IframeFallbackLink url={params.url} class="border-t border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))]" />
|
|
1519
1552
|
</div>
|
|
1520
1553
|
)
|
|
1521
1554
|
}
|
|
@@ -1528,13 +1561,13 @@ function ImageRenderer(props: { component: UIComponent }) {
|
|
|
1528
1561
|
const params = props.component.params as any
|
|
1529
1562
|
|
|
1530
1563
|
return (
|
|
1531
|
-
<figure class={`w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col ${params.className || ''}`}>
|
|
1532
|
-
<div class="flex-1 flex items-center justify-center p-4 bg-gray-50 dark:bg-gray-900 min-h-[200px]">
|
|
1564
|
+
<figure class={`w-full h-full 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 flex flex-col ${params.className || ''}`}>
|
|
1565
|
+
<div class="flex-1 flex items-center justify-center p-4 bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-50,#f9fafb))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-900,#111827))] min-h-[200px]">
|
|
1533
1566
|
<a
|
|
1534
1567
|
href={safeUrl(params.url)}
|
|
1535
1568
|
target="_blank"
|
|
1536
1569
|
rel="noopener noreferrer"
|
|
1537
|
-
class="cursor-zoom-in focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 rounded"
|
|
1570
|
+
class="cursor-zoom-in focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))] rounded"
|
|
1538
1571
|
aria-label={formatMCPUIString(strings.imageViewFullSize, {
|
|
1539
1572
|
alt: params.alt || strings.imageAltFallback,
|
|
1540
1573
|
})}
|
|
@@ -1542,14 +1575,14 @@ function ImageRenderer(props: { component: UIComponent }) {
|
|
|
1542
1575
|
<img
|
|
1543
1576
|
src={safeUrl(params.url, { allowDataImage: true })}
|
|
1544
1577
|
alt={params.alt || strings.imageAlt}
|
|
1545
|
-
class="max-w-full max-h-[500px] object-contain rounded shadow-sm hover:opacity-95 transition-opacity"
|
|
1578
|
+
class="max-w-full max-h-[500px] object-contain rounded shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] hover:opacity-95 transition-opacity"
|
|
1546
1579
|
loading="lazy"
|
|
1547
1580
|
/>
|
|
1548
1581
|
</a>
|
|
1549
1582
|
</div>
|
|
1550
1583
|
<Show when={params.caption}>
|
|
1551
|
-
<figcaption class="p-3 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
|
1552
|
-
<p class="text-sm text-gray-600 dark:text-gray-400 text-center">{params.caption}</p>
|
|
1584
|
+
<figcaption class="p-3 border-t border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))]">
|
|
1585
|
+
<p class="text-sm text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] text-center">{params.caption}</p>
|
|
1553
1586
|
</figcaption>
|
|
1554
1587
|
</Show>
|
|
1555
1588
|
</figure>
|
|
@@ -1572,10 +1605,10 @@ function LinkRenderer(props: { component: UIComponent }) {
|
|
|
1572
1605
|
label: params.label || strings.linkLabel,
|
|
1573
1606
|
description: params.description || params.url,
|
|
1574
1607
|
})}
|
|
1575
|
-
class={`flex items-center gap-3 p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors group h-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${params.className || ''}`}
|
|
1608
|
+
class={`flex items-center gap-3 p-4 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))] 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))] transition-colors group h-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))] ${params.className || ''}`}
|
|
1576
1609
|
onClick={(e) => e.stopPropagation()}
|
|
1577
1610
|
>
|
|
1578
|
-
<div class="p-2 bg-blue-50 dark:bg-blue-900
|
|
1611
|
+
<div class="p-2 bg-[color:var(--mcp-ui-accent-soft,var(--color-blue-50))] dark:bg-[color:var(--mcp-ui-accent-soft,color-mix(in_oklab,var(--color-blue-900)_30%,transparent))] rounded-full text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] group-hover:bg-[color:var(--mcp-ui-accent-soft,var(--color-blue-100))] dark:group-hover:bg-[color:var(--mcp-ui-accent-soft,color-mix(in_oklab,var(--color-blue-900)_50%,transparent))] shrink-0 transition-colors" aria-hidden="true">
|
|
1579
1612
|
<svg
|
|
1580
1613
|
xmlns="http://www.w3.org/2000/svg"
|
|
1581
1614
|
class="w-5 h-5"
|
|
@@ -1591,16 +1624,16 @@ function LinkRenderer(props: { component: UIComponent }) {
|
|
|
1591
1624
|
</svg>
|
|
1592
1625
|
</div>
|
|
1593
1626
|
<div class="flex-1 min-w-0">
|
|
1594
|
-
<h4 class="text-sm font-medium text-gray-900 dark:text-white truncate group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
|
|
1627
|
+
<h4 class="text-sm font-medium text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] truncate group-hover:text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:group-hover:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] transition-colors">
|
|
1595
1628
|
{params.label || params.url}
|
|
1596
1629
|
</h4>
|
|
1597
1630
|
<Show when={params.description}>
|
|
1598
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 truncate">{params.description}</p>
|
|
1631
|
+
<p 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))] truncate">{params.description}</p>
|
|
1599
1632
|
</Show>
|
|
1600
1633
|
</div>
|
|
1601
1634
|
<svg
|
|
1602
1635
|
xmlns="http://www.w3.org/2000/svg"
|
|
1603
|
-
class="w-4 h-4 text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-300 shrink-0 transition-colors"
|
|
1636
|
+
class="w-4 h-4 text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] group-hover:text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:group-hover:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] shrink-0 transition-colors"
|
|
1604
1637
|
viewBox="0 0 24 24"
|
|
1605
1638
|
fill="none"
|
|
1606
1639
|
stroke="currentColor"
|
|
@@ -1725,7 +1758,7 @@ function ComponentRenderer(props: {
|
|
|
1725
1758
|
if (mode === 'inline-warn') {
|
|
1726
1759
|
return (
|
|
1727
1760
|
<div
|
|
1728
|
-
class="inline-flex items-center gap-1.5 px-2 py-1 rounded-md bg-yellow-50 dark:bg-yellow-900
|
|
1761
|
+
class="inline-flex items-center gap-1.5 px-2 py-1 rounded-md 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))] text-xs text-[color:var(--mcp-ui-warning,var(--color-yellow-800))] dark:text-[color:var(--mcp-ui-warning,var(--color-yellow-200))]"
|
|
1729
1762
|
role="alert"
|
|
1730
1763
|
aria-label={strings.validationWarning}
|
|
1731
1764
|
title={firstError}
|
|
@@ -1752,9 +1785,9 @@ function ComponentRenderer(props: {
|
|
|
1752
1785
|
|
|
1753
1786
|
// mode === 'block' (default, pre-v5.4.0 behavior)
|
|
1754
1787
|
return (
|
|
1755
|
-
<div class="w-full h-full bg-red-50 dark:bg-red-900
|
|
1756
|
-
<p class="text-sm font-medium text-red-900 dark:text-red-100">{strings.validationError}</p>
|
|
1757
|
-
<p class="text-xs text-red-700 dark:text-red-300 mt-1">
|
|
1788
|
+
<div class="w-full h-full bg-[color:var(--mcp-ui-danger-soft,var(--color-red-50))] dark:bg-[color:var(--mcp-ui-danger-soft,color-mix(in_oklab,var(--color-red-900)_20%,transparent))] border border-[color:var(--mcp-ui-danger-border,var(--color-red-200))] dark:border-[color:var(--mcp-ui-danger-border,var(--color-red-800))] rounded-lg p-4">
|
|
1789
|
+
<p class="text-sm font-medium text-[color:var(--mcp-ui-danger,var(--color-red-900))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-100))]">{strings.validationError}</p>
|
|
1790
|
+
<p class="text-xs text-[color:var(--mcp-ui-danger,var(--color-red-700))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-300))] mt-1">
|
|
1758
1791
|
{firstError}
|
|
1759
1792
|
</p>
|
|
1760
1793
|
</div>
|
|
@@ -1859,7 +1892,7 @@ function UnsupportedComponentFallback(props: { component: UIComponent }) {
|
|
|
1859
1892
|
return (
|
|
1860
1893
|
<div
|
|
1861
1894
|
role="alert"
|
|
1862
|
-
class="w-full rounded-lg border border-amber-300 bg-amber-50 px-3 py-2 text-sm text-amber-800 dark:border-amber-700
|
|
1895
|
+
class="w-full rounded-lg border border-[color:var(--mcp-ui-warning-border,var(--color-amber-300))] bg-[color:var(--mcp-ui-warning-soft,var(--color-amber-50))] px-3 py-2 text-sm text-[color:var(--mcp-ui-warning,var(--color-amber-800))] dark:border-[color:var(--mcp-ui-warning-border,color-mix(in_oklab,var(--color-amber-700)_50%,transparent))] dark:bg-[color:var(--mcp-ui-warning-soft,color-mix(in_oklab,var(--color-amber-900)_20%,transparent))] dark:text-[color:var(--mcp-ui-warning,var(--color-amber-200))]"
|
|
1863
1896
|
>
|
|
1864
1897
|
{strings.unsupportedComponentType} <code class="font-mono">{props.component.type}</code>
|
|
1865
1898
|
</div>
|
|
@@ -1923,10 +1956,10 @@ function ActionRenderer(props: { component: UIComponent }) {
|
|
|
1923
1956
|
target={safeUrl(params.url) ? '_blank' : undefined}
|
|
1924
1957
|
rel="noopener noreferrer"
|
|
1925
1958
|
aria-label={params.ariaLabel || params.label}
|
|
1926
|
-
class={`inline-flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
|
|
1927
|
-
${params.variant === 'primary' ? 'bg-blue-600 text-white hover:bg-blue-700' :
|
|
1928
|
-
params.variant === 'outline' ? 'border border-gray-300 text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800' :
|
|
1929
|
-
'text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300'}
|
|
1959
|
+
class={`inline-flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))]
|
|
1960
|
+
${params.variant === 'primary' ? 'bg-[color:var(--mcp-ui-accent,var(--color-blue-600))] text-[color:var(--mcp-ui-accent-fg,var(--color-white,#fff))] hover:bg-[color:var(--mcp-ui-accent-hover,var(--color-blue-700))]' :
|
|
1961
|
+
params.variant === 'outline' ? 'border border-[color:var(--mcp-ui-border,var(--color-gray-300,#d1d5db))] text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-50,#f9fafb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-800,#1f2937))]' :
|
|
1962
|
+
'text-[color:var(--mcp-ui-accent,var(--color-blue-600))] hover:text-[color:var(--mcp-ui-accent,var(--color-blue-800))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] dark:hover:text-[color:var(--mcp-ui-accent,var(--color-blue-300))]'}
|
|
1930
1963
|
${params.className || ''}`}
|
|
1931
1964
|
onClick={handleClick}
|
|
1932
1965
|
>
|
|
@@ -1944,12 +1977,12 @@ function ActionRenderer(props: { component: UIComponent }) {
|
|
|
1944
1977
|
disabled={isDisabled()}
|
|
1945
1978
|
aria-busy={isExecuting() && isExecutable()}
|
|
1946
1979
|
aria-label={params.ariaLabel || params.label}
|
|
1947
|
-
class={`inline-flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
|
|
1948
|
-
${params.variant === 'primary' ? 'bg-blue-600 text-white hover:bg-blue-700 shadow-sm' :
|
|
1949
|
-
params.variant === 'secondary' ? 'bg-gray-100 text-gray-900 hover:bg-gray-200 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600' :
|
|
1950
|
-
params.variant === 'outline' ? 'border border-gray-300 text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800' :
|
|
1951
|
-
params.variant === 'danger' ? 'bg-red-600 text-white hover:bg-red-700' :
|
|
1952
|
-
'bg-transparent text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800'}
|
|
1980
|
+
class={`inline-flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))]
|
|
1981
|
+
${params.variant === 'primary' ? 'bg-[color:var(--mcp-ui-accent,var(--color-blue-600))] text-[color:var(--mcp-ui-accent-fg,var(--color-white,#fff))] hover:bg-[color:var(--mcp-ui-accent-hover,var(--color-blue-700))] shadow-[var(--mcp-ui-shadow,var(--shadow-sm))]' :
|
|
1982
|
+
params.variant === 'secondary' ? 'bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-100,#f3f4f6))] text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-200,#e5e7eb))] dark:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-600,#4b5563))]' :
|
|
1983
|
+
params.variant === 'outline' ? 'border border-[color:var(--mcp-ui-border,var(--color-gray-300,#d1d5db))] text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-50,#f9fafb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-800,#1f2937))]' :
|
|
1984
|
+
params.variant === 'danger' ? 'bg-[color:var(--mcp-ui-danger,var(--color-red-600))] text-[color:var(--mcp-ui-accent-fg,var(--color-white,#fff))] hover:bg-[color:var(--mcp-ui-danger-hover,var(--color-red-700))]' :
|
|
1985
|
+
'bg-transparent text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-100,#f3f4f6))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-800,#1f2937))]'}
|
|
1953
1986
|
${isDisabled() ? 'opacity-50 cursor-not-allowed' : ''}
|
|
1954
1987
|
${params.size === 'sm' ? 'px-3 py-1.5 text-xs' : params.size === 'lg' ? 'px-6 py-3 text-base' : ''}
|
|
1955
1988
|
${params.className || ''}`}
|
|
@@ -1984,32 +2017,32 @@ function ErrorCardRenderer(props: { error: any }) {
|
|
|
1984
2017
|
}
|
|
1985
2018
|
|
|
1986
2019
|
return (
|
|
1987
|
-
<div class="relative w-full bg-red-50 dark:bg-red-900
|
|
2020
|
+
<div class="relative w-full bg-[color:var(--mcp-ui-danger-soft,var(--color-red-50))] dark:bg-[color:var(--mcp-ui-danger-soft,color-mix(in_oklab,var(--color-red-900)_20%,transparent))] border border-[color:var(--mcp-ui-danger-border,var(--color-red-200))] dark:border-[color:var(--mcp-ui-danger-border,var(--color-red-800))] rounded-lg p-4 group">
|
|
1988
2021
|
<CopyButton getText={getErrorText} title={strings.copyErrorDetails} position="top-right" />
|
|
1989
2022
|
<div class="flex items-start gap-3">
|
|
1990
|
-
<div class="p-2 bg-red-100 dark:bg-red-900
|
|
1991
|
-
<svg class="w-5 h-5 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
2023
|
+
<div class="p-2 bg-[color:var(--mcp-ui-danger-soft,var(--color-red-100))] dark:bg-[color:var(--mcp-ui-danger-soft,color-mix(in_oklab,var(--color-red-900)_40%,transparent))] rounded-full shrink-0">
|
|
2024
|
+
<svg class="w-5 h-5 text-[color:var(--mcp-ui-danger,var(--color-red-600))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-400))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1992
2025
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
1993
2026
|
</svg>
|
|
1994
2027
|
</div>
|
|
1995
2028
|
<div class="flex-1 min-w-0">
|
|
1996
|
-
<h4 class="text-sm font-semibold text-red-800 dark:text-red-200">
|
|
2029
|
+
<h4 class="text-sm font-semibold text-[color:var(--mcp-ui-danger,var(--color-red-800))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-200))]">
|
|
1997
2030
|
{formatMCPUIString(strings.toolErrorTitle, {
|
|
1998
2031
|
tool: props.error.tool || strings.errorUnknownToolName,
|
|
1999
2032
|
})}
|
|
2000
2033
|
</h4>
|
|
2001
|
-
<p class="text-sm text-red-700 dark:text-red-300 mt-1">
|
|
2034
|
+
<p class="text-sm text-[color:var(--mcp-ui-danger,var(--color-red-700))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-300))] mt-1">
|
|
2002
2035
|
{props.error.message || strings.errorToolExecution}
|
|
2003
2036
|
</p>
|
|
2004
2037
|
<Show when={props.error.type}>
|
|
2005
|
-
<p class="text-xs text-red-600 dark:text-red-400 mt-1">
|
|
2038
|
+
<p class="text-xs text-[color:var(--mcp-ui-danger,var(--color-red-600))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-400))] mt-1">
|
|
2006
2039
|
{formatMCPUIString(strings.errorTypeLabel, { type: props.error.type })}
|
|
2007
2040
|
</p>
|
|
2008
2041
|
</Show>
|
|
2009
2042
|
<Show when={props.error.suggestions?.length}>
|
|
2010
2043
|
<div class="mt-3">
|
|
2011
|
-
<p class="text-xs font-medium text-red-700 dark:text-red-300">{strings.errorSuggestions}</p>
|
|
2012
|
-
<ul class="mt-1 text-xs text-red-600 dark:text-red-400 list-disc list-inside">
|
|
2044
|
+
<p class="text-xs font-medium text-[color:var(--mcp-ui-danger,var(--color-red-700))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-300))]">{strings.errorSuggestions}</p>
|
|
2045
|
+
<ul class="mt-1 text-xs text-[color:var(--mcp-ui-danger,var(--color-red-600))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-400))] list-disc list-inside">
|
|
2013
2046
|
<For each={props.error.suggestions}>
|
|
2014
2047
|
{(suggestion: string) => <li>{suggestion}</li>}
|
|
2015
2048
|
</For>
|
|
@@ -2017,7 +2050,7 @@ function ErrorCardRenderer(props: { error: any }) {
|
|
|
2017
2050
|
</div>
|
|
2018
2051
|
</Show>
|
|
2019
2052
|
<Show when={mounted() && props.error.timestamp}>
|
|
2020
|
-
<p class="text-xs text-red-500 dark:text-red-500 mt-2">
|
|
2053
|
+
<p class="text-xs text-[color:var(--mcp-ui-danger,var(--color-red-500))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-500))] mt-2">
|
|
2021
2054
|
{new Date(props.error.timestamp).toLocaleString(strings.locale)}
|
|
2022
2055
|
</p>
|
|
2023
2056
|
</Show>
|
|
@@ -2064,10 +2097,10 @@ function UIResourceHtmlRenderer(props: { resource: any }) {
|
|
|
2064
2097
|
}
|
|
2065
2098
|
|
|
2066
2099
|
return (
|
|
2067
|
-
<div class="w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
2100
|
+
<div class="w-full 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">
|
|
2068
2101
|
<Show when={props.resource.metadata?.title || props.resource.uri}>
|
|
2069
|
-
<div class="px-4 py-2 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
|
|
2070
|
-
<h3 class="text-sm font-semibold text-gray-900 dark:text-white capitalize">
|
|
2102
|
+
<div class="px-4 py-2 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))] bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-50,#f9fafb))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-900,#111827))]">
|
|
2103
|
+
<h3 class="text-sm font-semibold text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] capitalize">
|
|
2071
2104
|
{resourceTitle()}
|
|
2072
2105
|
</h3>
|
|
2073
2106
|
</div>
|
|
@@ -2198,7 +2231,7 @@ export const UIResourceRenderer: Component<UIResourceRendererProps> = (props) =>
|
|
|
2198
2231
|
return (
|
|
2199
2232
|
<RenderProvider renderComponent={renderComponent}>
|
|
2200
2233
|
<div
|
|
2201
|
-
class={`w-full ${props.class || ''}`}
|
|
2234
|
+
class={`mcp-ui-root [background-color:var(--mcp-ui-bg)] w-full ${props.class || ''}`}
|
|
2202
2235
|
{...(isLayoutContent
|
|
2203
2236
|
? { 'data-mcp-ui-layout-id': outerKey() }
|
|
2204
2237
|
: { 'data-mcp-ui-component-id': outerKey() })}
|