@jini-ai/ui 0.5.4 → 0.5.6
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 +10 -0
- package/dist/core/theme/validation.js +40 -38
- package/dist/features/a2ui/renderer.js +15 -13
- package/dist/features/about/react/components/AboutTab.js +64 -34
- package/dist/features/about/rules.d.ts +2 -2
- package/dist/features/about/rules.js +156 -129
- package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +4 -3
- package/dist/features/admin-widgets/components/ImagePreviewModal.js +6 -10
- package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
- package/dist/features/admin-widgets/components/InfoTip.hooks.js +2 -0
- package/dist/features/admin-widgets/components/InfoTip.js +34 -7
- package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +13 -0
- package/dist/features/admin-widgets/components/InfoTip.rules.js +43 -10
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.d.ts +30 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.d.ts +40 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +128 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.js +18 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.d.ts +14 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.js +12 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.rules.d.ts +20 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.rules.js +21 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.d.ts +18 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.d.ts +16 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +45 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.js +18 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.d.ts +32 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.js +26 -0
- package/dist/features/admin-widgets/components/Select/Select.hooks.d.ts +21 -0
- package/dist/features/admin-widgets/components/Select/Select.hooks.js +24 -2
- package/dist/features/admin-widgets/components/Select/Select.js +4 -3
- package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +29 -14
- package/dist/features/admin-widgets/index.d.ts +14 -2
- package/dist/features/admin-widgets/index.js +7 -1
- package/dist/features/admin-widgets/styles/admin-widgets.css +1 -0
- package/dist/features/asset-grid/react/components/AssetCard.js +7 -1
- package/dist/features/asset-grid/react/components/AssetGrid.d.ts +1 -1
- package/dist/features/asset-grid/react/components/AssetGrid.js +38 -18
- package/dist/features/asset-grid/react/components/useAssetGridFilters.d.ts +21 -0
- package/dist/features/asset-grid/react/components/useAssetGridFilters.js +19 -0
- package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +16 -11
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +2 -0
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +35 -11
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.d.ts +6 -6
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.js +10 -129
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.d.ts +50 -0
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.js +95 -0
- package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.d.ts +56 -0
- package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.js +80 -0
- package/dist/features/asset-tree-browser/rules.js +40 -22
- package/dist/features/browser-chrome/rules.js +74 -70
- package/dist/features/charts/browser.js +11 -5
- package/dist/features/charts/react.js +2 -2
- package/dist/features/charts/table.js +2 -2
- package/dist/features/command-palette/react/hooks/useCommandPalette.js +3 -9
- package/dist/features/connectors/ConnectorsBrowser.d.ts +4 -4
- package/dist/features/connectors/ConnectorsBrowser.js +26 -14
- package/dist/features/connectors/components/ConnectorCard.js +67 -38
- package/dist/features/connectors/components/ConnectorDetailDrawer.js +62 -31
- package/dist/features/connectors/components/ConnectorGrid.js +11 -6
- package/dist/features/connectors/hooks/useConnectorAuthorization.js +6 -0
- package/dist/features/connectors/hooks/useConnectorCatalog.d.ts +2 -0
- package/dist/features/connectors/hooks/useConnectorCatalog.js +32 -13
- package/dist/features/connectors/hooks/useWiredConnectorsBrowser.d.ts +3 -3
- package/dist/features/connectors/rules.d.ts +8 -1
- package/dist/features/connectors/rules.js +42 -20
- package/dist/features/connectors/types.d.ts +11 -0
- package/dist/features/execution/constants.d.ts +4 -10
- package/dist/features/execution/constants.js +9 -94
- package/dist/features/execution/react/components/ByokProviderForm.d.ts +2 -4
- package/dist/features/execution/react/components/ByokProviderForm.js +140 -43
- package/dist/features/execution/react/components/ExecutionTab.d.ts +1 -2
- package/dist/features/execution/react/components/ExecutionTab.js +52 -30
- package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -3
- package/dist/features/execution/react/components/LocalCliAgentList.js +14 -3
- package/dist/features/execution/react/components/LocalCliAgentSettings.js +14 -3
- package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +54 -36
- package/dist/features/execution/react/hooks/useReasoningControl.d.ts +1 -1
- package/dist/features/execution/react/hooks/useReasoningControl.js +116 -75
- package/dist/features/execution/rules.d.ts +13 -2
- package/dist/features/execution/rules.js +51 -11
- package/dist/features/execution/types.d.ts +6 -42
- package/dist/features/execution/types.js +0 -17
- package/dist/features/external-mcp/react/components/ExternalMcpTab.d.ts +6 -2
- package/dist/features/external-mcp/react/components/ExternalMcpTab.js +9 -2
- package/dist/features/file-dropzone/react/components/FileDropzone.js +24 -25
- package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.d.ts +2 -2
- package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.js +35 -30
- package/dist/features/file-dropzone/react/components/FileDropzoneThumbnailGrid.js +30 -24
- package/dist/features/file-dropzone/react/components/FileDropzoneZone.d.ts +14 -0
- package/dist/features/file-dropzone/react/components/FileDropzoneZone.js +26 -0
- package/dist/features/html-editor/canvas-content-wrapper.js +13 -13
- package/dist/features/html-editor/canvas-embed-placeholders.js +46 -37
- package/dist/features/html-editor/css.js +76 -69
- package/dist/features/html-editor/editor-blocks.js +47 -33
- package/dist/features/html-editor/editor-serialization.js +6 -5
- package/dist/features/html-editor/protected-elements.js +39 -28
- package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +4 -4
- package/dist/features/html-editor/source-splice.js +51 -30
- package/dist/features/i18n/dictionaries/settings-dialog.ja.js +1 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +1 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +1 -0
- package/dist/features/i18n/locale.js +20 -16
- package/dist/features/iframe-pool/react/dom-sync.js +8 -6
- package/dist/features/integrations/react/components/CodexInstallToggleButton.js +19 -4
- package/dist/features/integrations/react/components/IntegrationsTab.js +60 -14
- package/dist/features/interactive-ui/interactive-ui.css +1 -1
- package/dist/features/interactive-ui/providers/native/data-table.manifest.d.ts +6 -6
- package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -7
- package/dist/features/interactive-ui/providers/shadcn/data-table.manifest.d.ts +6 -6
- package/dist/features/lexical-rich-text-editor/mention-parser.js +60 -57
- package/dist/features/lexical-rich-text-editor/react/hooks/useKeyboardCommands.js +33 -38
- package/dist/features/lexical-rich-text-editor/rules.js +87 -74
- package/dist/features/lexical-rich-text-editor/serialize.js +27 -25
- package/dist/features/mcp-ui/secret-card.js +111 -61
- package/dist/features/mcp-ui/surfaces/confirmation.js +39 -21
- package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
- package/dist/features/mcp-ui/surfaces/document.js +10 -2
- package/dist/features/mcp-ui/surfaces/fields.js +30 -50
- package/dist/features/mcp-ui/surfaces/form.d.ts +6 -0
- package/dist/features/mcp-ui/surfaces/form.js +43 -21
- package/dist/features/mcp-ui/surfaces/outcome.js +30 -20
- package/dist/features/mcp-ui/surfaces/text-input.js +33 -21
- package/dist/features/mcp-ui/surfaces/tokens.d.ts +6 -1
- package/dist/features/mcp-ui/surfaces/tokens.js +5 -2
- package/dist/features/media-providers/constants.d.ts +1 -0
- package/dist/features/media-providers/constants.js +2 -81
- package/dist/features/media-providers/index.d.ts +1 -1
- package/dist/features/media-providers/ports.d.ts +2 -0
- package/dist/features/media-providers/react/components/MediaProviderCard.d.ts +22 -0
- package/dist/features/media-providers/react/components/MediaProviderCard.js +50 -0
- package/dist/features/media-providers/react/components/MediaProvidersTab.d.ts +5 -6
- package/dist/features/media-providers/react/components/MediaProvidersTab.js +40 -72
- package/dist/features/media-providers/react/components/media-providers-tab-labels.d.ts +11 -0
- package/dist/features/media-providers/react/components/media-providers-tab-labels.js +57 -0
- package/dist/features/media-providers/react/hooks/useMediaProvidersTab.d.ts +4 -2
- package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +37 -23
- package/dist/features/media-providers/rules.js +17 -10
- package/dist/features/media-providers/types.d.ts +4 -22
- package/dist/features/media-providers/types.js +3 -8
- package/dist/features/memory/formatters.js +127 -92
- package/dist/features/memory/react/components/MemoryConnectedPanel.js +92 -36
- package/dist/features/memory/react/components/MemoryList.js +37 -18
- package/dist/features/memory/react/hooks/useMemoryExtractions.store.js +42 -36
- package/dist/features/notifications/react/components/NotificationsTab.js +28 -28
- package/dist/features/observability/long-task.d.ts +0 -12
- package/dist/features/observability/long-task.js +15 -19
- package/dist/features/observability/resource-error.d.ts +0 -10
- package/dist/features/observability/resource-error.js +18 -13
- package/dist/features/observability/stuck-run.js +10 -6
- package/dist/features/observability/telemetry-url.d.ts +6 -0
- package/dist/features/observability/telemetry-url.js +16 -0
- package/dist/features/observability/white-screen.js +12 -24
- package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
- package/dist/features/panel-kit/fetch-query/adapter.react.js +4 -2
- package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +46 -33
- package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +3 -1
- package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +3 -1
- package/dist/features/privacy/react/components/PrivacyTab.js +24 -13
- package/dist/features/progress-card/components/ProgressCard.js +7 -3
- package/dist/features/progress-card/reference-adapters.js +59 -54
- package/dist/features/project-locations/react/components/ProjectLocationsTab.js +24 -15
- package/dist/features/project-locations/react/hooks/useProjectLocationsTab.d.ts +8 -0
- package/dist/features/project-locations/react/hooks/useProjectLocationsTab.js +31 -23
- package/dist/features/resource-dashboard/react/components/ResourceBoard.js +6 -47
- package/dist/features/resource-dashboard/react/components/ResourceBoardView.d.ts +2 -2
- package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -6
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +15 -0
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +35 -0
- package/dist/features/resource-dashboard/react/components/ResourceCard.js +5 -16
- package/dist/features/resource-dashboard/react/components/ResourceCardParts.d.ts +34 -0
- package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +34 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowList.js +3 -1
- package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +2 -2
- package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.d.ts +19 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +11 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListView.d.ts +1 -1
- package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +4 -4
- package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.d.ts +10 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.js +20 -0
- package/dist/features/resource-dashboard/react/components/resourceBoardProps.d.ts +48 -0
- package/dist/features/resource-dashboard/react/components/resourceBoardProps.js +70 -0
- package/dist/features/resource-dashboard/react/components/resourceRowListProps.d.ts +19 -0
- package/dist/features/resource-dashboard/react/components/resourceRowListProps.js +14 -0
- package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.d.ts +16 -0
- package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.js +13 -0
- package/dist/features/resource-dashboard/rules.js +2 -8
- package/dist/features/sketch-editor/dom.js +150 -99
- package/dist/features/sketch-editor/react/components/SketchEditor.js +26 -15
- package/dist/features/skills/react/components/SkillDraftForm.js +13 -19
- package/dist/features/skills/react/components/SkillDraftForm.labels.d.ts +31 -0
- package/dist/features/skills/react/components/SkillDraftForm.labels.js +54 -0
- package/dist/features/skills/react/components/SkillRow.js +38 -18
- package/dist/features/skills/react/components/SkillRow.labels.d.ts +7 -0
- package/dist/features/skills/react/components/SkillRow.labels.js +18 -0
- package/dist/features/skills/react/components/SkillsTab.js +24 -14
- package/dist/features/skills/react/components/SkillsTab.labels.d.ts +7 -0
- package/dist/features/skills/react/components/SkillsTab.labels.js +11 -0
- package/dist/features/skills/rules.js +32 -23
- package/dist/features/source-config-list/agent-handles.js +2 -1
- package/dist/features/source-config-list/dependencies.js +53 -49
- package/dist/features/source-config-list/index.d.ts +3 -3
- package/dist/features/source-config-list/react/components/SourceConfigAddForm.d.ts +6 -5
- package/dist/features/source-config-list/react/components/SourceConfigAddForm.js +38 -7
- package/dist/features/source-config-list/react/components/SourceConfigField.js +69 -25
- package/dist/features/source-config-list/react/components/SourceConfigItemCard.d.ts +6 -5
- package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +76 -20
- package/dist/features/source-config-list/react/components/SourceConfigListView.d.ts +5 -3
- package/dist/features/source-config-list/react/components/SourceConfigListView.js +33 -3
- package/dist/features/source-config-list/react/components/SourceConfigTestControl.js +17 -1
- package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.d.ts +11 -0
- package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.js +12 -0
- package/dist/features/source-config-list/types.d.ts +14 -0
- package/dist/features/tab-launcher-menu/react/components/TabLauncherMenu.js +25 -1
- package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +15 -16
- package/dist/features/tab-strip/react/components/TabBar.js +8 -1
- package/dist/features/tabbed-dialog/react/components/TabbedDialog.js +8 -30
- package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.d.ts +15 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.js +8 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.d.ts +12 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.js +6 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.d.ts +27 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.js +12 -0
- package/dist/features/tabbed-dialog/react/components/tabbedDialogView.d.ts +43 -0
- package/dist/features/tabbed-dialog/react/components/tabbedDialogView.js +59 -0
- package/dist/features/version-manager/dependencies.d.ts +3 -1
- package/dist/features/version-manager/dependencies.js +3 -2
- package/dist/features/version-manager/react/components/VersionManagerModal.js +10 -6
- package/dist/features/version-manager/react/hooks/useVersionManager.js +7 -4
- package/dist/features/viewer-shell/react/components/CommentSidePanel.js +103 -50
- package/dist/features/viewer-shell/react/components/MarkdownSplitPane.js +28 -8
- package/dist/features/viewer-shell/react/hooks/useCommentReorder.js +22 -16
- package/dist/features/viewer-shell/rules.js +19 -23
- package/dist/react/components/CustomSelect.d.ts +38 -1
- package/dist/react/components/CustomSelect.js +94 -38
- package/dist/react/components/OnboardingDropdown.js +20 -8
- package/dist/react/components/RemixIcon.stylesheet.js +3 -1
- package/dist/react/components/Toast.js +13 -2
- package/dist/react/components/TooltipLayer.js +5 -14
- package/dist/react/components/WorkingDirPicker.js +78 -50
- package/dist/react/components/remixicon-font-recovery.js +38 -30
- package/dist/react/components/select-popover.d.ts +8 -6
- package/dist/react/components/select-popover.js +33 -29
- package/dist/react/mcp-ui/McpUiHost.d.ts +1 -1
- package/dist/react/mcp-ui/McpUiHost.js +10 -11
- package/dist/react/mcp-ui/host-message-source.d.ts +14 -2
- package/dist/react/mcp-ui/host-message-source.js +15 -5
- package/dist/react/mcp-ui/mcp-ui-host-frame.d.ts +41 -0
- package/dist/react/mcp-ui/mcp-ui-host-frame.js +39 -0
- package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +97 -69
- package/dist/renderers/preview-modal-shell/react/components/PreviewModalShell.js +59 -30
- package/dist/renderers/preview-modal-shell/react/components/icons.js +4 -1
- package/dist/renderers/react/components/ArtifactView.js +9 -6
- package/dist/renderers/srcdoc/build.d.ts +4 -1
- package/dist/renderers/srcdoc/build.js +62 -48
- package/dist/styles/admin.css +29 -14
- package/dist/styles/lightbox.css +142 -0
- package/dist/styles/variables.css +128 -0
- package/dist/theme/apply.js +9 -5
- package/dist/utils/auto-open-file.d.ts +2 -1
- package/dist/utils/auto-open-file.js +69 -85
- package/dist/utils/markdown-scroll-sync.js +108 -87
- package/dist/utils/polygon-selection.js +25 -26
- package/dist/utils/smooth-scroll-to-top.d.ts +1 -1
- package/dist/utils/smooth-scroll-to-top.js +2 -32
- package/dist/utils/sse.js +26 -26
- package/dist/utils/unit-bezier.d.ts +7 -0
- package/dist/utils/unit-bezier.js +38 -0
- package/package.json +7 -10
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +0 -46
|
@@ -110,43 +110,47 @@ function confirmationActions(spec) {
|
|
|
110
110
|
actions.push({ id: 'cancel', label: spec.cancel.label, variant: 'neutral' });
|
|
111
111
|
return actions;
|
|
112
112
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if ((spec.choices ?? []).length
|
|
113
|
+
/** The ticked ids are merged into confirm.params at click time; a key already there (the token,
|
|
114
|
+
* say) would be silently replaced by an array.
|
|
115
|
+
* @complexity O(a) in the alternative count. @throws Error naming the colliding param (and alternative). */
|
|
116
|
+
function assertChoicesParamFree(spec, required) {
|
|
117
|
+
const { choicesParam } = required;
|
|
118
|
+
if ((spec.choices ?? []).length === 0)
|
|
119
|
+
return;
|
|
120
|
+
if (Object.prototype.hasOwnProperty.call(spec.confirm.params, choicesParam)) {
|
|
119
121
|
throw new Error(`choicesParam ${JSON.stringify(choicesParam)} collides with a key already in confirm.params.`);
|
|
120
122
|
}
|
|
121
|
-
for (const alternative of
|
|
123
|
+
for (const alternative of spec.alternatives ?? []) {
|
|
122
124
|
if (Object.prototype.hasOwnProperty.call(alternative.params, choicesParam)) {
|
|
123
125
|
throw new Error(`choicesParam ${JSON.stringify(choicesParam)} collides with a key already in the params of alternative ${JSON.stringify(alternative.id)}.`);
|
|
124
126
|
}
|
|
125
127
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const affirmative = new Set(['confirm', ...alternatives.map((alternative) => alternative.id)]);
|
|
128
|
+
}
|
|
129
|
+
/** Header, context, choices, warning, actions and the status region. @complexity O(n) in the rendered length. */
|
|
130
|
+
function confirmationBodyHtml(spec, required) {
|
|
131
|
+
const { affirmative } = required;
|
|
131
132
|
const warning = spec.warning === undefined ? '' : `<p class="mcpui-warning">${escapeHtml(spec.warning)}</p>`;
|
|
132
|
-
|
|
133
|
+
return [
|
|
133
134
|
renderSurfaceHeader(spec.description === undefined ? { title: spec.title } : { title: spec.title, description: spec.description }),
|
|
134
135
|
renderDetailList(spec.details ?? []),
|
|
135
136
|
renderChoices(spec.choices ?? []),
|
|
136
137
|
warning,
|
|
137
138
|
// Confirm starts disabled; the script enables it once the dwell has run out.
|
|
138
|
-
renderActions(
|
|
139
|
+
renderActions(confirmationActions(spec).map((action) => (affirmative.has(action.id) ? { ...action, disabled: true } : action))),
|
|
139
140
|
renderStatusRegion(),
|
|
140
141
|
]
|
|
141
142
|
.filter((fragment) => fragment !== '')
|
|
142
143
|
.join('\n');
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
}
|
|
145
|
+
/** A map from button id to what that button does. `null` means "dismiss locally, call nothing" —
|
|
146
|
+
* the shape a cancel-without-a-tool takes, kept as an explicit null rather than an absent key so
|
|
147
|
+
* the script can tell "this button dismisses" from "this button is unknown to me".
|
|
148
|
+
* @complexity O(a) in the alternative count. */
|
|
149
|
+
function confirmationPlan(spec, required) {
|
|
146
150
|
const plan = {
|
|
147
151
|
confirm: { toolName: spec.confirm.toolName, params: spec.confirm.params },
|
|
148
152
|
};
|
|
149
|
-
for (const alternative of alternatives) {
|
|
153
|
+
for (const alternative of required.alternatives) {
|
|
150
154
|
plan[alternative.id] = { toolName: alternative.toolName, params: alternative.params };
|
|
151
155
|
}
|
|
152
156
|
if (spec.cancel !== undefined) {
|
|
@@ -154,7 +158,12 @@ export function renderConfirmationDocument(spec) {
|
|
|
154
158
|
? { toolName: spec.cancel.toolName, params: spec.cancel.params }
|
|
155
159
|
: null;
|
|
156
160
|
}
|
|
157
|
-
|
|
161
|
+
return plan;
|
|
162
|
+
}
|
|
163
|
+
/** The inline runtime script: dwell, visibility, locking and tool calls. @complexity O(s) in the script length. */
|
|
164
|
+
function confirmationScript(required) {
|
|
165
|
+
const { plan, text, choicesParam, affirmative } = required;
|
|
166
|
+
return `(function () {
|
|
158
167
|
"use strict";
|
|
159
168
|
${SURFACE_SCRIPT_PRELUDE}
|
|
160
169
|
var PLAN = ${escapeJsValue(plan)};
|
|
@@ -303,10 +312,19 @@ ${SURFACE_SCRIPT_PRELUDE}
|
|
|
303
312
|
});
|
|
304
313
|
}
|
|
305
314
|
}());`;
|
|
315
|
+
}
|
|
316
|
+
export function renderConfirmationDocument(spec) {
|
|
317
|
+
const text = { ...DEFAULT_SURFACE_STATUS_TEXT, ...spec.text };
|
|
318
|
+
const choicesParam = spec.choicesParam ?? 'overwrite';
|
|
319
|
+
assertChoicesParamFree(spec, { choicesParam });
|
|
320
|
+
const alternatives = spec.alternatives ?? [];
|
|
321
|
+
assertAlternativeIds(alternatives);
|
|
322
|
+
// Every affirmative button starts disabled and waits out the dwell; only cancel is live at once.
|
|
323
|
+
const affirmative = new Set(['confirm', ...alternatives.map((alternative) => alternative.id)]);
|
|
306
324
|
return renderSurfaceDocument({
|
|
307
325
|
title: spec.title,
|
|
308
|
-
bodyHtml,
|
|
309
|
-
script,
|
|
326
|
+
bodyHtml: confirmationBodyHtml(spec, { affirmative }),
|
|
327
|
+
script: confirmationScript({ plan: confirmationPlan(spec, { alternatives }), text, choicesParam, affirmative }),
|
|
310
328
|
app: spec.app ?? DEFAULT_APP,
|
|
311
329
|
...(spec.lang === undefined ? {} : { lang: spec.lang }),
|
|
312
330
|
...(spec.tokens === undefined ? {} : { tokens: spec.tokens }),
|
|
@@ -8,7 +8,7 @@ import { type SurfaceTokenName } from './tokens.js';
|
|
|
8
8
|
* be a color no caller could reskin. `var(--x, fallback)` appears only where a value is genuinely
|
|
9
9
|
* optional.
|
|
10
10
|
*/
|
|
11
|
-
export declare const SURFACE_BASE_CSS = "*, *::before, *::after { box-sizing: border-box; }\nhtml, body { margin: 0; padding: 0; }\nbody {\n /* Transparent, not var(--jini-mcpui-bg): this document has no page of its own to paint. It is an\n allow-scripts-only sandboxed iframe embedded directly in a host's chat transcript, and html's\n background is left at its (also transparent) initial value, so the host's own pane shows\n straight through to .mcpui-surface below rather than through a second, unrelated rectangle of\n fill color sitting between the two. --jini-mcpui-bg stays declared for a caller styling\n something else via extraCss -- nothing in this stylesheet reads it anymore. */\n background: transparent;\n color: var(--jini-mcpui-text);\n font-family: var(--jini-mcpui-font);\n font-size: 14px;\n line-height: 1.5;\n padding: 16px;\n -webkit-font-smoothing: antialiased;\n}\n.mcpui-surface {\n background: linear-gradient(135deg,\n color-mix(in srgb, var(--jini-mcpui-accent) 5%, var(--jini-mcpui-panel)) 0%,\n var(--jini-mcpui-panel) 60%);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 18%, var(--jini-mcpui-border));\n border-radius: var(--jini-mcpui-radius-md);\n box-shadow: var(--jini-mcpui-surface-shadow);\n padding: 18px 20px 16px;\n max-width: 640px;\n margin: 0 auto;\n}\n.mcpui-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-description { margin: 6px 0 0; color: var(--jini-mcpui-text-muted); }\n.mcpui-details {\n display: grid;\n grid-template-columns:
|
|
11
|
+
export declare const SURFACE_BASE_CSS = "*, *::before, *::after { box-sizing: border-box; }\nhtml, body { margin: 0; padding: 0; }\nbody {\n /* Transparent, not var(--jini-mcpui-bg): this document has no page of its own to paint. It is an\n allow-scripts-only sandboxed iframe embedded directly in a host's chat transcript, and html's\n background is left at its (also transparent) initial value, so the host's own pane shows\n straight through to .mcpui-surface below rather than through a second, unrelated rectangle of\n fill color sitting between the two. --jini-mcpui-bg stays declared for a caller styling\n something else via extraCss -- nothing in this stylesheet reads it anymore. */\n background: transparent;\n color: var(--jini-mcpui-text);\n font-family: var(--jini-mcpui-font);\n font-size: 14px;\n line-height: 1.5;\n padding: 16px;\n -webkit-font-smoothing: antialiased;\n}\n.mcpui-surface {\n background: linear-gradient(135deg,\n color-mix(in srgb, var(--jini-mcpui-accent) 5%, var(--jini-mcpui-panel)) 0%,\n var(--jini-mcpui-panel) 60%);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 18%, var(--jini-mcpui-border));\n border-radius: var(--jini-mcpui-radius-md);\n box-shadow: var(--jini-mcpui-surface-shadow);\n padding: 18px 20px 16px;\n max-width: 640px;\n margin: 0 auto;\n}\n.mcpui-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-description { margin: 6px 0 0; color: var(--jini-mcpui-text-muted); }\n/* fit-content(40%), not max-content: one long label used to take the whole row and squeeze every\n value to one character per line. Labels wrap at 40% instead. */\n.mcpui-details {\n display: grid;\n grid-template-columns: fit-content(40%) minmax(0, 1fr);\n gap: 4px 14px;\n margin: 14px 0 0;\n}\n.mcpui-details dt { color: var(--jini-mcpui-text-soft); font-weight: 500; overflow-wrap: anywhere; }\n.mcpui-details dd { margin: 0; overflow-wrap: anywhere; color: var(--jini-mcpui-text); }\n.mcpui-details-outcome { display: flex; flex-direction: column; gap: 8px; }\n.mcpui-outcome-row { display: flex; align-items: flex-start; gap: 14px; }\n.mcpui-outcome-row dt { flex: 1; min-width: 0; overflow-wrap: anywhere; }\n.mcpui-outcome-row dt .mcpui-hint { display: block; font-weight: 400; }\n.mcpui-outcome-row dd { flex: 1; min-width: 0; }\n/* Capped so a long status cannot squeeze the label back to one character per line; the full text\n stays in the cell's title. */\n.mcpui-outcome-row dd.mcpui-detail-status {\n flex: 0 1 auto; max-width: 60%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n/* Frames up to 480px: stack each label above its value so the value gets the full width.\n Outcome lists keep their own row layout. */\n@media (max-width: 480px) {\n .mcpui-details:not(.mcpui-details-outcome) { grid-template-columns: minmax(0, 1fr); row-gap: 0; }\n .mcpui-details:not(.mcpui-details-outcome) dd { margin-bottom: 8px; }\n}\n/* The iframe has 16px body padding on each side: 352px viewport = 320px card. Stacked, the value\n has the full width, so it wraps between words instead of truncating. */\n@media (max-width: 352px) {\n .mcpui-outcome-row { flex-direction: column; gap: 2px; }\n .mcpui-outcome-row dd.mcpui-detail-status { max-width: 100%; white-space: normal; overflow-wrap: normal; }\n}\n.mcpui-code {\n margin: 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n max-height: 16em;\n overflow: auto;\n font-family: var(--jini-mcpui-font-mono);\n font-size: 12.5px;\n line-height: 1.45;\n padding: 6px 8px;\n border: 1px solid var(--jini-mcpui-border);\n border-radius: var(--jini-mcpui-radius-sm);\n background: var(--jini-mcpui-bg);\n}\n.mcpui-warning {\n margin: 14px 0 0;\n padding: 9px 11px;\n border-radius: var(--jini-mcpui-radius-md);\n border: 1px solid var(--jini-mcpui-danger);\n background: var(--jini-mcpui-danger-tint);\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-fields { display: flex; flex-direction: column; gap: 12px; margin: 16px 0 0; border: 0; padding: 0; }\n.mcpui-field { display: flex; flex-direction: column; gap: 4px; }\n.mcpui-field-inline { flex-direction: row; align-items: flex-start; gap: 8px; }\n.mcpui-label { font-weight: 500; color: var(--jini-mcpui-text-strong); }\n.mcpui-required { color: var(--jini-mcpui-accent); margin-left: 2px; }\n.mcpui-hint { color: var(--jini-mcpui-text-soft); font-size: 13px; }\n.mcpui-input, .mcpui-select, .mcpui-textarea {\n font: inherit;\n color: var(--jini-mcpui-text);\n background: var(--jini-mcpui-panel);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n border-radius: var(--jini-mcpui-radius-sm);\n padding: 7px 9px;\n width: 100%;\n min-width: 0;\n}\n.mcpui-textarea { resize: vertical; min-height: 72px; font-family: var(--jini-mcpui-font); }\n.mcpui-checkbox { margin: 3px 0 0; accent-color: var(--jini-mcpui-accent); }\n.mcpui-choice-group { border: 0; padding: 0; margin: 0; min-width: 0; }\n.mcpui-choice { display: flex; flex-direction: row; align-items: flex-start; gap: 8px; margin: 6px 0 0; }\n.mcpui-choice-label { color: var(--jini-mcpui-text); font-weight: 400; }\n.mcpui-input:focus-visible, .mcpui-select:focus-visible, .mcpui-textarea:focus-visible,\n.mcpui-checkbox:focus-visible, .mcpui-button:focus-visible {\n outline: 2px solid var(--jini-mcpui-accent-ring);\n outline-offset: 1px;\n border-color: var(--jini-mcpui-accent);\n}\n.mcpui-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 0; }\n.mcpui-button {\n font: inherit;\n font-weight: 500;\n padding: 7px 15px;\n border-radius: var(--jini-mcpui-radius-pill);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n background: var(--jini-mcpui-panel);\n color: var(--jini-mcpui-text);\n cursor: pointer;\n}\n.mcpui-button:hover:not([disabled]) { background: var(--jini-mcpui-accent-tint); }\n.mcpui-button-primary {\n background: var(--jini-mcpui-accent);\n border-color: var(--jini-mcpui-accent);\n color: var(--jini-mcpui-accent-ink);\n}\n.mcpui-button-primary:hover:not([disabled]) { background: var(--jini-mcpui-accent-hover); border-color: var(--jini-mcpui-accent-hover); }\n.mcpui-button-danger {\n background: var(--jini-mcpui-danger);\n border-color: var(--jini-mcpui-danger);\n color: var(--jini-mcpui-panel);\n}\n.mcpui-button-danger:hover:not([disabled]) { background: var(--jini-mcpui-danger-hover); border-color: var(--jini-mcpui-danger-hover); }\n.mcpui-button[disabled] { opacity: 0.55; cursor: default; }\n.mcpui-status { margin: 12px 0 0; min-height: 1.4em; color: var(--jini-mcpui-text-muted); }\n.mcpui-status[data-state=\"failed\"], .mcpui-status[data-state=\"invalid\"], .mcpui-status[data-state=\"expired\"] { color: var(--jini-mcpui-danger); }\n.mcpui-status[data-state=\"done\"] { color: var(--jini-mcpui-text-strong); }\n/* Neither a plain success nor a failure \u2014 e.g. an outcome surface reporting \"uploaded, not yet\n reachable\" (outcome.ts's own state: partial). Distinct from both --jini-mcpui-danger (would\n wrongly read as \"this failed\") and --jini-mcpui-text-strong (would wrongly read as \"this is\n fully done\"); the accent token reads as \"worth a second look\" without asserting either. */\n.mcpui-status[data-state=\"partial\"] { color: var(--jini-mcpui-accent); }\n.mcpui-code { font-family: var(--jini-mcpui-font-mono); font-size: 13px; color: var(--jini-mcpui-text-faint); }";
|
|
12
12
|
/**
|
|
13
13
|
* The Content-Security-Policy every generated surface declares in its own `<head>`.
|
|
14
14
|
*
|
|
@@ -59,13 +59,15 @@ body {
|
|
|
59
59
|
color: var(--jini-mcpui-text-strong);
|
|
60
60
|
}
|
|
61
61
|
.mcpui-description { margin: 6px 0 0; color: var(--jini-mcpui-text-muted); }
|
|
62
|
+
/* fit-content(40%), not max-content: one long label used to take the whole row and squeeze every
|
|
63
|
+
value to one character per line. Labels wrap at 40% instead. */
|
|
62
64
|
.mcpui-details {
|
|
63
65
|
display: grid;
|
|
64
|
-
grid-template-columns:
|
|
66
|
+
grid-template-columns: fit-content(40%) minmax(0, 1fr);
|
|
65
67
|
gap: 4px 14px;
|
|
66
68
|
margin: 14px 0 0;
|
|
67
69
|
}
|
|
68
|
-
.mcpui-details dt { color: var(--jini-mcpui-text-soft); font-weight: 500; }
|
|
70
|
+
.mcpui-details dt { color: var(--jini-mcpui-text-soft); font-weight: 500; overflow-wrap: anywhere; }
|
|
69
71
|
.mcpui-details dd { margin: 0; overflow-wrap: anywhere; color: var(--jini-mcpui-text); }
|
|
70
72
|
.mcpui-details-outcome { display: flex; flex-direction: column; gap: 8px; }
|
|
71
73
|
.mcpui-outcome-row { display: flex; align-items: flex-start; gap: 14px; }
|
|
@@ -77,6 +79,12 @@ body {
|
|
|
77
79
|
.mcpui-outcome-row dd.mcpui-detail-status {
|
|
78
80
|
flex: 0 1 auto; max-width: 60%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
79
81
|
}
|
|
82
|
+
/* Frames up to 480px: stack each label above its value so the value gets the full width.
|
|
83
|
+
Outcome lists keep their own row layout. */
|
|
84
|
+
@media (max-width: 480px) {
|
|
85
|
+
.mcpui-details:not(.mcpui-details-outcome) { grid-template-columns: minmax(0, 1fr); row-gap: 0; }
|
|
86
|
+
.mcpui-details:not(.mcpui-details-outcome) dd { margin-bottom: 8px; }
|
|
87
|
+
}
|
|
80
88
|
/* The iframe has 16px body padding on each side: 352px viewport = 320px card. Stacked, the value
|
|
81
89
|
has the full width, so it wraps between words instead of truncating. */
|
|
82
90
|
@media (max-width: 352px) {
|
|
@@ -18,15 +18,32 @@ import { renderCheckbox } from './checkbox.js';
|
|
|
18
18
|
import { renderChoiceGroup } from './choice-group.js';
|
|
19
19
|
import { renderSelect } from './select.js';
|
|
20
20
|
import { renderTextInput } from './text-input.js';
|
|
21
|
-
/**
|
|
21
|
+
/** The listed properties that are present on `source`, in listed order.
|
|
22
|
+
* Only the properties present on a given field are forwarded, because `exactOptionalPropertyTypes` makes `{ hint: undefined }` and `{}` different types.
|
|
23
|
+
* @complexity O(k) in the key count. */
|
|
24
|
+
function present(source, keys) {
|
|
25
|
+
const picked = {};
|
|
26
|
+
for (const key of keys) {
|
|
27
|
+
const value = source[key];
|
|
28
|
+
if (value !== undefined)
|
|
29
|
+
picked[key] = value;
|
|
30
|
+
}
|
|
31
|
+
return picked;
|
|
32
|
+
}
|
|
22
33
|
function base(field) {
|
|
23
|
-
return {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
return { name: field.name, label: field.label, ...present(field, ['hint', 'required', 'disabled']) };
|
|
35
|
+
}
|
|
36
|
+
/** One-of-N, drawn as radios or a select per {@link EnumField.presentation}. @complexity O(n) in the rendered length. */
|
|
37
|
+
function renderEnumField(field) {
|
|
38
|
+
if (field.presentation === 'radio') {
|
|
39
|
+
return renderChoiceGroup({
|
|
40
|
+
...base(field),
|
|
41
|
+
selection: 'single',
|
|
42
|
+
options: field.options,
|
|
43
|
+
...(field.value === undefined ? {} : { value: [field.value] }),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return renderSelect({ ...base(field), options: field.options, ...present(field, ['value', 'placeholder']) });
|
|
30
47
|
}
|
|
31
48
|
/**
|
|
32
49
|
* Renders the control fragment for one field, dispatching on its kind.
|
|
@@ -39,52 +56,15 @@ function base(field) {
|
|
|
39
56
|
export function renderFieldControl(field, text) {
|
|
40
57
|
switch (field.kind) {
|
|
41
58
|
case 'string':
|
|
42
|
-
return renderTextInput({
|
|
43
|
-
...base(field),
|
|
44
|
-
...text,
|
|
45
|
-
...(field.value === undefined ? {} : { value: field.value }),
|
|
46
|
-
...(field.placeholder === undefined ? {} : { placeholder: field.placeholder }),
|
|
47
|
-
...(field.multiline === undefined ? {} : { multiline: field.multiline }),
|
|
48
|
-
...(field.rows === undefined ? {} : { rows: field.rows }),
|
|
49
|
-
...(field.secret === undefined ? {} : { secret: field.secret }),
|
|
50
|
-
});
|
|
59
|
+
return renderTextInput({ ...base(field), ...text, ...present(field, ['value', 'placeholder', 'multiline', 'rows', 'secret']) });
|
|
51
60
|
case 'number':
|
|
52
|
-
return renderTextInput({
|
|
53
|
-
...base(field),
|
|
54
|
-
inputType: 'number',
|
|
55
|
-
...(field.value === undefined ? {} : { value: field.value }),
|
|
56
|
-
...(field.placeholder === undefined ? {} : { placeholder: field.placeholder }),
|
|
57
|
-
...(field.min === undefined ? {} : { min: field.min }),
|
|
58
|
-
...(field.max === undefined ? {} : { max: field.max }),
|
|
59
|
-
...(field.step === undefined ? {} : { step: field.step }),
|
|
60
|
-
});
|
|
61
|
+
return renderTextInput({ ...base(field), inputType: 'number', ...present(field, ['value', 'placeholder', 'min', 'max', 'step']) });
|
|
61
62
|
case 'boolean':
|
|
62
|
-
return renderCheckbox({
|
|
63
|
-
...base(field),
|
|
64
|
-
...(field.value === undefined ? {} : { value: field.value }),
|
|
65
|
-
});
|
|
63
|
+
return renderCheckbox({ ...base(field), ...present(field, ['value']) });
|
|
66
64
|
case 'enum':
|
|
67
|
-
|
|
68
|
-
return renderChoiceGroup({
|
|
69
|
-
...base(field),
|
|
70
|
-
selection: 'single',
|
|
71
|
-
options: field.options,
|
|
72
|
-
...(field.value === undefined ? {} : { value: [field.value] }),
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
return renderSelect({
|
|
76
|
-
...base(field),
|
|
77
|
-
options: field.options,
|
|
78
|
-
...(field.value === undefined ? {} : { value: field.value }),
|
|
79
|
-
...(field.placeholder === undefined ? {} : { placeholder: field.placeholder }),
|
|
80
|
-
});
|
|
65
|
+
return renderEnumField(field);
|
|
81
66
|
case 'multi-enum':
|
|
82
|
-
return renderChoiceGroup({
|
|
83
|
-
...base(field),
|
|
84
|
-
selection: 'multiple',
|
|
85
|
-
options: field.options,
|
|
86
|
-
...(field.value === undefined ? {} : { value: field.value }),
|
|
87
|
-
});
|
|
67
|
+
return renderChoiceGroup({ ...base(field), selection: 'multiple', options: field.options, ...present(field, ['value']) });
|
|
88
68
|
}
|
|
89
69
|
}
|
|
90
70
|
/** Projects fields down to what the inline script needs, so the generated JS carries no labels, hints, or option lists it will never read. */
|
|
@@ -42,6 +42,12 @@ export interface FormSurfaceSpec {
|
|
|
42
42
|
/** Styles the submit button as destructive. */
|
|
43
43
|
readonly danger?: boolean;
|
|
44
44
|
readonly submitLabel: string;
|
|
45
|
+
/** Alternative submit buttons collect the same fields, with explicit action params. */
|
|
46
|
+
readonly additionalSubmitActions?: readonly {
|
|
47
|
+
readonly id: string;
|
|
48
|
+
readonly label: string;
|
|
49
|
+
readonly params: Readonly<Record<string, unknown>>;
|
|
50
|
+
}[];
|
|
45
51
|
/** The tool the collected values are sent to. */
|
|
46
52
|
readonly toolName: string;
|
|
47
53
|
/**
|
|
@@ -31,51 +31,61 @@ import { DEFAULT_SECRET_INPUT_TEXT } from './text-input.js';
|
|
|
31
31
|
const DEFAULT_APP = { appName: 'jini-mcp-ui-form', appVersion: '1' };
|
|
32
32
|
/** The form element's id, fixed so the emitted script can find it without a fragile selector. */
|
|
33
33
|
const FORM_ELEMENT_ID = 'mcpui-form';
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
* @returns The full HTML string.
|
|
39
|
-
* @complexity O(n) in the number of fields plus the rendered length.
|
|
40
|
-
*/
|
|
41
|
-
export function renderFormDocument(spec) {
|
|
42
|
-
// A base param with a secret field's name would leak the same value through the emitted script,
|
|
43
|
-
// even though the collected value overrides it at submit time. Reject that hidden prefill too.
|
|
34
|
+
/** A base param with a secret field's name would leak the same value through the emitted script,
|
|
35
|
+
* even though the collected value overrides it at submit time. Reject that hidden prefill too.
|
|
36
|
+
* @complexity O(n) in the field count. @throws Error with fixed text, never the field name or value. */
|
|
37
|
+
function assertNoSecretBaseParam(spec) {
|
|
44
38
|
for (const field of spec.fields) {
|
|
45
39
|
if (field.kind === 'string' && field.secret === true && spec.baseParams?.[field.name] !== undefined) {
|
|
46
40
|
throw new Error('Secret fields cannot have a pre-filled value. Omit value.');
|
|
47
41
|
}
|
|
48
42
|
}
|
|
49
|
-
|
|
43
|
+
}
|
|
44
|
+
/** Submit, any alternative submits, then cancel. @complexity O(a) in the action count. */
|
|
45
|
+
function formActions(spec) {
|
|
50
46
|
// Not `type: 'submit'` — see the runtime script below for why a submit-typed button inside this
|
|
51
47
|
// particular `<form>` cannot work at all in production.
|
|
52
48
|
const actions = [
|
|
53
49
|
{ id: 'submit', label: spec.submitLabel, variant: spec.danger === true ? 'danger' : 'primary' },
|
|
54
50
|
];
|
|
51
|
+
for (const action of spec.additionalSubmitActions ?? [])
|
|
52
|
+
actions.push({ id: `submit:${action.id}`, label: action.label, variant: 'neutral' });
|
|
55
53
|
if (spec.cancel !== undefined)
|
|
56
54
|
actions.push({ id: 'cancel', label: spec.cancel.label, variant: 'neutral' });
|
|
55
|
+
return actions;
|
|
56
|
+
}
|
|
57
|
+
/** Header, context, the `<form>` itself and the status region. @complexity O(n) in the rendered length. */
|
|
58
|
+
function formBodyHtml(spec, text) {
|
|
57
59
|
const controls = spec.fields
|
|
58
60
|
.map((field) => renderFieldControl(field, { showSecret: text.showSecret, hideSecret: text.hideSecret }))
|
|
59
61
|
.join('\n');
|
|
60
62
|
const warning = spec.warning === undefined ? '' : `<p class="mcpui-warning">${escapeHtml(spec.warning)}</p>`;
|
|
61
|
-
|
|
63
|
+
return [
|
|
62
64
|
renderSurfaceHeader(spec.description === undefined ? { title: spec.title } : { title: spec.title, description: spec.description }),
|
|
63
65
|
renderDetailList(spec.details ?? []),
|
|
64
|
-
`<form id="${FORM_ELEMENT_ID}" novalidate>\n<fieldset class="mcpui-fields">\n${controls}\n</fieldset>\n${warning}\n${renderActions(
|
|
66
|
+
`<form id="${FORM_ELEMENT_ID}" novalidate>\n<fieldset class="mcpui-fields">\n${controls}\n</fieldset>\n${warning}\n${renderActions(formActions(spec))}\n</form>`,
|
|
65
67
|
renderStatusRegion(),
|
|
66
68
|
]
|
|
67
69
|
.filter((fragment) => fragment !== '')
|
|
68
70
|
.join('\n');
|
|
69
|
-
|
|
71
|
+
}
|
|
72
|
+
/** The cancel tool call, or null for a cancel that only tears the surface down. @complexity O(1). */
|
|
73
|
+
function cancelPlanOf(spec) {
|
|
74
|
+
return spec.cancel?.toolName === undefined
|
|
70
75
|
? null
|
|
71
76
|
: { toolName: spec.cancel.toolName, params: spec.cancel.params ?? {} };
|
|
72
|
-
|
|
77
|
+
}
|
|
78
|
+
/** The inline runtime script that reads, validates and submits the fields. @complexity O(n) in the field count plus the script length. */
|
|
79
|
+
function formScript(spec, text) {
|
|
80
|
+
const cancelPlan = cancelPlanOf(spec);
|
|
81
|
+
return `(function () {
|
|
73
82
|
"use strict";
|
|
74
83
|
${SURFACE_SCRIPT_PRELUDE}
|
|
75
84
|
var FIELDS = ${escapeJsValue(toFieldReadSpecs(spec.fields))};
|
|
76
85
|
var TOOL = ${escapeJsValue(spec.toolName)};
|
|
77
86
|
var BASE_PARAMS = ${escapeJsValue(spec.baseParams ?? {})};
|
|
78
87
|
var CANCEL = ${escapeJsValue(cancelPlan)};
|
|
88
|
+
var SUBMIT_ACTIONS = ${escapeJsValue(Object.fromEntries((spec.additionalSubmitActions ?? []).map(action => [`submit:${action.id}`, action.params])))};
|
|
79
89
|
var TEXT = ${escapeJsValue(text)};
|
|
80
90
|
var form = document.getElementById(${escapeJsValue(FORM_ELEMENT_ID)});
|
|
81
91
|
|
|
@@ -186,7 +196,7 @@ ${SURFACE_SCRIPT_PRELUDE}
|
|
|
186
196
|
// says the dialog is no longer pending, where a retry cannot succeed (see reportCallFailure).
|
|
187
197
|
var pending = false;
|
|
188
198
|
|
|
189
|
-
function runSubmit() {
|
|
199
|
+
function runSubmit(actionParams) {
|
|
190
200
|
if (pending) return;
|
|
191
201
|
var params = {};
|
|
192
202
|
var key;
|
|
@@ -201,6 +211,7 @@ ${SURFACE_SCRIPT_PRELUDE}
|
|
|
201
211
|
setStatus(TEXT.missingPrefix + missing.join(", "), "invalid");
|
|
202
212
|
return;
|
|
203
213
|
}
|
|
214
|
+
for (key in actionParams) if (Object.prototype.hasOwnProperty.call(actionParams, key)) params[key] = actionParams[key];
|
|
204
215
|
pending = true;
|
|
205
216
|
setBusy(true);
|
|
206
217
|
setStatus(TEXT.working, "pending");
|
|
@@ -225,14 +236,14 @@ ${SURFACE_SCRIPT_PRELUDE}
|
|
|
225
236
|
var type = (target.getAttribute("type") || "text").toLowerCase();
|
|
226
237
|
if (type === "checkbox" || type === "radio") return;
|
|
227
238
|
event.preventDefault();
|
|
228
|
-
runSubmit();
|
|
239
|
+
runSubmit({});
|
|
229
240
|
});
|
|
230
241
|
|
|
231
242
|
for (var b = 0; b < actionButtons.length; b++) {
|
|
232
243
|
var action = actionButtons[b].getAttribute("data-mcpui-action");
|
|
233
|
-
if (action === "submit") {
|
|
244
|
+
if (action === "submit" || Object.prototype.hasOwnProperty.call(SUBMIT_ACTIONS, action)) {
|
|
234
245
|
actionButtons[b].addEventListener("click", function (event) {
|
|
235
|
-
if (event.isTrusted === true) runSubmit();
|
|
246
|
+
if (event.isTrusted === true) runSubmit(SUBMIT_ACTIONS[event.currentTarget.getAttribute("data-mcpui-action")] || {});
|
|
236
247
|
});
|
|
237
248
|
continue;
|
|
238
249
|
}
|
|
@@ -256,10 +267,21 @@ ${SURFACE_SCRIPT_PRELUDE}
|
|
|
256
267
|
});
|
|
257
268
|
}
|
|
258
269
|
}());`;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Renders the form dialog as a complete, self-contained HTML document.
|
|
273
|
+
*
|
|
274
|
+
* @param spec - See {@link FormSurfaceSpec}.
|
|
275
|
+
* @returns The full HTML string.
|
|
276
|
+
* @complexity O(n) in the number of fields plus the rendered length.
|
|
277
|
+
*/
|
|
278
|
+
export function renderFormDocument(spec) {
|
|
279
|
+
assertNoSecretBaseParam(spec);
|
|
280
|
+
const text = { ...DEFAULT_SURFACE_STATUS_TEXT, ...DEFAULT_SECRET_INPUT_TEXT, ...spec.text };
|
|
259
281
|
return renderSurfaceDocument({
|
|
260
282
|
title: spec.title,
|
|
261
|
-
bodyHtml,
|
|
262
|
-
script,
|
|
283
|
+
bodyHtml: formBodyHtml(spec, text),
|
|
284
|
+
script: formScript(spec, text),
|
|
263
285
|
app: spec.app ?? DEFAULT_APP,
|
|
264
286
|
...(spec.lang === undefined ? {} : { lang: spec.lang }),
|
|
265
287
|
...(spec.tokens === undefined ? {} : { tokens: spec.tokens }),
|
|
@@ -48,6 +48,33 @@ import { renderActions, renderDetailList, renderStatusRegion, renderSurfaceDocum
|
|
|
48
48
|
const DEFAULT_APP = { appName: 'jini-mcp-ui-outcome', appVersion: '1' };
|
|
49
49
|
const DEFAULT_OPEN_LINK_LABEL = 'Open site';
|
|
50
50
|
const OPEN_LINK_ACTION_ID = 'open-link';
|
|
51
|
+
/** The status region's `data-state` for an outcome state. @complexity O(1). */
|
|
52
|
+
function statusStateOf(state) {
|
|
53
|
+
if (state === 'success')
|
|
54
|
+
return 'done';
|
|
55
|
+
return state === 'partial' ? 'partial' : 'failed';
|
|
56
|
+
}
|
|
57
|
+
/** The open-link button, or nothing without a link. @complexity O(n) in the label length. */
|
|
58
|
+
function renderOpenLinkActions(spec) {
|
|
59
|
+
if (spec.openLinkUrl === undefined)
|
|
60
|
+
return '';
|
|
61
|
+
return renderActions([{ id: OPEN_LINK_ACTION_ID, label: spec.openLinkLabel ?? DEFAULT_OPEN_LINK_LABEL, variant: spec.state === 'success' ? 'primary' : 'neutral' }]);
|
|
62
|
+
}
|
|
63
|
+
/** The inline script: wires the open-link button, or nothing at all. @complexity O(n) in the URL length. */
|
|
64
|
+
function outcomeScript(openLinkUrl) {
|
|
65
|
+
// No prelude at all when there is nothing to wire up — a pure-display result needs no bridge
|
|
66
|
+
// calls and no `[data-mcpui-action]` scan.
|
|
67
|
+
if (openLinkUrl === undefined)
|
|
68
|
+
return '(function () { "use strict"; }());';
|
|
69
|
+
return `(function () {
|
|
70
|
+
"use strict";
|
|
71
|
+
${SURFACE_SCRIPT_PRELUDE}
|
|
72
|
+
var OPEN_URL = ${escapeJsValue(openLinkUrl)};
|
|
73
|
+
for (var i = 0; i < actionButtons.length; i++) {
|
|
74
|
+
actionButtons[i].addEventListener("click", function () { api.openLink(OPEN_URL); });
|
|
75
|
+
}
|
|
76
|
+
}());`;
|
|
77
|
+
}
|
|
51
78
|
/**
|
|
52
79
|
* Renders the outcome document as a complete, self-contained HTML document.
|
|
53
80
|
*
|
|
@@ -56,40 +83,23 @@ const OPEN_LINK_ACTION_ID = 'open-link';
|
|
|
56
83
|
* @complexity O(n) in the rendered length.
|
|
57
84
|
*/
|
|
58
85
|
export function renderOutcomeDocument(spec) {
|
|
59
|
-
const statusState = spec.state === 'success' ? 'done' : spec.state === 'partial' ? 'partial' : 'failed';
|
|
60
86
|
// Rendered directly into the status region rather than left for the script to set — this document
|
|
61
87
|
// has nothing further to wait on (unlike a confirmation, which starts idle and only reaches "done"
|
|
62
88
|
// after a click), so the true outcome should be visible from the FIRST paint, not a moment after
|
|
63
89
|
// the script runs.
|
|
64
|
-
const statusHtml = `<p class="mcpui-status" id="${SURFACE_STATUS_ELEMENT_ID}" role="status" aria-live="polite" data-state="${
|
|
65
|
-
const hasOpenLink = spec.openLinkUrl !== undefined;
|
|
66
|
-
const actionsHtml = hasOpenLink
|
|
67
|
-
? renderActions([{ id: OPEN_LINK_ACTION_ID, label: spec.openLinkLabel ?? DEFAULT_OPEN_LINK_LABEL, variant: spec.state === 'success' ? 'primary' : 'neutral' }])
|
|
68
|
-
: '';
|
|
90
|
+
const statusHtml = `<p class="mcpui-status" id="${SURFACE_STATUS_ELEMENT_ID}" role="status" aria-live="polite" data-state="${statusStateOf(spec.state)}">${escapeHtml(spec.message)}</p>`;
|
|
69
91
|
const bodyHtml = [
|
|
70
92
|
renderSurfaceHeader(spec.description === undefined ? { title: spec.title } : { title: spec.title, description: spec.description }),
|
|
71
93
|
renderDetailList(spec.details ?? [], { layout: 'outcome' }),
|
|
72
94
|
statusHtml,
|
|
73
|
-
|
|
95
|
+
renderOpenLinkActions(spec),
|
|
74
96
|
]
|
|
75
97
|
.filter((fragment) => fragment !== '')
|
|
76
98
|
.join('\n');
|
|
77
|
-
const script = hasOpenLink
|
|
78
|
-
? `(function () {
|
|
79
|
-
"use strict";
|
|
80
|
-
${SURFACE_SCRIPT_PRELUDE}
|
|
81
|
-
var OPEN_URL = ${escapeJsValue(spec.openLinkUrl)};
|
|
82
|
-
for (var i = 0; i < actionButtons.length; i++) {
|
|
83
|
-
actionButtons[i].addEventListener("click", function () { api.openLink(OPEN_URL); });
|
|
84
|
-
}
|
|
85
|
-
}());`
|
|
86
|
-
// No prelude at all when there is nothing to wire up — a pure-display result needs no bridge
|
|
87
|
-
// calls and no `[data-mcpui-action]` scan.
|
|
88
|
-
: '(function () { "use strict"; }());';
|
|
89
99
|
return renderSurfaceDocument({
|
|
90
100
|
title: spec.title,
|
|
91
101
|
bodyHtml,
|
|
92
|
-
script,
|
|
102
|
+
script: outcomeScript(spec.openLinkUrl),
|
|
93
103
|
app: spec.app ?? DEFAULT_APP,
|
|
94
104
|
...(spec.lang === undefined ? {} : { lang: spec.lang }),
|
|
95
105
|
...(spec.tokens === undefined ? {} : { tokens: spec.tokens }),
|
|
@@ -29,6 +29,36 @@ function resolveInputType(props) {
|
|
|
29
29
|
return 'number';
|
|
30
30
|
return props.secret === true ? 'password' : 'text';
|
|
31
31
|
}
|
|
32
|
+
/** The reveal toggle a multiline secret carries. @complexity O(n) in the label lengths. */
|
|
33
|
+
function renderSecretToggle(props, id) {
|
|
34
|
+
const showSecret = props.showSecret ?? DEFAULT_SECRET_INPUT_TEXT.showSecret;
|
|
35
|
+
const hideSecret = props.hideSecret ?? DEFAULT_SECRET_INPUT_TEXT.hideSecret;
|
|
36
|
+
return `<button type="button" class="mcpui-button" data-mcpui-secret-toggle="${escapeHtml(id)}" data-mcpui-show-secret="${escapeHtml(showSecret)}" data-mcpui-hide-secret="${escapeHtml(hideSecret)}" aria-controls="${escapeHtml(id)}" aria-pressed="false"${booleanAttribute('disabled', props.disabled)}>${escapeHtml(showSecret)}</button>`;
|
|
37
|
+
}
|
|
38
|
+
/** The `<textarea>` control, masked with a reveal toggle when secret. @complexity O(n) in the rendered length. */
|
|
39
|
+
function renderTextarea(props, required) {
|
|
40
|
+
const secret = props.secret === true;
|
|
41
|
+
return `<textarea class="mcpui-textarea"${required.common}${optionalAttribute('rows', props.rows)}` +
|
|
42
|
+
// Password inputs strip line breaks. CSS masking keeps the textarea's actual value intact,
|
|
43
|
+
// and disables browser text assistance that could retain or alter a pasted credential.
|
|
44
|
+
(secret ? ' data-mcpui-secret style="-webkit-text-security: disc;" autocomplete="off" spellcheck="false"' : '') +
|
|
45
|
+
`>${props.value === undefined ? '' : escapeHtml(String(props.value))}</textarea>` +
|
|
46
|
+
(secret ? renderSecretToggle(props, required.id) : '');
|
|
47
|
+
}
|
|
48
|
+
/** The single-line `<input>` control. @complexity O(n) in the rendered length. */
|
|
49
|
+
function renderInput(props, required) {
|
|
50
|
+
const { type, common } = required;
|
|
51
|
+
return `<input class="mcpui-input" type="${type}"${common}` +
|
|
52
|
+
optionalAttribute('value', props.value) +
|
|
53
|
+
// A stored secret (e.g. a cloud provider's access key or a database password) must never be
|
|
54
|
+
// autofilled with a saved website login. `new-password`, not `off`: Chrome ignores `off` on
|
|
55
|
+
// password inputs by design and fills the saved login anyway.
|
|
56
|
+
(type === 'password' ? optionalAttribute('autocomplete', 'new-password') : '') +
|
|
57
|
+
(type === 'number'
|
|
58
|
+
? optionalAttribute('min', props.min) + optionalAttribute('max', props.max) + optionalAttribute('step', props.step)
|
|
59
|
+
: '') +
|
|
60
|
+
'>';
|
|
61
|
+
}
|
|
32
62
|
/**
|
|
33
63
|
* Renders one free-text field: its label, hint, and control, wrapped in `.mcpui-field`.
|
|
34
64
|
*
|
|
@@ -49,27 +79,9 @@ export function renderTextInput(props) {
|
|
|
49
79
|
booleanAttribute('disabled', props.disabled) +
|
|
50
80
|
fieldDescribedBy(props);
|
|
51
81
|
const type = resolveInputType(props);
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
? `<textarea class="mcpui-textarea"${common}${optionalAttribute('rows', props.rows)}` +
|
|
56
|
-
// Password inputs strip line breaks. CSS masking keeps the textarea's actual value intact,
|
|
57
|
-
// and disables browser text assistance that could retain or alter a pasted credential.
|
|
58
|
-
(secretMultiline ? ' data-mcpui-secret style="-webkit-text-security: disc;" autocomplete="off" spellcheck="false"' : '') +
|
|
59
|
-
`>${props.value === undefined ? '' : escapeHtml(String(props.value))}</textarea>` +
|
|
60
|
-
(secretMultiline
|
|
61
|
-
? `<button type="button" class="mcpui-button" data-mcpui-secret-toggle="${escapeHtml(id)}" data-mcpui-show-secret="${escapeHtml(props.showSecret ?? DEFAULT_SECRET_INPUT_TEXT.showSecret)}" data-mcpui-hide-secret="${escapeHtml(props.hideSecret ?? DEFAULT_SECRET_INPUT_TEXT.hideSecret)}" aria-controls="${escapeHtml(id)}" aria-pressed="false"${booleanAttribute('disabled', props.disabled)}>${escapeHtml(props.showSecret ?? DEFAULT_SECRET_INPUT_TEXT.showSecret)}</button>`
|
|
62
|
-
: '')
|
|
63
|
-
: `<input class="mcpui-input" type="${type}"${common}` +
|
|
64
|
-
optionalAttribute('value', props.value) +
|
|
65
|
-
// A stored secret (e.g. a cloud provider's access key or a database password) must never be
|
|
66
|
-
// autofilled with a saved website login. `new-password`, not `off`: Chrome ignores `off` on
|
|
67
|
-
// password inputs by design and fills the saved login anyway.
|
|
68
|
-
(type === 'password' ? optionalAttribute('autocomplete', 'new-password') : '') +
|
|
69
|
-
(isNumber
|
|
70
|
-
? optionalAttribute('min', props.min) + optionalAttribute('max', props.max) + optionalAttribute('step', props.step)
|
|
71
|
-
: '') +
|
|
72
|
-
'>';
|
|
82
|
+
const control = props.multiline === true && type !== 'number'
|
|
83
|
+
? renderTextarea(props, { id, common })
|
|
84
|
+
: renderInput(props, { type, common });
|
|
73
85
|
return `<div class="mcpui-field">\n${renderFieldLabel(props)}\n${control}\n</div>`;
|
|
74
86
|
}
|
|
75
87
|
//# sourceMappingURL=text-input.js.map
|
|
@@ -100,7 +100,12 @@ export declare const FORCE_LIGHT_SURFACE_THEME = true;
|
|
|
100
100
|
* @returns A `:root { … }` block, then a `:root:root { … }` block holding the overrides (when any),
|
|
101
101
|
* followed by a `@media (prefers-color-scheme: dark) { :root { … } }` block unless {@link FORCE_LIGHT_SURFACE_THEME} suppresses it, ready to paste into the first
|
|
102
102
|
* `<style>` tag.
|
|
103
|
+
* @param options.forceLight - Defaults to {@link FORCE_LIGHT_SURFACE_THEME}. Exposed so the dark
|
|
104
|
+
* variant this function still knows how to emit is reachable (and verified) rather than dead code
|
|
105
|
+
* behind a module constant; every production caller keeps the default.
|
|
103
106
|
* @complexity O(n) in the number of tokens.
|
|
104
107
|
*/
|
|
105
|
-
export declare function renderTokenBlock(overrides?: Partial<Record<SurfaceTokenName, string
|
|
108
|
+
export declare function renderTokenBlock(overrides?: Partial<Record<SurfaceTokenName, string>>, { forceLight }?: {
|
|
109
|
+
forceLight?: boolean;
|
|
110
|
+
}): string;
|
|
106
111
|
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -148,9 +148,12 @@ export const FORCE_LIGHT_SURFACE_THEME = true;
|
|
|
148
148
|
* @returns A `:root { … }` block, then a `:root:root { … }` block holding the overrides (when any),
|
|
149
149
|
* followed by a `@media (prefers-color-scheme: dark) { :root { … } }` block unless {@link FORCE_LIGHT_SURFACE_THEME} suppresses it, ready to paste into the first
|
|
150
150
|
* `<style>` tag.
|
|
151
|
+
* @param options.forceLight - Defaults to {@link FORCE_LIGHT_SURFACE_THEME}. Exposed so the dark
|
|
152
|
+
* variant this function still knows how to emit is reachable (and verified) rather than dead code
|
|
153
|
+
* behind a module constant; every production caller keeps the default.
|
|
151
154
|
* @complexity O(n) in the number of tokens.
|
|
152
155
|
*/
|
|
153
|
-
export function renderTokenBlock(overrides = {}) {
|
|
156
|
+
export function renderTokenBlock(overrides = {}, { forceLight = FORCE_LIGHT_SURFACE_THEME } = {}) {
|
|
154
157
|
const declare = (entries) => entries.map(([name, value]) => ` ${name}: ${value};`).join('\n');
|
|
155
158
|
const notOverridden = ([name]) => !(name in overrides);
|
|
156
159
|
const light = declare([
|
|
@@ -162,7 +165,7 @@ export function renderTokenBlock(overrides = {}) {
|
|
|
162
165
|
const overrideEntries = Object.entries(overrides);
|
|
163
166
|
const overrideBlock = overrideEntries.length === 0 ? '' : `\n:root:root {\n${declare(overrideEntries)}\n}`;
|
|
164
167
|
const lightBlock = `:root {\n${light}\n}${overrideBlock}`;
|
|
165
|
-
if (
|
|
168
|
+
if (forceLight)
|
|
166
169
|
return lightBlock;
|
|
167
170
|
const dark = declare([
|
|
168
171
|
...Object.entries(SURFACE_TOKENS_DARK).filter(notOverridden),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { MediaProviderOption } from './types.js';
|
|
2
|
+
/** @deprecated Supply a host catalog. The former starter set is exported by integrations/media-providers/catalog. */
|
|
2
3
|
export declare const DEFAULT_MEDIA_PROVIDER_CATALOG: readonly MediaProviderOption[];
|
|
3
4
|
//# sourceMappingURL=constants.d.ts.map
|