@seed-ship/mcp-ui-solid 6.19.1 → 6.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +663 -0
- package/README.md +911 -4
- package/dist/adapters/connector.cjs +15 -4
- package/dist/adapters/connector.cjs.map +1 -1
- package/dist/adapters/connector.d.ts +38 -0
- package/dist/adapters/connector.d.ts.map +1 -1
- package/dist/adapters/connector.js +15 -4
- package/dist/adapters/connector.js.map +1 -1
- package/dist/adapters/index.d.ts +4 -3
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/macro-run.cjs +25 -13
- package/dist/adapters/macro-run.cjs.map +1 -1
- package/dist/adapters/macro-run.d.ts +37 -2
- package/dist/adapters/macro-run.d.ts.map +1 -1
- package/dist/adapters/macro-run.js +25 -13
- package/dist/adapters/macro-run.js.map +1 -1
- package/dist/adapters.cjs +2 -0
- package/dist/adapters.cjs.map +1 -1
- package/dist/adapters.js +4 -2
- package/dist/components/ActionGroupRenderer.cjs +3 -1
- package/dist/components/ActionGroupRenderer.cjs.map +1 -1
- package/dist/components/ActionGroupRenderer.d.ts.map +1 -1
- package/dist/components/ActionGroupRenderer.js +3 -1
- package/dist/components/ActionGroupRenderer.js.map +1 -1
- package/dist/components/AgentCard.cjs +11 -8
- package/dist/components/AgentCard.cjs.map +1 -1
- package/dist/components/AgentCard.d.ts.map +1 -1
- package/dist/components/AgentCard.js +11 -8
- package/dist/components/AgentCard.js.map +1 -1
- package/dist/components/AgentHandoff.cjs +6 -1
- package/dist/components/AgentHandoff.cjs.map +1 -1
- package/dist/components/AgentHandoff.d.ts.map +1 -1
- package/dist/components/AgentHandoff.js +6 -1
- package/dist/components/AgentHandoff.js.map +1 -1
- package/dist/components/ArtifactRenderer.cjs +25 -6
- package/dist/components/ArtifactRenderer.cjs.map +1 -1
- package/dist/components/ArtifactRenderer.d.ts.map +1 -1
- package/dist/components/ArtifactRenderer.js +25 -6
- package/dist/components/ArtifactRenderer.js.map +1 -1
- package/dist/components/AutocompleteDropdown.cjs +30 -24
- package/dist/components/AutocompleteDropdown.cjs.map +1 -1
- package/dist/components/AutocompleteDropdown.d.ts.map +1 -1
- package/dist/components/AutocompleteDropdown.js +30 -24
- package/dist/components/AutocompleteDropdown.js.map +1 -1
- package/dist/components/AutocompleteFormField.cjs +3 -1
- package/dist/components/AutocompleteFormField.cjs.map +1 -1
- package/dist/components/AutocompleteFormField.d.ts.map +1 -1
- package/dist/components/AutocompleteFormField.js +4 -2
- package/dist/components/AutocompleteFormField.js.map +1 -1
- package/dist/components/BriefingDiff.cjs +45 -39
- package/dist/components/BriefingDiff.cjs.map +1 -1
- package/dist/components/BriefingDiff.d.ts.map +1 -1
- package/dist/components/BriefingDiff.js +45 -39
- package/dist/components/BriefingDiff.js.map +1 -1
- package/dist/components/CarouselRenderer.cjs +8 -2
- package/dist/components/CarouselRenderer.cjs.map +1 -1
- package/dist/components/CarouselRenderer.d.ts.map +1 -1
- package/dist/components/CarouselRenderer.js +8 -2
- package/dist/components/CarouselRenderer.js.map +1 -1
- package/dist/components/ChartJSRenderer.cjs +60 -36
- package/dist/components/ChartJSRenderer.cjs.map +1 -1
- package/dist/components/ChartJSRenderer.d.ts.map +1 -1
- package/dist/components/ChartJSRenderer.js +61 -37
- package/dist/components/ChartJSRenderer.js.map +1 -1
- package/dist/components/ChatPrompt.cjs +15 -9
- package/dist/components/ChatPrompt.cjs.map +1 -1
- package/dist/components/ChatPrompt.d.ts.map +1 -1
- package/dist/components/ChatPrompt.js +15 -9
- package/dist/components/ChatPrompt.js.map +1 -1
- package/dist/components/CodeBlockRenderer.cjs +34 -16
- package/dist/components/CodeBlockRenderer.cjs.map +1 -1
- package/dist/components/CodeBlockRenderer.d.ts.map +1 -1
- package/dist/components/CodeBlockRenderer.js +34 -16
- package/dist/components/CodeBlockRenderer.js.map +1 -1
- package/dist/components/DataPreviewSection.cjs +91 -44
- package/dist/components/DataPreviewSection.cjs.map +1 -1
- package/dist/components/DataPreviewSection.d.ts.map +1 -1
- package/dist/components/DataPreviewSection.js +92 -45
- package/dist/components/DataPreviewSection.js.map +1 -1
- package/dist/components/DegradedFallback.cjs +20 -16
- package/dist/components/DegradedFallback.cjs.map +1 -1
- package/dist/components/DegradedFallback.d.ts.map +1 -1
- package/dist/components/DegradedFallback.js +20 -16
- package/dist/components/DegradedFallback.js.map +1 -1
- package/dist/components/FooterRenderer.cjs +21 -17
- package/dist/components/FooterRenderer.cjs.map +1 -1
- package/dist/components/FooterRenderer.d.ts.map +1 -1
- package/dist/components/FooterRenderer.js +21 -17
- package/dist/components/FooterRenderer.js.map +1 -1
- package/dist/components/FormFieldRenderer.cjs +116 -93
- package/dist/components/FormFieldRenderer.cjs.map +1 -1
- package/dist/components/FormFieldRenderer.d.ts.map +1 -1
- package/dist/components/FormFieldRenderer.js +116 -93
- package/dist/components/FormFieldRenderer.js.map +1 -1
- package/dist/components/FormRenderer.cjs +55 -31
- package/dist/components/FormRenderer.cjs.map +1 -1
- package/dist/components/FormRenderer.d.ts.map +1 -1
- package/dist/components/FormRenderer.js +55 -31
- package/dist/components/FormRenderer.js.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.cjs +20 -15
- package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.d.ts.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.js +20 -15
- package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
- package/dist/components/GraphRenderer.cjs +56 -33
- package/dist/components/GraphRenderer.cjs.map +1 -1
- package/dist/components/GraphRenderer.d.ts.map +1 -1
- package/dist/components/GraphRenderer.js +56 -33
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/IframeFallbackLink.cjs +37 -0
- package/dist/components/IframeFallbackLink.cjs.map +1 -0
- package/dist/components/IframeFallbackLink.d.ts +23 -0
- package/dist/components/IframeFallbackLink.d.ts.map +1 -0
- package/dist/components/IframeFallbackLink.js +37 -0
- package/dist/components/IframeFallbackLink.js.map +1 -0
- package/dist/components/ImageGalleryRenderer.cjs +13 -4
- package/dist/components/ImageGalleryRenderer.cjs.map +1 -1
- package/dist/components/ImageGalleryRenderer.d.ts.map +1 -1
- package/dist/components/ImageGalleryRenderer.js +13 -4
- package/dist/components/ImageGalleryRenderer.js.map +1 -1
- package/dist/components/LightboxOverlay.cjs +16 -8
- package/dist/components/LightboxOverlay.cjs.map +1 -1
- package/dist/components/LightboxOverlay.d.ts.map +1 -1
- package/dist/components/LightboxOverlay.js +17 -9
- package/dist/components/LightboxOverlay.js.map +1 -1
- package/dist/components/MapRenderer.cjs +59 -29
- package/dist/components/MapRenderer.cjs.map +1 -1
- package/dist/components/MapRenderer.d.ts +10 -2
- package/dist/components/MapRenderer.d.ts.map +1 -1
- package/dist/components/MapRenderer.js +60 -30
- package/dist/components/MapRenderer.js.map +1 -1
- package/dist/components/ModalRenderer.cjs +4 -1
- package/dist/components/ModalRenderer.cjs.map +1 -1
- package/dist/components/ModalRenderer.d.ts.map +1 -1
- package/dist/components/ModalRenderer.js +5 -2
- package/dist/components/ModalRenderer.js.map +1 -1
- package/dist/components/ScratchpadPanel.cjs +578 -535
- package/dist/components/ScratchpadPanel.cjs.map +1 -1
- package/dist/components/ScratchpadPanel.d.ts.map +1 -1
- package/dist/components/ScratchpadPanel.js +578 -535
- package/dist/components/ScratchpadPanel.js.map +1 -1
- package/dist/components/StreamingUIRenderer.cjs +92 -28
- package/dist/components/StreamingUIRenderer.cjs.map +1 -1
- package/dist/components/StreamingUIRenderer.d.ts.map +1 -1
- package/dist/components/StreamingUIRenderer.js +92 -28
- package/dist/components/StreamingUIRenderer.js.map +1 -1
- package/dist/components/UIResourceRenderer.cjs +481 -365
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts +21 -0
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +484 -368
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/components/VerifiedText.cjs +39 -20
- package/dist/components/VerifiedText.cjs.map +1 -1
- package/dist/components/VerifiedText.d.ts +2 -1
- package/dist/components/VerifiedText.d.ts.map +1 -1
- package/dist/components/VerifiedText.js +39 -20
- package/dist/components/VerifiedText.js.map +1 -1
- package/dist/components/VideoRenderer.cjs +51 -20
- package/dist/components/VideoRenderer.cjs.map +1 -1
- package/dist/components/VideoRenderer.d.ts.map +1 -1
- package/dist/components/VideoRenderer.js +51 -20
- package/dist/components/VideoRenderer.js.map +1 -1
- package/dist/components/chart-data-table.cjs +38 -17
- package/dist/components/chart-data-table.cjs.map +1 -1
- package/dist/components/chart-data-table.d.ts +30 -1
- package/dist/components/chart-data-table.d.ts.map +1 -1
- package/dist/components/chart-data-table.js +38 -17
- package/dist/components/chart-data-table.js.map +1 -1
- package/dist/context/MCPActionContext.cjs +3 -1
- package/dist/context/MCPActionContext.cjs.map +1 -1
- package/dist/context/MCPActionContext.d.ts.map +1 -1
- package/dist/context/MCPActionContext.js +3 -1
- package/dist/context/MCPActionContext.js.map +1 -1
- package/dist/context/MCPUIConfigContext.cjs +29 -0
- package/dist/context/MCPUIConfigContext.cjs.map +1 -0
- package/dist/context/MCPUIConfigContext.d.ts +124 -0
- package/dist/context/MCPUIConfigContext.d.ts.map +1 -0
- package/dist/context/MCPUIConfigContext.js +29 -0
- package/dist/context/MCPUIConfigContext.js.map +1 -0
- package/dist/context/MCPUIStringsContext.cjs +287 -2
- package/dist/context/MCPUIStringsContext.cjs.map +1 -1
- package/dist/context/MCPUIStringsContext.d.ts +607 -3
- package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
- package/dist/context/MCPUIStringsContext.js +287 -2
- package/dist/context/MCPUIStringsContext.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useStreamingUI.cjs +28 -10
- package/dist/hooks/useStreamingUI.cjs.map +1 -1
- package/dist/hooks/useStreamingUI.d.ts +37 -0
- package/dist/hooks/useStreamingUI.d.ts.map +1 -1
- package/dist/hooks/useStreamingUI.js +28 -10
- package/dist/hooks/useStreamingUI.js.map +1 -1
- package/dist/hooks.cjs +1 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.js +2 -1
- package/dist/index.cjs +29 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -3
- package/dist/index.js.map +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/validation.cjs +52 -26
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts +67 -2
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +52 -26
- package/dist/services/validation.js.map +1 -1
- package/dist/utils/degraded-projections.cjs +37 -12
- package/dist/utils/degraded-projections.cjs.map +1 -1
- package/dist/utils/degraded-projections.d.ts +39 -3
- package/dist/utils/degraded-projections.d.ts.map +1 -1
- package/dist/utils/degraded-projections.js +37 -12
- package/dist/utils/degraded-projections.js.map +1 -1
- package/dist/utils/format-string.cjs +10 -0
- package/dist/utils/format-string.cjs.map +1 -0
- package/dist/utils/format-string.d.ts +26 -0
- package/dist/utils/format-string.d.ts.map +1 -0
- package/dist/utils/format-string.js +10 -0
- package/dist/utils/format-string.js.map +1 -0
- package/dist/utils/iframe-coep.cjs +30 -0
- package/dist/utils/iframe-coep.cjs.map +1 -0
- package/dist/utils/iframe-coep.d.ts +46 -0
- package/dist/utils/iframe-coep.d.ts.map +1 -0
- package/dist/utils/iframe-coep.js +30 -0
- package/dist/utils/iframe-coep.js.map +1 -0
- package/dist/validation.cjs +5 -0
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.d.cts +2 -1
- package/dist/validation.d.ts +2 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +6 -1
- package/package.json +1 -1
- package/src/adapters/connector.ts +58 -4
- package/src/adapters/index.ts +12 -2
- package/src/adapters/macro-run.ts +71 -11
- package/src/components/ActionGroupRenderer.tsx +3 -1
- package/src/components/AgentCard.tsx +27 -9
- package/src/components/AgentHandoff.tsx +4 -1
- package/src/components/ArtifactRenderer.tsx +17 -5
- package/src/components/AutocompleteDropdown.tsx +8 -6
- package/src/components/AutocompleteFormField.tsx +5 -1
- package/src/components/BriefingDiff.tsx +11 -3
- package/src/components/CarouselRenderer.tsx +4 -2
- package/src/components/ChartJSRenderer.tsx +18 -9
- package/src/components/ChatPrompt.tsx +13 -8
- package/src/components/CodeBlockRenderer.tsx +12 -10
- package/src/components/DataPreviewSection.tsx +92 -26
- package/src/components/DegradedFallback.tsx +4 -2
- package/src/components/FooterRenderer.tsx +3 -1
- package/src/components/FormFieldRenderer.tsx +34 -19
- package/src/components/FormRenderer.tsx +38 -10
- package/src/components/GenerativeUIErrorBoundary.tsx +8 -3
- package/src/components/GraphRenderer.tsx +31 -16
- package/src/components/IframeFallbackLink.tsx +57 -0
- package/src/components/ImageGalleryRenderer.tsx +8 -4
- package/src/components/LightboxOverlay.tsx +6 -4
- package/src/components/MapRenderer.tsx +57 -20
- package/src/components/ModalRenderer.tsx +3 -1
- package/src/components/ScratchpadPanel.tsx +95 -44
- package/src/components/StreamingUIRenderer.tsx +41 -11
- package/src/components/UIResourceRenderer.tsx +176 -67
- package/src/components/VerifiedText.tsx +19 -9
- package/src/components/VideoRenderer.tsx +26 -4
- package/src/components/chart-data-table.ts +68 -14
- package/src/context/MCPActionContext.tsx +4 -1
- package/src/context/MCPUIConfigContext.tsx +151 -0
- package/src/context/MCPUIStringsContext.tsx +1014 -1
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useStreamingUI.ts +64 -10
- package/src/index.ts +38 -0
- package/src/services/index.ts +1 -0
- package/src/services/validation.ts +131 -32
- package/src/solid-jsx-attrs.d.ts +24 -0
- package/src/utils/degraded-projections.ts +75 -12
- package/src/utils/format-string.ts +33 -0
- package/src/utils/iframe-coep.ts +74 -0
- package/src/validation.ts +9 -0
|
@@ -52,6 +52,7 @@ import type {
|
|
|
52
52
|
FormPromptConfig,
|
|
53
53
|
} from '../types/chat-bus'
|
|
54
54
|
import { FormFieldRenderer } from './FormFieldRenderer'
|
|
55
|
+
import { useMCPUIStrings } from '../context/MCPUIStringsContext'
|
|
55
56
|
import type { FormFieldParams } from '../types'
|
|
56
57
|
|
|
57
58
|
export interface ChatPromptProps {
|
|
@@ -78,6 +79,8 @@ export interface ChatPromptProps {
|
|
|
78
79
|
* />
|
|
79
80
|
*/
|
|
80
81
|
export const ChatPrompt: Component<ChatPromptProps> = (props) => {
|
|
82
|
+
const strings = useMCPUIStrings()
|
|
83
|
+
|
|
81
84
|
// F1: Guard against null/undefined config (e.g. after dismiss clears state)
|
|
82
85
|
if (!props.config) return null
|
|
83
86
|
|
|
@@ -100,7 +103,7 @@ export const ChatPrompt: Component<ChatPromptProps> = (props) => {
|
|
|
100
103
|
? 'px-3 py-1 text-xs font-medium text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors'
|
|
101
104
|
: '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'
|
|
102
105
|
}
|
|
103
|
-
aria-label={props.dismissLabel ||
|
|
106
|
+
aria-label={props.dismissLabel || strings.dismiss}
|
|
104
107
|
>
|
|
105
108
|
<Show when={props.dismissLabel} fallback={
|
|
106
109
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
@@ -124,10 +127,10 @@ export const ChatPrompt: Component<ChatPromptProps> = (props) => {
|
|
|
124
127
|
<Match when={props.config.type === 'confirm'}>
|
|
125
128
|
<ConfirmBody
|
|
126
129
|
config={props.config.config as ConfirmPromptConfig}
|
|
127
|
-
onConfirm={() => props.onSubmit({ type: 'confirm', value: 'confirmed', label: (props.config.config as ConfirmPromptConfig).confirmLabel ||
|
|
130
|
+
onConfirm={() => props.onSubmit({ type: 'confirm', value: 'confirmed', label: (props.config.config as ConfirmPromptConfig).confirmLabel || strings.promptConfirmed })}
|
|
128
131
|
onCancel={() => {
|
|
129
132
|
props.onDismiss?.()
|
|
130
|
-
props.onSubmit({ type: 'confirm', value: 'cancelled', label: (props.config.config as ConfirmPromptConfig).cancelLabel ||
|
|
133
|
+
props.onSubmit({ type: 'confirm', value: 'cancelled', label: (props.config.config as ConfirmPromptConfig).cancelLabel || strings.promptCancelled, dismissed: true })
|
|
131
134
|
}}
|
|
132
135
|
/>
|
|
133
136
|
</Match>
|
|
@@ -213,6 +216,7 @@ const ConfirmBody: Component<{
|
|
|
213
216
|
onConfirm: () => void
|
|
214
217
|
onCancel: () => void
|
|
215
218
|
}> = (props) => {
|
|
219
|
+
const strings = useMCPUIStrings()
|
|
216
220
|
const isDanger = () => props.config.variant === 'danger'
|
|
217
221
|
|
|
218
222
|
return (
|
|
@@ -225,7 +229,7 @@ const ConfirmBody: Component<{
|
|
|
225
229
|
onClick={props.onCancel}
|
|
226
230
|
class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
|
227
231
|
>
|
|
228
|
-
{props.config.cancelLabel ||
|
|
232
|
+
{props.config.cancelLabel || strings.cancel}
|
|
229
233
|
</button>
|
|
230
234
|
<button
|
|
231
235
|
onClick={props.onConfirm}
|
|
@@ -235,7 +239,7 @@ const ConfirmBody: Component<{
|
|
|
235
239
|
: 'bg-blue-600 hover:bg-blue-700'
|
|
236
240
|
}`}
|
|
237
241
|
>
|
|
238
|
-
{props.config.confirmLabel ||
|
|
242
|
+
{props.config.confirmLabel || strings.confirm}
|
|
239
243
|
</button>
|
|
240
244
|
</div>
|
|
241
245
|
</div>
|
|
@@ -248,6 +252,7 @@ const FormBody: Component<{
|
|
|
248
252
|
config: FormPromptConfig
|
|
249
253
|
onSubmit: (data: Record<string, unknown>, label: string) => void
|
|
250
254
|
}> = (props) => {
|
|
255
|
+
const strings = useMCPUIStrings()
|
|
251
256
|
const [formData, setFormData] = createSignal<Record<string, any>>({})
|
|
252
257
|
const [dynamicOptions, setDynamicOptions] = createSignal<Record<string, Array<{ label: string; value: string }>>>({})
|
|
253
258
|
const [previewText, setPreviewText] = createSignal<string>('')
|
|
@@ -336,7 +341,7 @@ const FormBody: Component<{
|
|
|
336
341
|
.filter(([, v]) => v !== undefined && v !== '' && !(Array.isArray(v) && v.length === 0))
|
|
337
342
|
.map(([k, v]) => `${k}: ${Array.isArray(v) ? v.join(', ') : v}`)
|
|
338
343
|
.join(', ')
|
|
339
|
-
props.onSubmit(data, label ||
|
|
344
|
+
props.onSubmit(data, label || strings.promptFormSubmitted)
|
|
340
345
|
}
|
|
341
346
|
|
|
342
347
|
const isValid = () => {
|
|
@@ -379,7 +384,7 @@ const FormBody: Component<{
|
|
|
379
384
|
<Show when={previewLoading()} fallback={
|
|
380
385
|
<p class="text-blue-700 dark:text-blue-300">{previewText()}</p>
|
|
381
386
|
}>
|
|
382
|
-
<p class="text-blue-400 animate-pulse">
|
|
387
|
+
<p class="text-blue-400 animate-pulse">{strings.promptLoadingPreview}</p>
|
|
383
388
|
</Show>
|
|
384
389
|
</div>
|
|
385
390
|
</Show>
|
|
@@ -390,7 +395,7 @@ const FormBody: Component<{
|
|
|
390
395
|
disabled={!isValid()}
|
|
391
396
|
class="px-4 py-2 text-sm font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
|
|
392
397
|
>
|
|
393
|
-
{props.config.submitLabel ||
|
|
398
|
+
{props.config.submitLabel || strings.submit}
|
|
394
399
|
</button>
|
|
395
400
|
</div>
|
|
396
401
|
</form>
|
|
@@ -10,6 +10,7 @@ import type { UIComponent, CodeComponentParams } from '../types'
|
|
|
10
10
|
import { ExpandableWrapper, useExpanded } from './ExpandableWrapper'
|
|
11
11
|
import { highlightQuery } from './UIResourceRenderer'
|
|
12
12
|
import { escapeHtml } from '../utils/escape-html'
|
|
13
|
+
import { useMCPUIStrings } from '../context/MCPUIStringsContext'
|
|
13
14
|
|
|
14
15
|
/** Map of `params.language` → file extension for the v6.2.0 download button. */
|
|
15
16
|
const LANGUAGE_EXTENSIONS: Record<string, string> = {
|
|
@@ -45,6 +46,7 @@ export interface CodeBlockRendererProps {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
export const CodeBlockRenderer: Component<CodeBlockRendererProps> = (props) => {
|
|
49
|
+
const strings = useMCPUIStrings()
|
|
48
50
|
const [highlightedCode, setHighlightedCode] = createSignal<string>('')
|
|
49
51
|
const [isCopied, setIsCopied] = createSignal(false)
|
|
50
52
|
const [isHljsLoaded, setIsHljsLoaded] = createSignal(false)
|
|
@@ -201,12 +203,12 @@ export const CodeBlockRenderer: Component<CodeBlockRendererProps> = (props) => {
|
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
return (
|
|
204
|
-
<ExpandableWrapper title={params()?.filename || params()?.language ||
|
|
206
|
+
<ExpandableWrapper title={params()?.filename || params()?.language || strings.codeTitle} copyData={params()?.code} copyLabel={strings.codeCopy} toolbarVariant={props.toolbarVariant}>
|
|
205
207
|
<div class={`w-full bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden text-sm flex flex-col ${isExpanded() ? 'flex-1 min-h-0' : ''}`}>
|
|
206
208
|
{/* Header */}
|
|
207
209
|
<div class="flex items-center justify-between px-4 py-2 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 shrink-0">
|
|
208
210
|
<div class="font-mono text-xs text-gray-600 dark:text-gray-400">
|
|
209
|
-
{params()?.filename || params()?.language ||
|
|
211
|
+
{params()?.filename || params()?.language || strings.codeTitle}
|
|
210
212
|
</div>
|
|
211
213
|
<div class="flex items-center gap-2">
|
|
212
214
|
{/* Search input (v6.2.0) */}
|
|
@@ -214,16 +216,16 @@ export const CodeBlockRenderer: Component<CodeBlockRendererProps> = (props) => {
|
|
|
214
216
|
type="text"
|
|
215
217
|
value={searchQuery()}
|
|
216
218
|
onInput={(e) => setSearchQuery(e.currentTarget.value)}
|
|
217
|
-
placeholder=
|
|
219
|
+
placeholder={strings.codeSearchPlaceholder}
|
|
218
220
|
class="px-2 py-0.5 text-xs border border-gray-200 dark:border-gray-600 rounded 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 w-32"
|
|
219
|
-
aria-label=
|
|
221
|
+
aria-label={strings.codeSearchAria}
|
|
220
222
|
/>
|
|
221
223
|
{/* Download button (v6.2.0) */}
|
|
222
224
|
<button
|
|
223
225
|
onClick={handleDownload}
|
|
224
226
|
class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 focus:outline-none transition-colors"
|
|
225
|
-
aria-label=
|
|
226
|
-
title=
|
|
227
|
+
aria-label={strings.codeDownloadAria}
|
|
228
|
+
title={strings.codeDownload}
|
|
227
229
|
>
|
|
228
230
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
229
231
|
<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" />
|
|
@@ -233,8 +235,8 @@ export const CodeBlockRenderer: Component<CodeBlockRendererProps> = (props) => {
|
|
|
233
235
|
<button
|
|
234
236
|
onClick={() => setWordWrap(!wordWrap())}
|
|
235
237
|
class={`focus:outline-none transition-colors ${wordWrap() ? 'text-blue-500 dark:text-blue-400' : 'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200'}`}
|
|
236
|
-
aria-label=
|
|
237
|
-
title={wordWrap() ?
|
|
238
|
+
aria-label={strings.codeToggleWordWrap}
|
|
239
|
+
title={wordWrap() ? strings.codeWordWrapDisable : strings.codeWordWrapEnable}
|
|
238
240
|
>
|
|
239
241
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
240
242
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a4 4 0 010 8H9m4 0l-3-3m3 3l-3 3M3 6h18M3 14h4" />
|
|
@@ -244,8 +246,8 @@ export const CodeBlockRenderer: Component<CodeBlockRendererProps> = (props) => {
|
|
|
244
246
|
<button
|
|
245
247
|
onClick={handleCopy}
|
|
246
248
|
class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 focus:outline-none transition-colors"
|
|
247
|
-
aria-label=
|
|
248
|
-
title=
|
|
249
|
+
aria-label={strings.codeCopy}
|
|
250
|
+
title={strings.codeCopy}
|
|
249
251
|
>
|
|
250
252
|
<Show when={isCopied()} fallback={
|
|
251
253
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
@@ -5,8 +5,13 @@
|
|
|
5
5
|
* @experimental
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { createSignal, createMemo, createEffect, For, Show } from 'solid-js'
|
|
8
|
+
import { createSignal, createMemo, createEffect, For, Show, onMount } from 'solid-js'
|
|
9
9
|
import type { DataPreviewContent, DataPreviewColumn } from '../types/chat-bus'
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_MCPUI_STRINGS,
|
|
12
|
+
formatMCPUIString,
|
|
13
|
+
useMCPUIStrings,
|
|
14
|
+
} from '../context/MCPUIStringsContext'
|
|
10
15
|
|
|
11
16
|
export interface DataPreviewSectionProps {
|
|
12
17
|
content: DataPreviewContent
|
|
@@ -16,24 +21,62 @@ type SortDir = 'asc' | 'desc' | null
|
|
|
16
21
|
|
|
17
22
|
// ─── Formatting helpers ─────────────────────────────────────
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
/**
|
|
25
|
+
* `locale` — BCP-47 tag (`MCPUIStrings.locale`, default `'en-US'`). Before
|
|
26
|
+
* 6.20.0 these helpers hardcoded `'fr-FR'` / `'fr'`.
|
|
27
|
+
*/
|
|
28
|
+
function formatNumber(
|
|
29
|
+
value: unknown,
|
|
30
|
+
format?: string,
|
|
31
|
+
locale: string = DEFAULT_MCPUI_STRINGS.locale
|
|
32
|
+
): string {
|
|
20
33
|
if (typeof value !== 'number' || !isFinite(value)) return String(value ?? '')
|
|
21
|
-
if (format === 'percent')
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
if (format === 'percent') {
|
|
35
|
+
return new Intl.NumberFormat(locale, {
|
|
36
|
+
style: 'percent',
|
|
37
|
+
minimumFractionDigits: 1,
|
|
38
|
+
maximumFractionDigits: 1,
|
|
39
|
+
useGrouping: false,
|
|
40
|
+
}).format(value)
|
|
41
|
+
}
|
|
42
|
+
if (format === 'currency') return `${value.toLocaleString(locale)} EUR`
|
|
43
|
+
if (Number.isInteger(value)) return value.toLocaleString(locale)
|
|
44
|
+
return value.toLocaleString(locale, { maximumFractionDigits: 2 })
|
|
25
45
|
}
|
|
26
46
|
|
|
27
|
-
function formatCell(
|
|
47
|
+
function formatCell(
|
|
48
|
+
value: unknown,
|
|
49
|
+
col: DataPreviewColumn,
|
|
50
|
+
locale: string = DEFAULT_MCPUI_STRINGS.locale,
|
|
51
|
+
/** False before mount: date-time values stay raw so SSR and hydration match. */
|
|
52
|
+
zoneAware = true
|
|
53
|
+
): string {
|
|
28
54
|
if (value == null) return '\u2014'
|
|
29
|
-
if (col.type === 'number') return formatNumber(value, col.format)
|
|
55
|
+
if (col.type === 'number') return formatNumber(value, col.format, locale)
|
|
30
56
|
if (col.type === 'date' && typeof value === 'string') {
|
|
31
|
-
|
|
57
|
+
const date = new Date(value)
|
|
58
|
+
if (Number.isNaN(date.getTime())) return value
|
|
59
|
+
// A date-only value (`2026-09-17`) has no time zone: format it in UTC so
|
|
60
|
+
// every viewer, and the server, shows the same calendar date.
|
|
61
|
+
const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(value)
|
|
62
|
+
// A date-time's calendar date depends on the viewer's time zone, which the
|
|
63
|
+
// server cannot know: keep the raw value until the component has mounted.
|
|
64
|
+
if (!dateOnly && !zoneAware) return value
|
|
65
|
+
try {
|
|
66
|
+
return date.toLocaleDateString(locale, dateOnly ? { timeZone: 'UTC' } : undefined)
|
|
67
|
+
} catch {
|
|
68
|
+
return value
|
|
69
|
+
}
|
|
32
70
|
}
|
|
33
71
|
return String(value)
|
|
34
72
|
}
|
|
35
73
|
|
|
36
|
-
function compareValues(
|
|
74
|
+
function compareValues(
|
|
75
|
+
a: unknown,
|
|
76
|
+
b: unknown,
|
|
77
|
+
type?: string,
|
|
78
|
+
locale: string = DEFAULT_MCPUI_STRINGS.locale
|
|
79
|
+
): number {
|
|
37
80
|
if (a == null && b == null) return 0
|
|
38
81
|
if (a == null) return 1
|
|
39
82
|
if (b == null) return -1
|
|
@@ -53,7 +96,7 @@ function compareValues(a: unknown, b: unknown, type?: string): number {
|
|
|
53
96
|
if (isNaN(db)) return -1
|
|
54
97
|
return da - db
|
|
55
98
|
}
|
|
56
|
-
return String(a).localeCompare(String(b),
|
|
99
|
+
return String(a).localeCompare(String(b), locale, { sensitivity: 'base' })
|
|
57
100
|
}
|
|
58
101
|
|
|
59
102
|
// ─── Export helpers ─────────────────────────────────────────
|
|
@@ -101,6 +144,7 @@ function resolveContent(raw: unknown): DataPreviewContent | null {
|
|
|
101
144
|
// ─── Component ──────────────────────────────────────────────
|
|
102
145
|
|
|
103
146
|
export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
147
|
+
const strings = useMCPUIStrings()
|
|
104
148
|
const content = createMemo(() => {
|
|
105
149
|
const resolved = resolveContent(props.content)
|
|
106
150
|
if (!resolved) {
|
|
@@ -115,6 +159,8 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
115
159
|
const showPageInfo = () => content()?.showPageInfo !== false
|
|
116
160
|
|
|
117
161
|
const [page, setPage] = createSignal(content()?.initialPage ?? 0)
|
|
162
|
+
const [mounted, setMounted] = createSignal(false)
|
|
163
|
+
onMount(() => setMounted(true))
|
|
118
164
|
const [sortKey, setSortKey] = createSignal<string | null>(null)
|
|
119
165
|
const [sortDir, setSortDir] = createSignal<SortDir>(null)
|
|
120
166
|
|
|
@@ -142,7 +188,7 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
142
188
|
if (!key || !dir) return r
|
|
143
189
|
const col = columns().find(c => c.key === key)
|
|
144
190
|
return [...r].sort((a, b) => {
|
|
145
|
-
const cmp = compareValues(a[key], b[key], col?.type)
|
|
191
|
+
const cmp = compareValues(a[key], b[key], col?.type, strings.locale)
|
|
146
192
|
return dir === 'desc' ? -cmp : cmp
|
|
147
193
|
})
|
|
148
194
|
})
|
|
@@ -186,7 +232,7 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
186
232
|
return (
|
|
187
233
|
<Show when={content()} fallback={
|
|
188
234
|
<div class="text-xs text-amber-600 dark:text-amber-400 p-2">
|
|
189
|
-
|
|
235
|
+
{strings.previewInvalidContent}
|
|
190
236
|
</div>
|
|
191
237
|
}>
|
|
192
238
|
{(c) => (
|
|
@@ -208,14 +254,14 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
208
254
|
<button
|
|
209
255
|
class="px-2 py-1 text-xs rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
|
210
256
|
onClick={handleExportCSV}
|
|
211
|
-
title={
|
|
257
|
+
title={formatMCPUIString(strings.exportCsvRows, { count: sortedRows().length })}
|
|
212
258
|
>
|
|
213
259
|
CSV
|
|
214
260
|
</button>
|
|
215
261
|
<button
|
|
216
262
|
class="px-2 py-1 text-xs rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
|
217
263
|
onClick={handleExportJSON}
|
|
218
|
-
title={
|
|
264
|
+
title={formatMCPUIString(strings.exportJsonRows, { count: sortedRows().length })}
|
|
219
265
|
>
|
|
220
266
|
JSON
|
|
221
267
|
</button>
|
|
@@ -234,7 +280,7 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
234
280
|
class="px-3 py-2 font-medium text-xs text-gray-600 dark:text-gray-400 uppercase tracking-wider border-b border-gray-200 dark:border-gray-700 cursor-pointer select-none hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"
|
|
235
281
|
style={{ "text-align": columnAlign(col) }}
|
|
236
282
|
onClick={() => handleSort(col.key)}
|
|
237
|
-
title={
|
|
283
|
+
title={formatMCPUIString(strings.sortBy, { column: col.label })}
|
|
238
284
|
>
|
|
239
285
|
<span class="inline-flex items-center gap-1">
|
|
240
286
|
{col.label}
|
|
@@ -266,7 +312,7 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
266
312
|
class="px-3 py-2 text-gray-800 dark:text-gray-200"
|
|
267
313
|
style={{ "text-align": columnAlign(col) }}
|
|
268
314
|
>
|
|
269
|
-
{formatCell(row[col.key], col)}
|
|
315
|
+
{formatCell(row[col.key], col, strings.locale, mounted())}
|
|
270
316
|
</td>
|
|
271
317
|
)}
|
|
272
318
|
</For>
|
|
@@ -282,13 +328,22 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
282
328
|
<Show when={showPageInfo()}>
|
|
283
329
|
<span>
|
|
284
330
|
{isPaginated()
|
|
285
|
-
?
|
|
286
|
-
|
|
287
|
-
|
|
331
|
+
? formatMCPUIString(strings.previewShowingRange, {
|
|
332
|
+
start: rangeStart().toLocaleString(strings.locale),
|
|
333
|
+
end: rangeEnd().toLocaleString(strings.locale),
|
|
334
|
+
// `strings.locale` formats the count, so the chrome
|
|
335
|
+
// string and the digits agree (and SSR matches the client).
|
|
336
|
+
total: sortedRows().length.toLocaleString(strings.locale),
|
|
337
|
+
})
|
|
338
|
+
: formatMCPUIString(
|
|
339
|
+
sortedRows().length === 1 ? strings.previewRowsOne : strings.previewRowsMany,
|
|
340
|
+
{ count: sortedRows().length.toLocaleString(strings.locale) }
|
|
341
|
+
)}
|
|
288
342
|
{c().totalRows && c().totalRows! > sortedRows().length
|
|
289
|
-
?
|
|
290
|
-
|
|
291
|
-
|
|
343
|
+
? formatMCPUIString(strings.previewTotalSuffix, {
|
|
344
|
+
total: c().totalRows!.toLocaleString(strings.locale),
|
|
345
|
+
})
|
|
346
|
+
: ''}
|
|
292
347
|
</span>
|
|
293
348
|
</Show>
|
|
294
349
|
<Show when={!showPageInfo()}><span /></Show>
|
|
@@ -296,19 +351,30 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
296
351
|
<Show when={isPaginated()}>
|
|
297
352
|
<div class="flex items-center gap-1">
|
|
298
353
|
<button
|
|
354
|
+
type="button"
|
|
299
355
|
class="px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
300
356
|
disabled={page() === 0}
|
|
301
357
|
onClick={() => setPage(p => p - 1)}
|
|
358
|
+
aria-label={strings.paginationPrevious}
|
|
359
|
+
data-mcp-ui-action="page-prev"
|
|
302
360
|
>
|
|
303
|
-
|
|
361
|
+
<span aria-hidden="true">◀</span> {strings.previewPrev}
|
|
304
362
|
</button>
|
|
305
|
-
<span class="px-2"
|
|
363
|
+
<span class="px-2" aria-live="polite">
|
|
364
|
+
{formatMCPUIString(strings.previewPageIndicator, {
|
|
365
|
+
page: page() + 1,
|
|
366
|
+
total: totalPages(),
|
|
367
|
+
})}
|
|
368
|
+
</span>
|
|
306
369
|
<button
|
|
370
|
+
type="button"
|
|
307
371
|
class="px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
308
372
|
disabled={page() >= totalPages() - 1}
|
|
309
373
|
onClick={() => setPage(p => p + 1)}
|
|
374
|
+
aria-label={strings.paginationNext}
|
|
375
|
+
data-mcp-ui-action="page-next"
|
|
310
376
|
>
|
|
311
|
-
|
|
377
|
+
{strings.previewNext} <span aria-hidden="true">▶</span>
|
|
312
378
|
</button>
|
|
313
379
|
</div>
|
|
314
380
|
</Show>
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { Component, For, Show } from 'solid-js';
|
|
19
|
+
import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext';
|
|
19
20
|
|
|
20
21
|
export interface DegradedFallbackProps {
|
|
21
22
|
/** Short, human-readable reason the native render was skipped/failed. */
|
|
@@ -37,6 +38,7 @@ export interface DegradedFallbackProps {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
|
|
41
|
+
const strings = useMCPUIStrings();
|
|
40
42
|
const maxRows = () => props.maxRows ?? 50;
|
|
41
43
|
const allRows = () => props.rows ?? [];
|
|
42
44
|
const shownRows = () => allRows().slice(0, maxRows());
|
|
@@ -50,7 +52,7 @@ export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
|
|
|
50
52
|
>
|
|
51
53
|
<p class="text-sm font-medium text-amber-900 dark:text-amber-100">{props.message}</p>
|
|
52
54
|
<p class="mt-0.5 text-xs text-amber-700 dark:text-amber-300">
|
|
53
|
-
{props.caption ??
|
|
55
|
+
{props.caption ?? strings.degradedCaption}
|
|
54
56
|
</p>
|
|
55
57
|
|
|
56
58
|
<Show when={hasTable()}>
|
|
@@ -84,7 +86,7 @@ export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
|
|
|
84
86
|
</div>
|
|
85
87
|
<Show when={hiddenCount() > 0}>
|
|
86
88
|
<p class="mt-1 text-[10px] text-amber-600 dark:text-amber-400">
|
|
87
|
-
|
|
89
|
+
{formatMCPUIString(strings.degradedMoreRows, { count: hiddenCount() })}
|
|
88
90
|
</p>
|
|
89
91
|
</Show>
|
|
90
92
|
</Show>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Component, Show, For } from 'solid-js'
|
|
2
2
|
import { safeUrl } from '../utils/safe-url'
|
|
3
|
+
import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
|
|
3
4
|
|
|
4
5
|
export interface FooterComponentParams {
|
|
5
6
|
poweredBy?: string
|
|
@@ -11,6 +12,7 @@ export interface FooterComponentParams {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export const FooterRenderer: Component<{ params: FooterComponentParams }> = (props) => {
|
|
15
|
+
const strings = useMCPUIStrings()
|
|
14
16
|
return (
|
|
15
17
|
<div class="mt-4 pt-3 border-t border-gray-200 dark:border-gray-700 text-xs text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-4">
|
|
16
18
|
<Show when={props.params.poweredBy}>
|
|
@@ -40,7 +42,7 @@ export const FooterRenderer: Component<{ params: FooterComponentParams }> = (pro
|
|
|
40
42
|
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
41
43
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 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" />
|
|
42
44
|
</svg>
|
|
43
|
-
{props.params.sourceCount}
|
|
45
|
+
{formatMCPUIString(strings.footerSources, { count: props.params.sourceCount! })}
|
|
44
46
|
</span>
|
|
45
47
|
</Show>
|
|
46
48
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { Component, Show, For, Switch, Match, Accessor, createSignal, createEffect, onCleanup } from 'solid-js'
|
|
8
8
|
import type { FormFieldParams, PrefillSource } from '../types'
|
|
9
9
|
import { useConditionalField } from '../hooks/useConditionalField'
|
|
10
|
+
import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
|
|
10
11
|
|
|
11
12
|
export interface FormFieldRendererProps {
|
|
12
13
|
field: FormFieldParams
|
|
@@ -20,15 +21,24 @@ export interface FormFieldRendererProps {
|
|
|
20
21
|
formData?: Accessor<Record<string, any>>
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Badge config by prefill source. Module-level, so the title cannot be read
|
|
26
|
+
* from the context here — each badge carries the `MCPUIStrings` key instead
|
|
27
|
+
* and the rendering component resolves it at render time.
|
|
28
|
+
*/
|
|
29
|
+
const SOURCE_BADGES: Record<
|
|
30
|
+
PrefillSource,
|
|
31
|
+
{ icon: string; titleKey: 'sourceDetected' | 'sourceInferred' | 'sourcePrevious' } | null
|
|
32
|
+
> = {
|
|
33
|
+
detected: { icon: '\u2705', titleKey: 'sourceDetected' },
|
|
34
|
+
inferred: { icon: '\uD83D\uDD17', titleKey: 'sourceInferred' },
|
|
35
|
+
user: { icon: '\u270F\uFE0F', titleKey: 'sourcePrevious' },
|
|
28
36
|
default: null,
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
|
|
40
|
+
const strings = useMCPUIStrings()
|
|
41
|
+
|
|
32
42
|
// Conditional visibility based on showWhen
|
|
33
43
|
const { isVisible } = useConditionalField({
|
|
34
44
|
condition: props.field.showWhen,
|
|
@@ -76,14 +86,14 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
|
|
|
76
86
|
class={`block text-sm font-medium ${isUnsupported() ? 'text-gray-400 dark:text-gray-500' : isMuted() ? 'text-gray-400 dark:text-gray-500' : 'text-gray-700 dark:text-gray-300'}`}
|
|
77
87
|
>
|
|
78
88
|
<Show when={sourceBadge()}>
|
|
79
|
-
<span class="mr-1" title={sourceBadge()!.
|
|
89
|
+
<span class="mr-1" title={strings[sourceBadge()!.titleKey]}>{sourceBadge()!.icon}</span>
|
|
80
90
|
</Show>
|
|
81
91
|
{props.field.label}
|
|
82
92
|
<Show when={props.field.required || status() === 'required'}>
|
|
83
93
|
<span class="text-red-500 ml-1" aria-hidden="true">*</span>
|
|
84
94
|
</Show>
|
|
85
95
|
<Show when={isUnsupported()}>
|
|
86
|
-
<span class="ml-2 text-[10px] font-medium bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400 px-1.5 py-0.5 rounded">
|
|
96
|
+
<span class="ml-2 text-[10px] font-medium bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400 px-1.5 py-0.5 rounded">{strings.fieldNotSupported}</span>
|
|
87
97
|
</Show>
|
|
88
98
|
<Show when={status() === 'unknown'}>
|
|
89
99
|
<span class="ml-2 text-[10px] font-medium bg-yellow-100 dark:bg-yellow-900/30 text-yellow-600 dark:text-yellow-400 px-1.5 py-0.5 rounded">?</span>
|
|
@@ -316,7 +326,7 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
|
|
|
316
326
|
<Match when={props.field.type === 'fieldset'}>
|
|
317
327
|
<fieldset class="border border-gray-200 dark:border-gray-600 rounded-lg p-3">
|
|
318
328
|
<legend class="text-xs font-medium text-gray-500 dark:text-gray-400 px-1">{props.field.label}</legend>
|
|
319
|
-
<p class="text-xs text-gray-400">{props.field.helpText ||
|
|
329
|
+
<p class="text-xs text-gray-400">{props.field.helpText || strings.fieldGroupContainer}</p>
|
|
320
330
|
</fieldset>
|
|
321
331
|
</Match>
|
|
322
332
|
|
|
@@ -332,7 +342,9 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
|
|
|
332
342
|
disabled={props.disabled}
|
|
333
343
|
class={baseInputClass()}
|
|
334
344
|
/>
|
|
335
|
-
<p class="text-xs text-amber-500 mt-0.5">
|
|
345
|
+
<p class="text-xs text-amber-500 mt-0.5">
|
|
346
|
+
{formatMCPUIString(strings.fieldUnknownType, { type: props.field.type })}
|
|
347
|
+
</p>
|
|
336
348
|
</Match>
|
|
337
349
|
</Switch>
|
|
338
350
|
|
|
@@ -376,6 +388,7 @@ const MultiSelectField: Component<{
|
|
|
376
388
|
disabled?: boolean
|
|
377
389
|
baseClass: string
|
|
378
390
|
}> = (props) => {
|
|
391
|
+
const strings = useMCPUIStrings()
|
|
379
392
|
const [open, setOpen] = createSignal(false)
|
|
380
393
|
const [filter, setFilter] = createSignal('')
|
|
381
394
|
|
|
@@ -416,7 +429,7 @@ const MultiSelectField: Component<{
|
|
|
416
429
|
type="button"
|
|
417
430
|
onClick={() => removeChip(val)}
|
|
418
431
|
class="hover:text-blue-900 dark:hover:text-blue-100"
|
|
419
|
-
aria-label={
|
|
432
|
+
aria-label={formatMCPUIString(strings.removeItem, { name: getLabel(val) })}
|
|
420
433
|
>
|
|
421
434
|
×
|
|
422
435
|
</button>
|
|
@@ -435,8 +448,8 @@ const MultiSelectField: Component<{
|
|
|
435
448
|
>
|
|
436
449
|
<span class={props.value.length ? 'text-gray-900 dark:text-white' : 'text-gray-400'}>
|
|
437
450
|
{props.value.length
|
|
438
|
-
?
|
|
439
|
-
: props.field.placeholder ||
|
|
451
|
+
? formatMCPUIString(strings.fieldSelectedCount, { count: props.value.length })
|
|
452
|
+
: props.field.placeholder || strings.fieldSelectPlaceholder}
|
|
440
453
|
</span>
|
|
441
454
|
<svg class="w-4 h-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
442
455
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
|
@@ -456,7 +469,7 @@ const MultiSelectField: Component<{
|
|
|
456
469
|
type="text"
|
|
457
470
|
value={filter()}
|
|
458
471
|
onInput={(e) => setFilter(e.currentTarget.value)}
|
|
459
|
-
placeholder=
|
|
472
|
+
placeholder={strings.filterPlaceholder}
|
|
460
473
|
class="w-full px-2 py-1 text-sm border border-gray-200 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 outline-none"
|
|
461
474
|
autofocus
|
|
462
475
|
/>
|
|
@@ -478,7 +491,7 @@ const MultiSelectField: Component<{
|
|
|
478
491
|
)}
|
|
479
492
|
</For>
|
|
480
493
|
<Show when={filteredOptions().length === 0}>
|
|
481
|
-
<p class="px-3 py-2 text-sm text-gray-400">
|
|
494
|
+
<p class="px-3 py-2 text-sm text-gray-400">{strings.fieldNoMatches}</p>
|
|
482
495
|
</Show>
|
|
483
496
|
</div>
|
|
484
497
|
</div>
|
|
@@ -496,6 +509,7 @@ const AutocompleteField: Component<{
|
|
|
496
509
|
disabled?: boolean
|
|
497
510
|
baseClass: string
|
|
498
511
|
}> = (props) => {
|
|
512
|
+
const strings = useMCPUIStrings()
|
|
499
513
|
const [query, setQuery] = createSignal('')
|
|
500
514
|
const [suggestions, setSuggestions] = createSignal<Array<{ label: string; value: string }>>([])
|
|
501
515
|
const [isOpen, setIsOpen] = createSignal(false)
|
|
@@ -669,7 +683,7 @@ const AutocompleteField: Component<{
|
|
|
669
683
|
<Show when={resolving()}>
|
|
670
684
|
<div class="flex items-center gap-1 mb-1 text-xs text-gray-400">
|
|
671
685
|
<span class="animate-spin h-3 w-3 border border-gray-400 border-t-transparent rounded-full" />
|
|
672
|
-
|
|
686
|
+
{strings.fieldResolving}
|
|
673
687
|
</div>
|
|
674
688
|
</Show>
|
|
675
689
|
|
|
@@ -684,7 +698,7 @@ const AutocompleteField: Component<{
|
|
|
684
698
|
type="button"
|
|
685
699
|
onClick={() => removeChip(val)}
|
|
686
700
|
class="hover:text-blue-900 dark:hover:text-blue-100"
|
|
687
|
-
aria-label={
|
|
701
|
+
aria-label={formatMCPUIString(strings.removeItem, { name: getLabel(val) })}
|
|
688
702
|
>
|
|
689
703
|
×
|
|
690
704
|
</button>
|
|
@@ -701,7 +715,7 @@ const AutocompleteField: Component<{
|
|
|
701
715
|
onFocus={() => { if (suggestions().length) setIsOpen(true) }}
|
|
702
716
|
onBlur={handleBlur}
|
|
703
717
|
placeholder={isMultiple() && selectedValues().length
|
|
704
|
-
?
|
|
718
|
+
? strings.fieldAddMorePlaceholder
|
|
705
719
|
: props.field.placeholder}
|
|
706
720
|
disabled={props.disabled || resolving()}
|
|
707
721
|
class={props.baseClass}
|
|
@@ -746,6 +760,7 @@ const TagsField: Component<{
|
|
|
746
760
|
disabled?: boolean
|
|
747
761
|
baseClass: string
|
|
748
762
|
}> = (props) => {
|
|
763
|
+
const strings = useMCPUIStrings()
|
|
749
764
|
const [input, setInput] = createSignal('')
|
|
750
765
|
|
|
751
766
|
const addTag = () => {
|
|
@@ -778,7 +793,7 @@ const TagsField: Component<{
|
|
|
778
793
|
{(tag) => (
|
|
779
794
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full">
|
|
780
795
|
{tag}
|
|
781
|
-
<button type="button" onClick={() => removeTag(tag)} class="hover:text-blue-900 dark:hover:text-blue-100" aria-label={
|
|
796
|
+
<button type="button" onClick={() => removeTag(tag)} class="hover:text-blue-900 dark:hover:text-blue-100" aria-label={formatMCPUIString(strings.removeItem, { name: tag })}>×</button>
|
|
782
797
|
</span>
|
|
783
798
|
)}
|
|
784
799
|
</For>
|
|
@@ -790,7 +805,7 @@ const TagsField: Component<{
|
|
|
790
805
|
onInput={(e) => setInput(e.currentTarget.value)}
|
|
791
806
|
onKeyDown={handleKeyDown}
|
|
792
807
|
onBlur={addTag}
|
|
793
|
-
placeholder={props.placeholder ||
|
|
808
|
+
placeholder={props.placeholder || strings.fieldTagsPlaceholder}
|
|
794
809
|
disabled={props.disabled}
|
|
795
810
|
class={props.baseClass}
|
|
796
811
|
/>
|