@jini-ai/ui 0.6.12 → 0.6.15-rc.2
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 +28 -0
- package/README.md +14 -0
- package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
- package/dist/features/admin-widgets/components/InfoTip.hooks.d.ts +17 -2
- package/dist/features/admin-widgets/components/InfoTip.hooks.js +64 -14
- package/dist/features/admin-widgets/components/InfoTip.js +2 -2
- package/dist/features/admin-widgets/index.d.ts +1 -1
- package/dist/features/admin-widgets/index.js +1 -1
- package/dist/features/execution/react/components/AgentCliEnvFields.js +27 -8
- package/dist/features/execution/react/components/ByokProviderForm.js +16 -19
- package/dist/features/interactive-ui/interactive-ui.css +1 -1
- package/dist/features/interactive-ui/providers/shadcn/button.d.ts +8 -6
- package/dist/features/interactive-ui/providers/shadcn/button.js +5 -6
- package/dist/features/interactive-ui/providers/shadcn/card.d.ts +3 -3
- package/dist/features/interactive-ui/providers/shadcn/card.js +3 -4
- package/dist/features/interactive-ui/providers/shadcn/select.d.ts +3 -5
- package/dist/features/interactive-ui/providers/shadcn/select.js +3 -7
- package/dist/features/interactive-ui/providers/shadcn/table.d.ts +4 -5
- package/dist/features/interactive-ui/providers/shadcn/table.js +4 -9
- package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
- package/dist/features/mcp-ui/surfaces/document.js +28 -0
- package/dist/features/mcp-ui/surfaces/fields.d.ts +28 -1
- package/dist/features/mcp-ui/surfaces/fields.js +5 -1
- package/dist/features/mcp-ui/surfaces/form.js +86 -16
- package/dist/features/mcp-ui/surfaces/text-input.d.ts +3 -3
- package/dist/features/mcp-ui/surfaces/text-input.js +26 -11
- package/dist/features/media-providers/catalog.d.ts +3 -0
- package/dist/features/media-providers/catalog.js +3 -0
- package/dist/features/media-providers/react/components/MediaProviderCard.js +5 -4
- package/dist/features/media-providers/react/components/media-providers-tab-labels.js +2 -1
- package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +10 -5
- package/dist/features/panel-kit/fetch-query/adapter.react.js +9 -3
- package/dist/features/panel-kit/fetch-query/binding.react.d.ts +4 -0
- package/dist/features/panel-kit/fetch-query/binding.react.js +29 -10
- package/dist/features/panel-kit/fetch-query/index.d.ts +1 -1
- package/dist/features/panel-kit/fetch-query/index.js +1 -1
- package/dist/features/panel-kit/fetch-query/tanstack.d.ts +1 -1
- package/dist/features/panel-kit/fetch-query/tanstack.js +1 -1
- package/dist/features/privacy/react/components/PrivacyTab.d.ts +1 -3
- package/dist/features/privacy/react/components/PrivacyTab.js +5 -8
- package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -2
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +0 -2
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +0 -6
- package/dist/features/settings/dialog/styles/settings-dialog.css +5 -70
- package/dist/features/source-config-list/react/components/SourceConfigField.js +27 -26
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/react/components/SecretInput.d.ts +47 -0
- package/dist/react/components/SecretInput.hooks.d.ts +26 -0
- package/dist/react/components/SecretInput.hooks.js +25 -0
- package/dist/react/components/SecretInput.js +30 -0
- package/dist/styles/lightbox.css +1 -1
- package/dist/styles/variables.css +1 -1
- package/package.json +9 -9
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ported
|
|
2
|
+
* Ported from shadcn's `new-york` style registry (`ui.shadcn.com/r/styles/new-york/card.json`,
|
|
3
3
|
* 2026-08-18). Only the `cn` import path changed, same as `table.tsx`.
|
|
4
|
+
* The unused private footer primitive is omitted.
|
|
4
5
|
*/
|
|
5
6
|
import * as React from 'react';
|
|
6
7
|
declare const Card: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -8,6 +9,5 @@ declare const CardHeader: React.ForwardRefExoticComponent<React.HTMLAttributes<H
|
|
|
8
9
|
declare const CardTitle: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
9
10
|
declare const CardDescription: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
10
11
|
declare const CardContent: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
11
|
-
|
|
12
|
-
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
|
12
|
+
export { Card, CardHeader, CardTitle, CardDescription, CardContent };
|
|
13
13
|
//# sourceMappingURL=card.d.ts.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* Ported
|
|
3
|
+
* Ported from shadcn's `new-york` style registry (`ui.shadcn.com/r/styles/new-york/card.json`,
|
|
4
4
|
* 2026-08-18). Only the `cn` import path changed, same as `table.tsx`.
|
|
5
|
+
* The unused private footer primitive is omitted.
|
|
5
6
|
*/
|
|
6
7
|
import * as React from 'react';
|
|
7
8
|
import { cn } from './lib/utils.js';
|
|
@@ -15,7 +16,5 @@ const CardDescription = React.forwardRef(({ className, ...props }, ref) => (_jsx
|
|
|
15
16
|
CardDescription.displayName = 'CardDescription';
|
|
16
17
|
const CardContent = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn('p-6 pt-0', className), ...props })));
|
|
17
18
|
CardContent.displayName = 'CardContent';
|
|
18
|
-
|
|
19
|
-
CardFooter.displayName = 'CardFooter';
|
|
20
|
-
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
|
19
|
+
export { Card, CardHeader, CardTitle, CardDescription, CardContent };
|
|
21
20
|
//# sourceMappingURL=card.js.map
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ported
|
|
2
|
+
* Ported from shadcn's `new-york` style registry (`ui.shadcn.com/r/styles/new-york/select.json`,
|
|
3
3
|
* 2026-08-18). Only the `cn` import path changed, same as `table.tsx`; every class name and
|
|
4
4
|
* `data-*` attribute below is unmodified CLI output.
|
|
5
|
+
* Unused private group, label, and separator primitives are omitted.
|
|
5
6
|
*/
|
|
6
7
|
import * as React from 'react';
|
|
7
8
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
8
9
|
declare const Select: React.FC<SelectPrimitive.SelectProps>;
|
|
9
|
-
declare const SelectGroup: React.ForwardRefExoticComponent<SelectPrimitive.SelectGroupProps & React.RefAttributes<HTMLDivElement>>;
|
|
10
10
|
declare const SelectValue: React.ForwardRefExoticComponent<SelectPrimitive.SelectValueProps & React.RefAttributes<HTMLSpanElement>>;
|
|
11
11
|
declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
12
12
|
declare const SelectScrollUpButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollUpButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
13
13
|
declare const SelectScrollDownButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollDownButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
14
14
|
declare const SelectContent: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
15
|
-
declare const SelectLabel: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectLabelProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
16
15
|
declare const SelectItem: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
17
|
-
|
|
18
|
-
export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton, };
|
|
16
|
+
export { Select, SelectValue, SelectTrigger, SelectContent, SelectItem, SelectScrollUpButton, SelectScrollDownButton, };
|
|
19
17
|
//# sourceMappingURL=select.d.ts.map
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* Ported
|
|
3
|
+
* Ported from shadcn's `new-york` style registry (`ui.shadcn.com/r/styles/new-york/select.json`,
|
|
4
4
|
* 2026-08-18). Only the `cn` import path changed, same as `table.tsx`; every class name and
|
|
5
5
|
* `data-*` attribute below is unmodified CLI output.
|
|
6
|
+
* Unused private group, label, and separator primitives are omitted.
|
|
6
7
|
*/
|
|
7
8
|
import * as React from 'react';
|
|
8
9
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
9
10
|
import { Check, ChevronDown, ChevronUp } from './icons.js';
|
|
10
11
|
import { cn } from './lib/utils.js';
|
|
11
12
|
const Select = SelectPrimitive.Root;
|
|
12
|
-
const SelectGroup = SelectPrimitive.Group;
|
|
13
13
|
const SelectValue = SelectPrimitive.Value;
|
|
14
14
|
const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(SelectPrimitive.Trigger, { ref: ref, className: cn('flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1', className), ...props, children: [children, _jsx(SelectPrimitive.Icon, { asChild: true, children: _jsx(ChevronDown, { className: "h-4 w-4 opacity-50" }) })] })));
|
|
15
15
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
|
@@ -20,11 +20,7 @@ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayNam
|
|
|
20
20
|
const SelectContent = React.forwardRef(({ className, children, position = 'popper', ...props }, ref) => (_jsx(SelectPrimitive.Portal, { children: _jsxs(SelectPrimitive.Content, { ref: ref, className: cn('relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]', position === 'popper' &&
|
|
21
21
|
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1', className), position: position, ...props, children: [_jsx(SelectScrollUpButton, {}), _jsx(SelectPrimitive.Viewport, { className: cn('p-1', position === 'popper' && 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'), children: children }), _jsx(SelectScrollDownButton, {})] }) })));
|
|
22
22
|
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
|
23
|
-
const SelectLabel = React.forwardRef(({ className, ...props }, ref) => _jsx(SelectPrimitive.Label, { ref: ref, className: cn('px-2 py-1.5 text-sm font-semibold', className), ...props }));
|
|
24
|
-
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
|
25
23
|
const SelectItem = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(SelectPrimitive.Item, { ref: ref, className: cn('relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', className), ...props, children: [_jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(SelectPrimitive.ItemIndicator, { children: _jsx(Check, { className: "h-4 w-4" }) }) }), _jsx(SelectPrimitive.ItemText, { children: children })] })));
|
|
26
24
|
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
|
27
|
-
|
|
28
|
-
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
|
29
|
-
export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton, };
|
|
25
|
+
export { Select, SelectValue, SelectTrigger, SelectContent, SelectItem, SelectScrollUpButton, SelectScrollDownButton, };
|
|
30
26
|
//# sourceMappingURL=select.js.map
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ported
|
|
2
|
+
* Ported from `npx shadcn@latest add table` (real CLI run, 2026-08-08, against a
|
|
3
3
|
* scratch Vite+Tailwind v4 project — see source-map.md). Only the `cn` import path changed
|
|
4
4
|
* (`@/lib/utils` -> `./lib/utils.js`, this monorepo's relative-import convention); every
|
|
5
|
-
* class name, `data-slot` attribute, and prop shape below is unmodified CLI output.
|
|
5
|
+
* retained class name, `data-slot` attribute, and prop shape below is unmodified CLI output.
|
|
6
|
+
* Unused private footer and caption primitives are omitted.
|
|
6
7
|
*/
|
|
7
8
|
import * as React from 'react';
|
|
8
9
|
declare function Table({ className, ...props }: React.ComponentProps<'table'>): React.JSX.Element;
|
|
9
10
|
declare function TableHeader({ className, ...props }: React.ComponentProps<'thead'>): React.JSX.Element;
|
|
10
11
|
declare function TableBody({ className, ...props }: React.ComponentProps<'tbody'>): React.JSX.Element;
|
|
11
|
-
declare function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>): React.JSX.Element;
|
|
12
12
|
declare function TableRow({ className, ...props }: React.ComponentProps<'tr'>): React.JSX.Element;
|
|
13
13
|
declare function TableHead({ className, ...props }: React.ComponentProps<'th'>): React.JSX.Element;
|
|
14
14
|
declare function TableCell({ className, ...props }: React.ComponentProps<'td'>): React.JSX.Element;
|
|
15
|
-
|
|
16
|
-
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
|
|
15
|
+
export { Table, TableHeader, TableBody, TableHead, TableRow, TableCell };
|
|
17
16
|
//# sourceMappingURL=table.d.ts.map
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* Ported
|
|
3
|
+
* Ported from `npx shadcn@latest add table` (real CLI run, 2026-08-08, against a
|
|
4
4
|
* scratch Vite+Tailwind v4 project — see source-map.md). Only the `cn` import path changed
|
|
5
5
|
* (`@/lib/utils` -> `./lib/utils.js`, this monorepo's relative-import convention); every
|
|
6
|
-
* class name, `data-slot` attribute, and prop shape below is unmodified CLI output.
|
|
6
|
+
* retained class name, `data-slot` attribute, and prop shape below is unmodified CLI output.
|
|
7
|
+
* Unused private footer and caption primitives are omitted.
|
|
7
8
|
*/
|
|
8
9
|
import * as React from 'react';
|
|
9
10
|
import { cn } from './lib/utils.js';
|
|
@@ -16,9 +17,6 @@ function TableHeader({ className, ...props }) {
|
|
|
16
17
|
function TableBody({ className, ...props }) {
|
|
17
18
|
return _jsx("tbody", { "data-slot": "table-body", className: cn('[&_tr:last-child]:border-0', className), ...props });
|
|
18
19
|
}
|
|
19
|
-
function TableFooter({ className, ...props }) {
|
|
20
|
-
return (_jsx("tfoot", { "data-slot": "table-footer", className: cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className), ...props }));
|
|
21
|
-
}
|
|
22
20
|
function TableRow({ className, ...props }) {
|
|
23
21
|
return (_jsx("tr", { "data-slot": "table-row", className: cn('border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted', className), ...props }));
|
|
24
22
|
}
|
|
@@ -28,8 +26,5 @@ function TableHead({ className, ...props }) {
|
|
|
28
26
|
function TableCell({ className, ...props }) {
|
|
29
27
|
return (_jsx("td", { "data-slot": "table-cell", className: cn('p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0', className), ...props }));
|
|
30
28
|
}
|
|
31
|
-
|
|
32
|
-
return _jsx("caption", { "data-slot": "table-caption", className: cn('mt-4 text-sm text-muted-foreground', className), ...props });
|
|
33
|
-
}
|
|
34
|
-
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
|
|
29
|
+
export { Table, TableHeader, TableBody, TableHead, TableRow, TableCell };
|
|
35
30
|
//# sourceMappingURL=table.js.map
|
|
@@ -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/* 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); }";
|
|
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/* A secret control's eye toggle sits inside the field's right edge. The control is padded so long\n values never run under it; the button is a 40px tap target centred on the input, transparent,\n so it overhangs the ~36px input invisibly instead of resizing the field. */\n.mcpui-secret { position: relative; display: flex; flex-direction: column; }\n.mcpui-secret > .mcpui-input, .mcpui-secret > .mcpui-textarea { padding-right: 44px; }\n.mcpui-secret-toggle {\n position: absolute;\n top: 50%;\n right: 0;\n width: 40px;\n height: 40px;\n margin-top: -20px;\n padding: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 0;\n border-radius: var(--jini-mcpui-radius-sm);\n background: transparent;\n color: var(--jini-mcpui-text-soft);\n cursor: pointer;\n}\n.mcpui-secret > .mcpui-textarea + .mcpui-secret-toggle { top: 0; margin-top: -2px; }\n.mcpui-secret-toggle:hover:not([disabled]) { color: var(--jini-mcpui-text-strong); }\n.mcpui-secret-toggle[disabled] { opacity: 0.55; cursor: default; }\n.mcpui-secret-toggle:focus-visible { outline: 2px solid var(--jini-mcpui-accent-ring); outline-offset: -6px; }\n.mcpui-secret-toggle[aria-pressed=\"true\"] .mcpui-secret-icon-show,\n.mcpui-secret-toggle:not([aria-pressed=\"true\"]) .mcpui-secret-icon-hide { display: none; }\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
|
*
|
|
@@ -130,6 +130,34 @@ body {
|
|
|
130
130
|
min-width: 0;
|
|
131
131
|
}
|
|
132
132
|
.mcpui-textarea { resize: vertical; min-height: 72px; font-family: var(--jini-mcpui-font); }
|
|
133
|
+
/* A secret control's eye toggle sits inside the field's right edge. The control is padded so long
|
|
134
|
+
values never run under it; the button is a 40px tap target centred on the input, transparent,
|
|
135
|
+
so it overhangs the ~36px input invisibly instead of resizing the field. */
|
|
136
|
+
.mcpui-secret { position: relative; display: flex; flex-direction: column; }
|
|
137
|
+
.mcpui-secret > .mcpui-input, .mcpui-secret > .mcpui-textarea { padding-right: 44px; }
|
|
138
|
+
.mcpui-secret-toggle {
|
|
139
|
+
position: absolute;
|
|
140
|
+
top: 50%;
|
|
141
|
+
right: 0;
|
|
142
|
+
width: 40px;
|
|
143
|
+
height: 40px;
|
|
144
|
+
margin-top: -20px;
|
|
145
|
+
padding: 0;
|
|
146
|
+
display: inline-flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
justify-content: center;
|
|
149
|
+
border: 0;
|
|
150
|
+
border-radius: var(--jini-mcpui-radius-sm);
|
|
151
|
+
background: transparent;
|
|
152
|
+
color: var(--jini-mcpui-text-soft);
|
|
153
|
+
cursor: pointer;
|
|
154
|
+
}
|
|
155
|
+
.mcpui-secret > .mcpui-textarea + .mcpui-secret-toggle { top: 0; margin-top: -2px; }
|
|
156
|
+
.mcpui-secret-toggle:hover:not([disabled]) { color: var(--jini-mcpui-text-strong); }
|
|
157
|
+
.mcpui-secret-toggle[disabled] { opacity: 0.55; cursor: default; }
|
|
158
|
+
.mcpui-secret-toggle:focus-visible { outline: 2px solid var(--jini-mcpui-accent-ring); outline-offset: -6px; }
|
|
159
|
+
.mcpui-secret-toggle[aria-pressed="true"] .mcpui-secret-icon-show,
|
|
160
|
+
.mcpui-secret-toggle:not([aria-pressed="true"]) .mcpui-secret-icon-hide { display: none; }
|
|
133
161
|
.mcpui-checkbox { margin: 3px 0 0; accent-color: var(--jini-mcpui-accent); }
|
|
134
162
|
.mcpui-choice-group { border: 0; padding: 0; margin: 0; min-width: 0; }
|
|
135
163
|
.mcpui-choice { display: flex; flex-direction: row; align-items: flex-start; gap: 8px; margin: 6px 0 0; }
|
|
@@ -7,10 +7,24 @@ interface SurfaceFieldBase {
|
|
|
7
7
|
/** Help text, wired up with `aria-describedby`. */
|
|
8
8
|
readonly hint?: string;
|
|
9
9
|
readonly required?: boolean;
|
|
10
|
+
/** Waive on an exact choice or an alternative control; headerNames restricts entered headers. Kept secrets are validated by the host. */
|
|
11
|
+
readonly requiredUnless?: {
|
|
12
|
+
readonly field: string;
|
|
13
|
+
readonly equals: string;
|
|
14
|
+
readonly orPresent?: string;
|
|
15
|
+
readonly headerNames?: readonly string[];
|
|
16
|
+
};
|
|
17
|
+
/** Require only while the controlling public field matches one of these choices. */
|
|
18
|
+
readonly requiredWhen?: {
|
|
19
|
+
readonly field: string;
|
|
20
|
+
readonly in: readonly string[];
|
|
21
|
+
};
|
|
10
22
|
readonly disabled?: boolean;
|
|
11
23
|
}
|
|
12
24
|
export interface StringField extends SurfaceFieldBase {
|
|
13
25
|
readonly kind: 'string';
|
|
26
|
+
/** A sealed saved value satisfies validation while the requirement remains visible. */
|
|
27
|
+
readonly hasSavedValue?: boolean;
|
|
14
28
|
readonly value?: string;
|
|
15
29
|
readonly placeholder?: string;
|
|
16
30
|
readonly multiline?: boolean;
|
|
@@ -73,8 +87,21 @@ export interface FieldReadSpec {
|
|
|
73
87
|
readonly label: string;
|
|
74
88
|
readonly kind: SurfaceField['kind'];
|
|
75
89
|
readonly required: boolean;
|
|
90
|
+
readonly hasSavedValue?: boolean;
|
|
91
|
+
readonly requiredUnless?: {
|
|
92
|
+
readonly field: string;
|
|
93
|
+
readonly equals: string;
|
|
94
|
+
readonly orPresent?: string;
|
|
95
|
+
readonly headerNames?: readonly string[];
|
|
96
|
+
};
|
|
97
|
+
/** Require only while the controlling public field matches one of these choices. */
|
|
98
|
+
readonly requiredWhen?: {
|
|
99
|
+
readonly field: string;
|
|
100
|
+
readonly in: readonly string[];
|
|
101
|
+
};
|
|
102
|
+
readonly hint?: string;
|
|
76
103
|
}
|
|
77
|
-
/** Projects fields
|
|
104
|
+
/** Projects fields to runtime validation data; conditional requirements also carry their guidance. */
|
|
78
105
|
export declare function toFieldReadSpecs(fields: readonly SurfaceField[]): readonly FieldReadSpec[];
|
|
79
106
|
export {};
|
|
80
107
|
//# sourceMappingURL=fields.d.ts.map
|
|
@@ -67,13 +67,17 @@ export function renderFieldControl(field, text) {
|
|
|
67
67
|
return renderChoiceGroup({ ...base(field), selection: 'multiple', options: field.options, ...present(field, ['value']) });
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
/** Projects fields
|
|
70
|
+
/** Projects fields to runtime validation data; conditional requirements also carry their guidance. */
|
|
71
71
|
export function toFieldReadSpecs(fields) {
|
|
72
72
|
return fields.map((field) => ({
|
|
73
73
|
name: field.name,
|
|
74
74
|
label: field.label,
|
|
75
75
|
kind: field.kind,
|
|
76
76
|
required: field.required === true,
|
|
77
|
+
...(field.requiredUnless ? { requiredUnless: field.requiredUnless } : {}),
|
|
78
|
+
...(field.requiredWhen ? { requiredWhen: field.requiredWhen } : {}),
|
|
79
|
+
...(field.kind === 'string' && field.hasSavedValue ? { hasSavedValue: true } : {}),
|
|
80
|
+
...((field.requiredUnless || field.requiredWhen) && field.hint ? { hint: field.hint } : {}),
|
|
77
81
|
}));
|
|
78
82
|
}
|
|
79
83
|
//# sourceMappingURL=fields.js.map
|
|
@@ -78,6 +78,7 @@ function cancelPlanOf(spec) {
|
|
|
78
78
|
/** The inline runtime script that reads, validates and submits the fields. @complexity O(n) in the field count plus the script length. */
|
|
79
79
|
function formScript(spec, text) {
|
|
80
80
|
const cancelPlan = cancelPlanOf(spec);
|
|
81
|
+
const hasAlternatives = spec.fields.some(field => field.requiredUnless?.orPresent);
|
|
81
82
|
return `(function () {
|
|
82
83
|
"use strict";
|
|
83
84
|
${SURFACE_SCRIPT_PRELUDE}
|
|
@@ -124,20 +125,29 @@ ${SURFACE_SCRIPT_PRELUDE}
|
|
|
124
125
|
control.dispatchEvent(new Event("input", { bubbles: true }));
|
|
125
126
|
});
|
|
126
127
|
|
|
127
|
-
//
|
|
128
|
-
// discard line breaks
|
|
128
|
+
// The eye toggle only changes presentation. A textarea stays a textarea (a password input would
|
|
129
|
+
// discard line breaks) and flips its CSS masking; a single-line input flips password <-> text.
|
|
130
|
+
// Listen on the form so clicks on the button's icon work too.
|
|
131
|
+
function setRevealed(button, reveal) {
|
|
132
|
+
var control = document.getElementById(button.getAttribute("data-mcpui-secret-toggle"));
|
|
133
|
+
if (!control) return;
|
|
134
|
+
if (control.tagName === "TEXTAREA") control.setAttribute("style", "-webkit-text-security: " + (reveal ? "none" : "disc") + ";");
|
|
135
|
+
else control.type = reveal ? "text" : "password";
|
|
136
|
+
button.setAttribute("aria-pressed", reveal ? "true" : "false");
|
|
137
|
+
button.setAttribute("aria-label", button.getAttribute(reveal ? "data-mcpui-hide-secret" : "data-mcpui-show-secret"));
|
|
138
|
+
}
|
|
139
|
+
// Re-mask every secret once a save succeeds, so nothing stays readable on screen behind the result.
|
|
140
|
+
function concealSecrets() {
|
|
141
|
+
var toggles = form.querySelectorAll("button[data-mcpui-secret-toggle]");
|
|
142
|
+
for (var t = 0; t < toggles.length; t++) setRevealed(toggles[t], false);
|
|
143
|
+
}
|
|
129
144
|
form.addEventListener("click", function (event) {
|
|
130
145
|
if (event.isTrusted !== true) return;
|
|
131
146
|
var target = event.target;
|
|
132
147
|
if (!target || typeof target.closest !== "function") return;
|
|
133
148
|
var button = target.closest("button[data-mcpui-secret-toggle]");
|
|
134
149
|
if (!button || button.disabled) return;
|
|
135
|
-
|
|
136
|
-
if (!control) return;
|
|
137
|
-
var reveal = button.getAttribute("aria-pressed") !== "true";
|
|
138
|
-
control.setAttribute("style", "-webkit-text-security: " + (reveal ? "none" : "disc") + ";");
|
|
139
|
-
button.setAttribute("aria-pressed", reveal ? "true" : "false");
|
|
140
|
-
button.textContent = button.getAttribute(reveal ? "data-mcpui-hide-secret" : "data-mcpui-show-secret");
|
|
150
|
+
setRevealed(button, button.getAttribute("aria-pressed") !== "true");
|
|
141
151
|
});
|
|
142
152
|
|
|
143
153
|
// form.elements[name] yields a RadioNodeList when several controls share a name and the BARE
|
|
@@ -172,6 +182,69 @@ ${SURFACE_SCRIPT_PRELUDE}
|
|
|
172
182
|
return value === "";
|
|
173
183
|
}
|
|
174
184
|
|
|
185
|
+
// Presentation only; the host's save rule validates the effective settings and kept secrets.
|
|
186
|
+
${hasAlternatives ? ` function hasAlternative(field) {
|
|
187
|
+
var name = field.requiredUnless && field.requiredUnless.orPresent;
|
|
188
|
+
if (!name) return false;
|
|
189
|
+
var control = form.elements[name];
|
|
190
|
+
var value = control && control.value.trim();
|
|
191
|
+
if (!value) return FIELDS.some(function(item) { return item.name === name && item.hasSavedValue; });
|
|
192
|
+
var names = field.requiredUnless.headerNames;
|
|
193
|
+
if (!names) return true;
|
|
194
|
+
var pairs = value.split(",").map(function(pair) { return /^([^=\\s][^=]*?)\\s*=\\s*(.+)$/.exec(pair.trim()); });
|
|
195
|
+
return pairs.every(Boolean) && pairs.some(function(pair) {
|
|
196
|
+
return names.some(function(name) { return name.toLowerCase() === pair[1].toLowerCase(); }) && pair[2].trim() !== "";
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
` : ''} function requiredNow(field) {
|
|
200
|
+
var condition = field.requiredUnless;
|
|
201
|
+
var other = condition && form.elements[condition.field];
|
|
202
|
+
var when = field.requiredWhen;
|
|
203
|
+
var controlling = when && form.elements[when.field];
|
|
204
|
+
var required = when ? Boolean(controlling && when.in.indexOf(controlling.value) !== -1) : field.required;
|
|
205
|
+
${hasAlternatives ? ` if (hasAlternative(field)) return false;
|
|
206
|
+
` : ''} return required && (!condition || !other || other.value !== condition.equals);
|
|
207
|
+
}
|
|
208
|
+
function refreshRequirements() {
|
|
209
|
+
for (var i = 0; i < FIELDS.length; i++) {
|
|
210
|
+
var field = FIELDS[i];
|
|
211
|
+
if (!field.requiredUnless && !field.requiredWhen && !field.hasSavedValue) continue;
|
|
212
|
+
var control = form.elements[field.name];
|
|
213
|
+
var required = requiredNow(field);
|
|
214
|
+
control.required = required && !field.hasSavedValue;
|
|
215
|
+
control.setAttribute("aria-required", String(required && !field.hasSavedValue));
|
|
216
|
+
var label = control.labels && control.labels[0];
|
|
217
|
+
var mark = label && label.querySelector(".mcpui-required");
|
|
218
|
+
if (label && !mark) {
|
|
219
|
+
mark = document.createElement("span");
|
|
220
|
+
mark.className = "mcpui-required";
|
|
221
|
+
mark.setAttribute("aria-hidden", "true");
|
|
222
|
+
mark.textContent = "*";
|
|
223
|
+
label.appendChild(mark);
|
|
224
|
+
}
|
|
225
|
+
if (mark) mark.hidden = !required;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
form.addEventListener("change", refreshRequirements);
|
|
229
|
+
form.addEventListener("input", refreshRequirements);
|
|
230
|
+
refreshRequirements();
|
|
231
|
+
|
|
232
|
+
// Collect field values and validation feedback before the submit path performs any effects.
|
|
233
|
+
function readFields() {
|
|
234
|
+
var params = {};
|
|
235
|
+
var missing = [];
|
|
236
|
+
var missingMessage;
|
|
237
|
+
for (var i = 0; i < FIELDS.length; i++) {
|
|
238
|
+
var value = readField(FIELDS[i]);
|
|
239
|
+
if (requiredNow(FIELDS[i]) && !FIELDS[i].hasSavedValue && isBlank(FIELDS[i], value)) {
|
|
240
|
+
missing.push(FIELDS[i].label);
|
|
241
|
+
if ((FIELDS[i].requiredUnless || FIELDS[i].requiredWhen) && FIELDS[i].hint) missingMessage = FIELDS[i].label + " is required. " + FIELDS[i].hint;
|
|
242
|
+
}
|
|
243
|
+
params[FIELDS[i].name] = value;
|
|
244
|
+
}
|
|
245
|
+
return { params: params, missing: missing, missingMessage: missingMessage };
|
|
246
|
+
}
|
|
247
|
+
|
|
175
248
|
// NOT wired to the form's "submit" event. Per the WHATWG form submission algorithm, the
|
|
176
249
|
// sandboxed-forms browsing-context check runs BEFORE that event is ever dispatched, so in the
|
|
177
250
|
// real target sandbox (MCP_UI_VIEW_SANDBOX grants "allow-scripts" and nothing else -- no
|
|
@@ -201,14 +274,10 @@ ${SURFACE_SCRIPT_PRELUDE}
|
|
|
201
274
|
var params = {};
|
|
202
275
|
var key;
|
|
203
276
|
for (key in BASE_PARAMS) if (Object.prototype.hasOwnProperty.call(BASE_PARAMS, key)) params[key] = BASE_PARAMS[key];
|
|
204
|
-
var
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
params[FIELDS[i].name] = value;
|
|
209
|
-
}
|
|
210
|
-
if (missing.length > 0) {
|
|
211
|
-
setStatus(TEXT.missingPrefix + missing.join(", "), "invalid");
|
|
277
|
+
var fields = readFields();
|
|
278
|
+
Object.assign(params, fields.params);
|
|
279
|
+
if (fields.missing.length > 0) {
|
|
280
|
+
setStatus(fields.missingMessage || TEXT.missingPrefix + fields.missing.join(", "), "invalid");
|
|
212
281
|
return;
|
|
213
282
|
}
|
|
214
283
|
for (key in actionParams) if (Object.prototype.hasOwnProperty.call(actionParams, key)) params[key] = actionParams[key];
|
|
@@ -216,6 +285,7 @@ ${SURFACE_SCRIPT_PRELUDE}
|
|
|
216
285
|
setBusy(true);
|
|
217
286
|
setStatus(TEXT.working, "pending");
|
|
218
287
|
api.callTool(TOOL, params).then(function () {
|
|
288
|
+
concealSecrets();
|
|
219
289
|
setStatus(TEXT.done, "done");
|
|
220
290
|
api.requestTeardown();
|
|
221
291
|
}, function (error) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Verbs the host can translate for a secret's reveal toggle; the field label is appended. */
|
|
2
2
|
export interface SecretInputText {
|
|
3
3
|
readonly showSecret: string;
|
|
4
4
|
readonly hideSecret: string;
|
|
@@ -18,8 +18,8 @@ export interface TextInputProps extends Partial<SecretInputText> {
|
|
|
18
18
|
readonly disabled?: boolean;
|
|
19
19
|
/** `'number'` also constrains the on-screen keyboard on touch devices, which `'text'` would not. */
|
|
20
20
|
readonly inputType?: 'text' | 'number';
|
|
21
|
-
/** Renders as `<input type="password">`, or a masked textarea
|
|
22
|
-
*
|
|
21
|
+
/** Renders as `<input type="password">`, or a masked textarea when multiline — either way with
|
|
22
|
+
* an eye show/hide toggle. No `<textarea type="password">` exists, so multiline uses CSS text security.
|
|
23
23
|
* Ignored for presentation when `inputType` is `'number'` (no such thing as a masked number) —
|
|
24
24
|
* same precedence `multiline`'s own doc comment already gives `inputType: 'number'`.
|
|
25
25
|
* A supplied value is always rejected: masking cannot protect a secret serialized into HTML. */
|
|
@@ -29,35 +29,50 @@ function resolveInputType(props) {
|
|
|
29
29
|
return 'number';
|
|
30
30
|
return props.secret === true ? 'password' : 'text';
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
32
|
+
/** Eye / eye-off glyphs. Both ship; CSS shows the one matching `aria-pressed`, so the inline script
|
|
33
|
+
* never builds markup. `currentColor` keeps them on the surface's theme tokens in light and dark. */
|
|
34
|
+
const EYE_ICON = '<svg class="mcpui-secret-icon-show" aria-hidden="true" focusable="false" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg>' +
|
|
35
|
+
'<svg class="mcpui-secret-icon-hide" aria-hidden="true" focusable="false" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.9 4.24A9.1 9.1 0 0 1 12 4c6.5 0 10 7 10 7a17.8 17.8 0 0 1-2.16 3.19M6.6 6.6C3.9 8.4 2 12 2 12s3.5 7 10 7a9.7 9.7 0 0 0 5.4-1.6M14.12 14.12a3 3 0 1 1-4.24-4.24"/><path d="M2 2l20 20"/></svg>';
|
|
36
|
+
/**
|
|
37
|
+
* The eye toggle every secret control carries, inside the field's right edge. Its accessible name
|
|
38
|
+
* is the host's verb plus the field label ("Show Token"); the full show/hide names ride on data
|
|
39
|
+
* attributes so the form script swaps them without knowing the label.
|
|
40
|
+
* @complexity O(n) in the label lengths.
|
|
41
|
+
*/
|
|
33
42
|
function renderSecretToggle(props, id) {
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
return `<button type="button" class="mcpui-
|
|
43
|
+
const show = `${props.showSecret ?? DEFAULT_SECRET_INPUT_TEXT.showSecret} ${props.label}`;
|
|
44
|
+
const hide = `${props.hideSecret ?? DEFAULT_SECRET_INPUT_TEXT.hideSecret} ${props.label}`;
|
|
45
|
+
return `<button type="button" class="mcpui-secret-toggle" data-mcpui-secret-toggle="${escapeHtml(id)}" data-mcpui-show-secret="${escapeHtml(show)}" data-mcpui-hide-secret="${escapeHtml(hide)}" aria-label="${escapeHtml(show)}" aria-controls="${escapeHtml(id)}" aria-pressed="false"${booleanAttribute('disabled', props.disabled)}>${EYE_ICON}</button>`;
|
|
46
|
+
}
|
|
47
|
+
/** Wraps a secret control so its toggle sits inside the field's right edge. @complexity O(n) in the rendered length. */
|
|
48
|
+
function withSecretToggle(control, props, id) {
|
|
49
|
+
return `<div class="mcpui-secret">${control}${renderSecretToggle(props, id)}</div>`;
|
|
37
50
|
}
|
|
38
51
|
/** The `<textarea>` control, masked with a reveal toggle when secret. @complexity O(n) in the rendered length. */
|
|
39
52
|
function renderTextarea(props, required) {
|
|
40
53
|
const secret = props.secret === true;
|
|
41
|
-
|
|
54
|
+
const control = `<textarea class="mcpui-textarea"${required.common}${optionalAttribute('rows', props.rows)}` +
|
|
42
55
|
// Password inputs strip line breaks. CSS masking keeps the textarea's actual value intact,
|
|
43
56
|
// and disables browser text assistance that could retain or alter a pasted credential.
|
|
44
57
|
(secret ? ' data-mcpui-secret style="-webkit-text-security: disc;" autocomplete="off" spellcheck="false"' : '') +
|
|
45
|
-
`>${props.value === undefined ? '' : escapeHtml(String(props.value))}</textarea
|
|
46
|
-
|
|
58
|
+
`>${props.value === undefined ? '' : escapeHtml(String(props.value))}</textarea>`;
|
|
59
|
+
return secret ? withSecretToggle(control, props, required.id) : control;
|
|
47
60
|
}
|
|
48
|
-
/** The single-line `<input>` control. @complexity O(n) in the rendered length. */
|
|
61
|
+
/** The single-line `<input>` control, with the eye toggle when masked. @complexity O(n) in the rendered length. */
|
|
49
62
|
function renderInput(props, required) {
|
|
50
63
|
const { type, common } = required;
|
|
51
|
-
|
|
64
|
+
const control = `<input class="mcpui-input" type="${type}"${common}` +
|
|
52
65
|
optionalAttribute('value', props.value) +
|
|
53
66
|
// A stored secret (e.g. a cloud provider's access key or a database password) must never be
|
|
54
67
|
// autofilled with a saved website login. `new-password`, not `off`: Chrome ignores `off` on
|
|
55
68
|
// password inputs by design and fills the saved login anyway.
|
|
56
|
-
|
|
69
|
+
// spellcheck off: once revealed as `type="text"`, enhanced spellcheck would ship the secret out.
|
|
70
|
+
(type === 'password' ? optionalAttribute('autocomplete', 'new-password') + optionalAttribute('spellcheck', 'false') : '') +
|
|
57
71
|
(type === 'number'
|
|
58
72
|
? optionalAttribute('min', props.min) + optionalAttribute('max', props.max) + optionalAttribute('step', props.step)
|
|
59
73
|
: '') +
|
|
60
74
|
'>';
|
|
75
|
+
return type === 'password' ? withSecretToggle(control, props, required.id) : control;
|
|
61
76
|
}
|
|
62
77
|
/**
|
|
63
78
|
* Renders one free-text field: its label, hint, and control, wrapped in `.mcpui-field`.
|
|
@@ -81,7 +96,7 @@ export function renderTextInput(props) {
|
|
|
81
96
|
const type = resolveInputType(props);
|
|
82
97
|
const control = props.multiline === true && type !== 'number'
|
|
83
98
|
? renderTextarea(props, { id, common })
|
|
84
|
-
: renderInput(props, { type, common });
|
|
99
|
+
: renderInput(props, { id, type, common });
|
|
85
100
|
return `<div class="mcpui-field">\n${renderFieldLabel(props)}\n${control}\n</div>`;
|
|
86
101
|
}
|
|
87
102
|
//# sourceMappingURL=text-input.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment } from 'react';
|
|
2
|
+
import { Fragment, useId } from 'react';
|
|
3
3
|
import { agentHandleProps } from '@jini-ai/agentic';
|
|
4
|
-
import {
|
|
4
|
+
import { SecretInput } from '../../../../react/components/SecretInput.js';
|
|
5
5
|
import { isEntryPresent, isMarkerOnlyEntry, isProviderBaseUrlInvalid, maskedKeyLabel, resolveProviderBaseUrl, } from '../../rules.js';
|
|
6
6
|
/** One provider's credential card: API key, base URL, optional model, clear, and the base-URL hint. */
|
|
7
7
|
export function MediaProviderCard(props) {
|
|
@@ -23,12 +23,13 @@ function CardHead({ option, labels, t, maskedLabel, pending, }) {
|
|
|
23
23
|
_jsx("span", { className: "jini-field-status-badge jini-field-status-badge-success", children: t(labels.savedWithMaskTemplate, { mask: maskedLabel }) })) : null, !saved && pending ? _jsx("span", { className: "jini-field-status-badge", children: labels.unsavedLabel }) : null] }));
|
|
24
24
|
}
|
|
25
25
|
function ApiKeyField({ option, entry, labels, providerHandle, onUpdate, maskedLabel, keyVisible, onToggleKeyVisible, }) {
|
|
26
|
-
|
|
26
|
+
const inputId = useId();
|
|
27
|
+
return (_jsxs("div", { className: "jini-media-provider-field jini-media-provider-field--secret", children: [_jsx("label", { htmlFor: inputId, className: "jini-field-label jini-sr-only", children: labels.apiKeyLabel }), _jsx(SecretInput, { id: inputId, label: `${option.label} ${labels.apiKeyLabel}`, revealed: keyVisible, onRevealedChange: onToggleKeyVisible, toggleLabels: { show: `${labels.showKeyLabel} ${option.label} ${labels.apiKeyLabel}`, hide: `${labels.hideKeyLabel} ${option.label} ${labels.apiKeyLabel}` }, toggleAttrs: agentHandleProps({}, { base: providerHandle, ...({ action: 'reveal-key', role: 'button', label: `${option.label} ${labels.showKeyLabel}` }) }), className: "jini-input",
|
|
27
28
|
/* `new-password`, NOT `"off"` — Chrome deliberately ignores `off` on
|
|
28
29
|
credential-shaped fields, which is how a saved password gets autofilled into
|
|
29
30
|
an API-key box. Same defect, same fix, same reasoning as
|
|
30
31
|
`ByokProviderForm`'s own API-key input; see its comment. */
|
|
31
|
-
autoComplete: "new-password", spellCheck: false, placeholder: maskedLabel ?? labels.apiKeyPlaceholder, "aria-label": `${option.label} ${labels.apiKeyLabel}`, value: entry.apiKey ?? '', onChange: (event) => onUpdate({ apiKey: event.target.value }), ...agentHandleProps({}, { base: providerHandle, ...({ action: 'api-key', role: 'field', label: `${option.label} ${labels.apiKeyLabel}` }) }) })
|
|
32
|
+
autoComplete: "new-password", spellCheck: false, placeholder: maskedLabel ?? labels.apiKeyPlaceholder, "aria-label": `${option.label} ${labels.apiKeyLabel}`, value: entry.apiKey ?? '', onChange: (event) => onUpdate({ apiKey: event.target.value }), ...agentHandleProps({}, { base: providerHandle, ...({ action: 'api-key', role: 'field', label: `${option.label} ${labels.apiKeyLabel}` }) }) })] }));
|
|
32
33
|
}
|
|
33
34
|
function BaseUrlField({ option, entry, labels, providerHandle, onUpdate }) {
|
|
34
35
|
return (_jsxs("label", { className: "jini-media-provider-field", children: [_jsx("span", { className: "jini-field-label jini-sr-only", children: labels.baseUrlLabel }), _jsx("input", { className: "jini-input", type: "url", inputMode: "url", spellCheck: false, placeholder: option.defaultBaseUrl || labels.baseUrlPlaceholder, "aria-label": `${option.label} ${labels.baseUrlLabel}`, value: entry.baseUrl ?? '', "aria-invalid": isProviderBaseUrlInvalid(entry) || undefined, onChange: (event) => onUpdate({ baseUrl: event.target.value }), ...agentHandleProps({}, { base: providerHandle, ...({ action: 'base-url', role: 'field', label: `${option.label} ${labels.baseUrlLabel}` }) }) })] }));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MEDIA_PROVIDER_BASE_URL_PLACEHOLDER } from '../../catalog.js';
|
|
1
2
|
/** English defaults, keyed exactly like `MediaProvidersTabLabels`; each is passed through `t`
|
|
2
3
|
* only when the host did not supply that label (the dictionaries are keyed by this English text). */
|
|
3
4
|
const DEFAULT_LABELS = {
|
|
@@ -12,7 +13,7 @@ const DEFAULT_LABELS = {
|
|
|
12
13
|
showKeyLabel: 'Show',
|
|
13
14
|
hideKeyLabel: 'Hide',
|
|
14
15
|
baseUrlLabel: 'Base URL',
|
|
15
|
-
baseUrlPlaceholder:
|
|
16
|
+
baseUrlPlaceholder: MEDIA_PROVIDER_BASE_URL_PLACEHOLDER,
|
|
16
17
|
baseUrlDefaultHintTemplate: 'Uses {url} by default.',
|
|
17
18
|
baseUrlInvalidLabel: 'Enter an absolute http:// or https:// URL that is not a private or internal address.',
|
|
18
19
|
modelLabel: 'Model',
|