@jini-ai/ui 0.4.6 → 0.4.8
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/README.md +33 -0
- package/dist/features/admin-widgets/components/InfoTip.d.ts +23 -2
- package/dist/features/admin-widgets/components/InfoTip.hooks.d.ts +22 -2
- package/dist/features/admin-widgets/components/InfoTip.hooks.js +75 -28
- package/dist/features/admin-widgets/components/InfoTip.js +12 -4
- package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +72 -0
- package/dist/features/admin-widgets/components/InfoTip.rules.js +68 -0
- package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.d.ts +36 -0
- package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.d.ts +58 -0
- package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +66 -0
- package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.js +24 -0
- package/dist/features/admin-widgets/components/useDebouncedHtmlPreview.hooks.d.ts +23 -0
- package/dist/features/admin-widgets/components/useDebouncedHtmlPreview.hooks.js +28 -0
- package/dist/features/admin-widgets/index.d.ts +9 -0
- package/dist/features/admin-widgets/index.js +5 -0
- package/dist/features/execution/constants.js +11 -10
- package/dist/features/execution/http-adapter.d.ts +3 -1
- package/dist/features/execution/http-adapter.js +9 -4
- package/dist/features/execution/index.d.ts +1 -1
- package/dist/features/execution/index.js +1 -1
- package/dist/features/execution/react/components/ByokProviderForm.d.ts +18 -1
- package/dist/features/execution/react/components/ByokProviderForm.js +14 -16
- package/dist/features/execution/react/components/LocalCliAgentCard.d.ts +5 -1
- package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -87
- package/dist/features/execution/react/components/LocalCliAgentList.d.ts +4 -1
- package/dist/features/execution/react/components/LocalCliAgentList.js +13 -4
- package/dist/features/execution/react/components/LocalCliAgentSettings.d.ts +20 -0
- package/dist/features/execution/react/components/LocalCliAgentSettings.js +20 -0
- package/dist/features/execution/react/hooks/use-byok-field-validation.hooks.d.ts +24 -0
- package/dist/features/execution/react/hooks/use-byok-field-validation.hooks.js +27 -0
- package/dist/features/execution/react/hooks/useExecutionTab.js +10 -1
- package/dist/features/execution/react/hooks/useLocalCliAgentCard.d.ts +48 -0
- package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +110 -0
- package/dist/features/execution/react/hooks/useLocalCliAgentList.d.ts +19 -0
- package/dist/features/execution/react/hooks/useLocalCliAgentList.js +13 -0
- package/dist/features/execution/rules.d.ts +13 -0
- package/dist/features/execution/rules.js +14 -0
- package/dist/features/execution/types.d.ts +2 -0
- package/dist/features/html-editor/canvas-embed-placeholders.d.ts +13 -1
- package/dist/features/html-editor/canvas-embed-placeholders.js +61 -12
- package/dist/features/html-editor/dom.d.ts +5 -0
- package/dist/features/html-editor/dom.js +10 -0
- package/dist/features/html-editor/editor-blocks.d.ts +71 -0
- package/dist/features/html-editor/editor-blocks.js +185 -0
- package/dist/features/html-editor/editor-serialization.d.ts +24 -0
- package/dist/features/html-editor/editor-serialization.js +110 -0
- package/dist/features/html-editor/index.d.ts +5 -0
- package/dist/features/html-editor/index.js +3 -0
- package/dist/features/html-editor/protected-elements.d.ts +64 -0
- package/dist/features/html-editor/protected-elements.js +189 -0
- package/dist/features/html-editor/react/components/InteractiveHtmlEditor.d.ts +6 -4
- package/dist/features/html-editor/react/components/InteractiveHtmlEditor.js +3 -3
- package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.d.ts +9 -1
- package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +107 -196
- package/dist/features/html-editor/source-splice.d.ts +23 -0
- package/dist/features/html-editor/source-splice.js +45 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ar.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.bn.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.de.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.en.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.es.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.fa.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.fr.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.hi.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.hu.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.id.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.it.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ja.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ko.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.pl.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.pt-BR.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ru.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.th.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.tr.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.uk.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ur.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +4 -0
- package/dist/features/interactive-ui/interactive-ui.css +1 -1
- package/dist/features/interactive-ui/providers/native/data-table.js +8 -1
- package/dist/features/interactive-ui/providers/native/data-table.manifest.d.ts +3 -5
- package/dist/features/interactive-ui/providers/native/data-table.manifest.js +5 -7
- package/dist/features/interactive-ui/providers/recharts/bar-chart.js +8 -3
- package/dist/features/interactive-ui/providers/recharts/chart-config.d.ts +40 -0
- package/dist/features/interactive-ui/providers/recharts/chart-config.js +32 -0
- package/dist/features/interactive-ui/providers/recharts/chart-tokens.d.ts +18 -12
- package/dist/features/interactive-ui/providers/recharts/chart-tokens.js +6 -11
- package/dist/features/interactive-ui/providers/recharts/chart.d.ts +31 -0
- package/dist/features/interactive-ui/providers/recharts/chart.js +34 -0
- package/dist/features/interactive-ui/providers/recharts/line-chart.js +10 -3
- package/dist/features/interactive-ui/providers/recharts/pie-chart.js +8 -3
- package/dist/features/interactive-ui/providers/shadcn/button.d.ts +1 -1
- package/dist/features/interactive-ui/providers/shadcn/data-table.d.ts +5 -1
- package/dist/features/interactive-ui/providers/shadcn/data-table.js +26 -2
- package/dist/features/interactive-ui/providers/shadcn/data-table.manifest.d.ts +8 -1
- package/dist/features/interactive-ui/providers/shadcn/data-table.manifest.js +6 -3
- package/dist/features/interactive-ui/table-data.d.ts +25 -0
- package/dist/features/interactive-ui/table-data.js +36 -0
- package/dist/features/mcp-ui/escape.js +2 -6
- package/dist/features/mcp-ui/sandbox-proxy-link-forwarder.d.ts +26 -0
- package/dist/features/mcp-ui/sandbox-proxy-link-forwarder.js +40 -0
- package/dist/features/mcp-ui/sandbox-proxy.d.ts +5 -1
- package/dist/features/mcp-ui/sandbox-proxy.js +11 -0
- package/dist/features/mcp-ui/surfaces/bridge.js +9 -3
- package/dist/features/mcp-ui/surfaces/document.d.ts +11 -4
- package/dist/features/mcp-ui/surfaces/document.js +25 -3
- package/dist/features/mcp-ui/surfaces/outcome.js +1 -1
- package/dist/features/media-providers/constants.d.ts +0 -7
- package/dist/features/media-providers/constants.js +7 -15
- package/dist/features/media-providers/react/components/MediaProvidersTab.js +3 -2
- package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +6 -3
- package/dist/features/memory/dependencies.d.ts +2 -0
- package/dist/features/memory/dependencies.js +9 -25
- package/dist/features/memory/index.d.ts +1 -0
- package/dist/features/memory/index.js +1 -0
- package/dist/features/memory/react/MemoryPorts.d.ts +17 -0
- package/dist/features/memory/react/MemoryPorts.js +15 -0
- package/dist/features/memory/react/hooks/useMemoryConfig.hooks.d.ts +2 -2
- package/dist/features/memory/react/hooks/useMemoryConfig.hooks.js +7 -7
- package/dist/features/memory/react/hooks/useMemoryConnectors.hooks.d.ts +2 -3
- package/dist/features/memory/react/hooks/useMemoryConnectors.hooks.js +4 -5
- package/dist/features/memory/react/hooks/useMemoryEntries.hooks.d.ts +2 -3
- package/dist/features/memory/react/hooks/useMemoryEntries.hooks.js +4 -5
- package/dist/features/memory/react/hooks/useMemoryExtractions.hooks.js +2 -2
- package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
- package/dist/features/panel-kit/fetch-query/adapter.react.js +5 -3
- package/dist/features/panel-kit/fetch-query/adapter.tanstack.d.ts +11 -0
- package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +212 -0
- package/dist/features/panel-kit/fetch-query/binding.react.d.ts +17 -0
- package/dist/features/panel-kit/fetch-query/binding.react.js +24 -0
- package/dist/features/panel-kit/fetch-query/environment.browser.d.ts +1 -1
- package/dist/features/panel-kit/fetch-query/index.d.ts +6 -3
- package/dist/features/panel-kit/fetch-query/index.js +2 -1
- package/dist/features/panel-kit/fetch-query/tanstack.d.ts +8 -0
- package/dist/features/panel-kit/fetch-query/tanstack.js +4 -0
- package/dist/features/panel-kit/helpers/standing-draft-recovery-message.d.ts +3 -1
- package/dist/features/panel-kit/helpers/standing-draft-recovery-message.js +9 -2
- package/dist/features/settings/dialog/styles/settings-dialog.css +169 -21
- package/dist/features/tabbed-dialog/styles/tabbed-dialog.css +91 -0
- package/dist/react/components/RemixIcon.d.ts +1 -41
- package/dist/react/components/RemixIcon.js +3 -176
- package/dist/react/components/RemixIcon.stylesheet.d.ts +84 -0
- package/dist/react/components/RemixIcon.stylesheet.js +208 -0
- package/dist/react/components/remixicon-font-recovery.d.ts +55 -0
- package/dist/react/components/remixicon-font-recovery.js +110 -0
- package/dist/styles/admin.css +8 -2
- package/package.json +25 -8
- package/dist/features/panel-kit/fetch-query/cache.d.ts +0 -123
- package/dist/features/panel-kit/fetch-query/cache.js +0 -231
- package/dist/features/panel-kit/fetch-query/types.d.ts +0 -91
- package/dist/features/panel-kit/fetch-query/types.js +0 -2
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* "Claude orange should be the default color" (owner, 2026-10-05). The values live in CSS, never
|
|
16
16
|
* here, so a host's theme stays the single owner of color.
|
|
17
17
|
*/
|
|
18
|
-
/** Categorical series colors in fixed order
|
|
18
|
+
/** Categorical series colors in fixed order; larger datasets repeat the palette with named legends. */
|
|
19
19
|
export declare const CHART_SERIES_COLORS: readonly ["var(--jini-chart-1, var(--jini-chart-default-1))", "var(--jini-chart-2, var(--jini-chart-default-2))", "var(--jini-chart-3, var(--jini-chart-default-3))", "var(--jini-chart-4, var(--jini-chart-default-4))", "var(--jini-chart-5, var(--jini-chart-default-5))", "var(--jini-chart-6, var(--jini-chart-default-6))"];
|
|
20
20
|
/** The single-series color (bar fill, line stroke). */
|
|
21
21
|
export declare const CHART_PRIMARY_COLOR: "var(--jini-chart-1, var(--jini-chart-default-1))";
|
|
@@ -24,20 +24,26 @@ export declare const CHART_GRID_STROKE = "var(--jini-chart-grid, var(--jini-bord
|
|
|
24
24
|
/** Axis tick label props: muted ink, never the series color. */
|
|
25
25
|
export declare const CHART_AXIS_TICK: {
|
|
26
26
|
readonly fill: "var(--jini-chart-axis, var(--jini-muted))";
|
|
27
|
+
readonly fontSize: 12;
|
|
28
|
+
};
|
|
29
|
+
/** Shared axis chrome; category visibility and numeric formatting remain separate concerns. */
|
|
30
|
+
export declare const CHART_AXIS_PROPS: {
|
|
31
|
+
readonly tick: {
|
|
32
|
+
readonly fill: "var(--jini-chart-axis, var(--jini-muted))";
|
|
33
|
+
readonly fontSize: 12;
|
|
34
|
+
};
|
|
35
|
+
readonly tickLine: false;
|
|
36
|
+
readonly axisLine: false;
|
|
37
|
+
readonly tickMargin: 10;
|
|
38
|
+
};
|
|
39
|
+
/** Hover points use the same series ink, with a surface halo in either color scheme. */
|
|
40
|
+
export declare const CHART_ACTIVE_DOT: {
|
|
41
|
+
readonly r: 5;
|
|
42
|
+
readonly stroke: "var(--jini-chart-surface, var(--jini-bg))";
|
|
43
|
+
readonly strokeWidth: 2;
|
|
27
44
|
};
|
|
28
45
|
/** The hover band behind a hovered bar (recharts' default is an opaque grey block). */
|
|
29
46
|
export declare const CHART_CURSOR: {
|
|
30
47
|
readonly fill: "var(--jini-chart-cursor, var(--jini-accent-soft))";
|
|
31
48
|
};
|
|
32
|
-
/** Tooltip box: theme surface and ink, so it does not stay a white box in dark mode. */
|
|
33
|
-
export declare const CHART_TOOLTIP_CONTENT_STYLE: {
|
|
34
|
-
readonly background: "var(--jini-chart-surface, var(--jini-bg))";
|
|
35
|
-
readonly border: "1px solid var(--jini-chart-grid, var(--jini-border))";
|
|
36
|
-
readonly borderRadius: "var(--jini-radius-sm)";
|
|
37
|
-
readonly color: "var(--jini-chart-text, var(--jini-text))";
|
|
38
|
-
};
|
|
39
|
-
/** Tooltip label and value ink: text tokens, never the series color (recharts' item default). */
|
|
40
|
-
export declare const CHART_TOOLTIP_TEXT_STYLE: {
|
|
41
|
-
readonly color: "var(--jini-chart-text, var(--jini-text))";
|
|
42
|
-
};
|
|
43
49
|
//# sourceMappingURL=chart-tokens.d.ts.map
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* "Claude orange should be the default color" (owner, 2026-10-05). The values live in CSS, never
|
|
16
16
|
* here, so a host's theme stays the single owner of color.
|
|
17
17
|
*/
|
|
18
|
-
/** Categorical series colors in fixed order
|
|
18
|
+
/** Categorical series colors in fixed order; larger datasets repeat the palette with named legends. */
|
|
19
19
|
export const CHART_SERIES_COLORS = [
|
|
20
20
|
'var(--jini-chart-1, var(--jini-chart-default-1))',
|
|
21
21
|
'var(--jini-chart-2, var(--jini-chart-default-2))',
|
|
@@ -29,16 +29,11 @@ export const CHART_PRIMARY_COLOR = CHART_SERIES_COLORS[0];
|
|
|
29
29
|
/** Grid lines and axis lines: recessive. */
|
|
30
30
|
export const CHART_GRID_STROKE = 'var(--jini-chart-grid, var(--jini-border))';
|
|
31
31
|
/** Axis tick label props: muted ink, never the series color. */
|
|
32
|
-
export const CHART_AXIS_TICK = { fill: 'var(--jini-chart-axis, var(--jini-muted))' };
|
|
32
|
+
export const CHART_AXIS_TICK = { fill: 'var(--jini-chart-axis, var(--jini-muted))', fontSize: 12 };
|
|
33
|
+
/** Shared axis chrome; category visibility and numeric formatting remain separate concerns. */
|
|
34
|
+
export const CHART_AXIS_PROPS = { tick: CHART_AXIS_TICK, tickLine: false, axisLine: false, tickMargin: 10 };
|
|
35
|
+
/** Hover points use the same series ink, with a surface halo in either color scheme. */
|
|
36
|
+
export const CHART_ACTIVE_DOT = { r: 5, stroke: 'var(--jini-chart-surface, var(--jini-bg))', strokeWidth: 2 };
|
|
33
37
|
/** The hover band behind a hovered bar (recharts' default is an opaque grey block). */
|
|
34
38
|
export const CHART_CURSOR = { fill: 'var(--jini-chart-cursor, var(--jini-accent-soft))' };
|
|
35
|
-
/** Tooltip box: theme surface and ink, so it does not stay a white box in dark mode. */
|
|
36
|
-
export const CHART_TOOLTIP_CONTENT_STYLE = {
|
|
37
|
-
background: 'var(--jini-chart-surface, var(--jini-bg))',
|
|
38
|
-
border: `1px solid ${CHART_GRID_STROKE}`,
|
|
39
|
-
borderRadius: 'var(--jini-radius-sm)',
|
|
40
|
-
color: 'var(--jini-chart-text, var(--jini-text))',
|
|
41
|
-
};
|
|
42
|
-
/** Tooltip label and value ink: text tokens, never the series color (recharts' item default). */
|
|
43
|
-
export const CHART_TOOLTIP_TEXT_STYLE = { color: 'var(--jini-chart-text, var(--jini-text))' };
|
|
44
39
|
//# sourceMappingURL=chart-tokens.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
import { type ChartConfig } from './chart-config.js';
|
|
3
|
+
interface ChartContainerProps {
|
|
4
|
+
readonly config: ChartConfig;
|
|
5
|
+
readonly title: string;
|
|
6
|
+
readonly description: string;
|
|
7
|
+
readonly children: ReactElement;
|
|
8
|
+
}
|
|
9
|
+
/** Shared, measurable chart frame with an accessible caption and a wrapping legend. */
|
|
10
|
+
export declare function ChartContainer({ config, title, description, children }: ChartContainerProps): ReactElement;
|
|
11
|
+
interface ChartTooltipEntry {
|
|
12
|
+
readonly dataKey?: unknown;
|
|
13
|
+
readonly name?: string | number;
|
|
14
|
+
readonly value?: unknown;
|
|
15
|
+
readonly color?: string;
|
|
16
|
+
readonly type?: string;
|
|
17
|
+
readonly payload?: {
|
|
18
|
+
readonly _jiniChartKey?: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
interface ChartTooltipContentProps {
|
|
22
|
+
readonly active?: boolean;
|
|
23
|
+
readonly label?: ReactNode;
|
|
24
|
+
readonly payload?: readonly ChartTooltipEntry[];
|
|
25
|
+
}
|
|
26
|
+
/** Shadcn-style tooltip card; series config owns both labels and swatch colors. */
|
|
27
|
+
export declare function ChartTooltipContent({ active, label, payload }: ChartTooltipContentProps): ReactElement | null;
|
|
28
|
+
/** Compact named legend; pie values remain readable without hover or outside labels. */
|
|
29
|
+
export declare function ChartLegendContent(): ReactElement | null;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=chart.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useId } from 'react';
|
|
3
|
+
import { ResponsiveContainer } from 'recharts';
|
|
4
|
+
import { formatChartValue } from './chart-config.js';
|
|
5
|
+
import { CHART_PRIMARY_COLOR } from './chart-tokens.js';
|
|
6
|
+
const ChartContext = createContext({});
|
|
7
|
+
/** Shared, measurable chart frame with an accessible caption and a wrapping legend. */
|
|
8
|
+
export function ChartContainer({ config, title, description, children }) {
|
|
9
|
+
const id = useId();
|
|
10
|
+
return (_jsx(ChartContext.Provider, { value: config, children: _jsxs("figure", { className: "jini-chart", "aria-label": title, "aria-describedby": `${id}-description`, children: [_jsxs("figcaption", { className: "jini-chart-caption", children: [_jsx("span", { children: title }), _jsx("span", { id: `${id}-description`, children: description })] }), _jsx(ResponsiveContainer, { width: "100%", height: 300, minWidth: 0, children: children }), _jsx(ChartLegendContent, {})] }) }));
|
|
11
|
+
}
|
|
12
|
+
/** Shadcn-style tooltip card; series config owns both labels and swatch colors. */
|
|
13
|
+
export function ChartTooltipContent({ active, label, payload = [] }) {
|
|
14
|
+
const config = useContext(ChartContext);
|
|
15
|
+
const entries = payload.filter((entry) => entry.type !== 'none' && entry.value != null);
|
|
16
|
+
if (!active || entries.length === 0)
|
|
17
|
+
return null;
|
|
18
|
+
return (_jsxs("div", { className: "jini-chart-tooltip", role: "tooltip", "aria-live": "polite", "aria-atomic": "true", children: [label != null && _jsx("div", { className: "jini-chart-tooltip-label", children: label }), _jsx("div", { className: "jini-chart-tooltip-items", children: entries.map((entry, index) => {
|
|
19
|
+
// Pie names may repeat; its internal key keeps swatches tied to the actual slice.
|
|
20
|
+
const dataKey = String(entry.dataKey ?? entry.name);
|
|
21
|
+
const key = Object.hasOwn(config, dataKey) ? dataKey : entry.payload?._jiniChartKey ?? dataKey;
|
|
22
|
+
const series = Object.hasOwn(config, key) ? config[key] : undefined;
|
|
23
|
+
return (_jsxs("div", { className: "jini-chart-tooltip-item", children: [_jsx("span", { className: "jini-chart-swatch", style: { backgroundColor: series?.color ?? entry.color ?? CHART_PRIMARY_COLOR }, "aria-hidden": "true" }), _jsx("span", { className: "jini-chart-tooltip-name", children: series?.label ?? String(entry.name ?? entry.dataKey ?? 'Value') }), _jsx("span", { className: "jini-chart-tooltip-value", children: formatChartValue({ value: entry.value }) })] }, `${key}-${index}`));
|
|
24
|
+
}) })] }));
|
|
25
|
+
}
|
|
26
|
+
/** Compact named legend; pie values remain readable without hover or outside labels. */
|
|
27
|
+
export function ChartLegendContent() {
|
|
28
|
+
const config = useContext(ChartContext);
|
|
29
|
+
const entries = Object.entries(config);
|
|
30
|
+
if (entries.length === 0)
|
|
31
|
+
return null;
|
|
32
|
+
return (_jsx("ul", { className: "jini-chart-legend", "aria-label": "Chart legend", children: entries.map(([key, series]) => (_jsxs("li", { className: "jini-chart-legend-item", children: [_jsx("span", { className: "jini-chart-swatch", style: { backgroundColor: series.color }, "aria-hidden": "true" }), _jsx("span", { children: series.label }), series.value !== undefined && _jsx("span", { className: "jini-chart-legend-value", children: formatChartValue({ value: series.value }) })] }, key))) }));
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=chart.js.map
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { useId } from 'react';
|
|
3
|
+
import { Area, CartesianGrid, ComposedChart, Line, Tooltip, XAxis, YAxis } from 'recharts';
|
|
4
|
+
import { CHART_ACTIVE_DOT, CHART_AXIS_PROPS, CHART_GRID_STROKE, CHART_PRIMARY_COLOR } from './chart-tokens.js';
|
|
5
|
+
import { chartTickFormatter, createChartConfig } from './chart-config.js';
|
|
6
|
+
import { ChartContainer, ChartTooltipContent } from './chart.js';
|
|
4
7
|
export function LineChart({ data, categoryKey, valueKey, color = CHART_PRIMARY_COLOR }) {
|
|
5
|
-
|
|
8
|
+
const gradientId = `jini-chart-area-${useId().replace(/:/g, '')}`;
|
|
9
|
+
const config = createChartConfig({ series: [{ key: valueKey, label: valueKey }] }, { color });
|
|
10
|
+
const title = `${valueKey} by ${categoryKey}`;
|
|
11
|
+
const description = `Line chart with ${data.length} points. Use arrow keys to explore values.`;
|
|
12
|
+
return (_jsx(ChartContainer, { config: config, title: title, description: description, children: _jsxs(ComposedChart, { data: data, accessibilityLayer: true, title: title, desc: description, margin: { top: 16, right: 12, bottom: 8, left: 0 }, children: [_jsx("defs", { children: _jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx("stop", { offset: "0%", stopColor: config[valueKey].color, stopOpacity: 0.22 }), _jsx("stop", { offset: "100%", stopColor: config[valueKey].color, stopOpacity: 0.02 })] }) }), _jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: CHART_GRID_STROKE, strokeOpacity: 0.6, vertical: false }), _jsx(XAxis, { dataKey: categoryKey, interval: 0, ...CHART_AXIS_PROPS }), _jsx(YAxis, { ...CHART_AXIS_PROPS, tickFormatter: chartTickFormatter, width: 48 }), _jsx(Tooltip, { cursor: { stroke: CHART_GRID_STROKE, strokeDasharray: '3 3' }, content: _jsx(ChartTooltipContent, {}), payloadUniqBy: (entry) => entry.graphicalItemId, isAnimationActive: false }), _jsx(Area, { type: "monotone", dataKey: valueKey, stroke: "none", fill: `url(#${gradientId})`, tooltipType: "none", legendType: "none", activeDot: false, isAnimationActive: false }), _jsx(Line, { type: "monotone", dataKey: valueKey, stroke: config[valueKey].color, strokeWidth: 2.5, dot: false, activeDot: CHART_ACTIVE_DOT, isAnimationActive: false })] }) }));
|
|
6
13
|
}
|
|
7
14
|
//# sourceMappingURL=line-chart.js.map
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Cell, Pie, PieChart as RechartsPieChart,
|
|
3
|
-
import {
|
|
2
|
+
import { Cell, Pie, PieChart as RechartsPieChart, Tooltip } from 'recharts';
|
|
3
|
+
import { createChartConfig, formatChartValue } from './chart-config.js';
|
|
4
|
+
import { ChartContainer, ChartTooltipContent } from './chart.js';
|
|
4
5
|
export function PieChart({ data, color }) {
|
|
5
|
-
|
|
6
|
+
const slices = data.map((entry, index) => ({ ...entry, _jiniChartKey: `slice-${index}` }));
|
|
7
|
+
const config = createChartConfig({ series: slices.map((entry) => ({ key: entry._jiniChartKey, label: entry.name, value: entry.value })) }, { color });
|
|
8
|
+
const total = data.reduce((sum, entry) => sum + entry.value, 0);
|
|
9
|
+
const description = `Donut chart with ${data.length} categories. Total: ${formatChartValue({ value: total })}. Values are listed in the legend.`;
|
|
10
|
+
return (_jsx(ChartContainer, { config: config, title: "Distribution", description: description, children: _jsxs(RechartsPieChart, { accessibilityLayer: true, title: "Distribution", desc: description, margin: { top: 12, right: 12, bottom: 12, left: 12 }, children: [_jsx(Tooltip, { content: _jsx(ChartTooltipContent, {}), isAnimationActive: false }), _jsx(Pie, { data: slices, dataKey: "value", nameKey: "name", cx: "50%", cy: "50%", innerRadius: "55%", outerRadius: "78%", paddingAngle: 3, cornerRadius: 5, stroke: "none", label: false, labelLine: false, isAnimationActive: false, children: slices.map((entry) => (_jsx(Cell, { fill: config[entry._jiniChartKey].color }, entry._jiniChartKey))) }), _jsxs("text", { x: "50%", y: "50%", textAnchor: "middle", dominantBaseline: "middle", className: "jini-chart-total", "aria-hidden": "true", children: [_jsx("tspan", { x: "50%", dy: "-0.2em", children: formatChartValue({ value: total }, { notation: 'compact' }) }), _jsx("tspan", { x: "50%", dy: "1.8em", className: "jini-chart-total-label", children: "Total" })] })] }) }));
|
|
6
11
|
}
|
|
7
12
|
//# sourceMappingURL=pie-chart.js.map
|
|
@@ -8,7 +8,7 @@ import * as React from 'react';
|
|
|
8
8
|
import { type VariantProps } from 'class-variance-authority';
|
|
9
9
|
declare const buttonVariants: (props?: ({
|
|
10
10
|
variant?: "link" | "default" | "outline" | "ghost" | "destructive" | "secondary" | null | undefined;
|
|
11
|
-
size?: "default" | "
|
|
11
|
+
size?: "default" | "icon" | "sm" | "lg" | null | undefined;
|
|
12
12
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
13
13
|
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
14
14
|
asChild?: boolean;
|
|
@@ -5,9 +5,13 @@ export interface DataTableColumn {
|
|
|
5
5
|
export interface DataTableProps {
|
|
6
6
|
readonly columns: readonly DataTableColumn[];
|
|
7
7
|
readonly rows: readonly Record<string, unknown>[];
|
|
8
|
+
/** Local header sorting; defaults to true. Initial row order is always preserved. */
|
|
9
|
+
readonly sortable?: boolean;
|
|
10
|
+
/** Opt-in local paging; omitted means all rows, preserving existing callers. */
|
|
11
|
+
readonly pageSize?: number;
|
|
8
12
|
/** Host-wired, not part of the agent-facing wire schema — see data-table.manifest.ts. */
|
|
9
13
|
readonly onRowClick?: (row: Record<string, unknown>, index: number) => void;
|
|
10
14
|
}
|
|
11
15
|
/** Composes shadcn's real `table.tsx` primitives into the same `columns`/`rows`/`onRowClick` contract `native.data-table` exposes. */
|
|
12
|
-
export declare function DataTable({ columns, rows, onRowClick }: DataTableProps): import("react").JSX.Element;
|
|
16
|
+
export declare function DataTable({ columns, rows, onRowClick, sortable, pageSize }: DataTableProps): import("react").JSX.Element;
|
|
13
17
|
//# sourceMappingURL=data-table.d.ts.map
|
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { getNumericColumnKeys, sortTableRows } from '../../table-data.js';
|
|
2
4
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './table.js';
|
|
3
5
|
/** Composes shadcn's real `table.tsx` primitives into the same `columns`/`rows`/`onRowClick` contract `native.data-table` exposes. */
|
|
4
|
-
export function DataTable({ columns, rows, onRowClick }) {
|
|
5
|
-
|
|
6
|
+
export function DataTable({ columns, rows, onRowClick, sortable = true, pageSize }) {
|
|
7
|
+
const [sort, setSort] = useState(null);
|
|
8
|
+
const [requestedPage, setPage] = useState(0);
|
|
9
|
+
const numericKeys = useMemo(() => getNumericColumnKeys({ columns, rows }), [columns, rows]);
|
|
10
|
+
const activeSort = sortable && columns.some((column) => column.key === sort?.key) ? sort : null;
|
|
11
|
+
const sortedRows = useMemo(() => sortTableRows({ rows, sort: activeSort, numericKeys }), [rows, activeSort, numericKeys]);
|
|
12
|
+
const requestedSize = pageSize !== undefined && Number.isInteger(pageSize) && pageSize > 0 ? pageSize : null;
|
|
13
|
+
const paging = requestedSize !== null;
|
|
14
|
+
const size = requestedSize ?? Math.max(rows.length, 1);
|
|
15
|
+
const pageCount = Math.max(1, Math.ceil(rows.length / size));
|
|
16
|
+
// Clamp during render so a smaller result set never shows a stale, empty page.
|
|
17
|
+
const page = paging ? Math.min(requestedPage, pageCount - 1) : 0;
|
|
18
|
+
const start = page * size;
|
|
19
|
+
const visibleRows = sortedRows.slice(start, start + size);
|
|
20
|
+
return (_jsxs("div", { className: "jini-data-table-frame", "data-table-provider": "shadcn", children: [_jsxs(Table, { className: "jini-data-table", children: [_jsx(TableHeader, { children: _jsx(TableRow, { children: columns.map((column) => (_jsx(TableHead, { scope: "col", "data-numeric": numericKeys.has(column.key), "aria-sort": sortable ? (activeSort?.key === column.key ? activeSort.direction : 'none') : undefined, children: sortable ? (_jsxs("button", { type: "button", className: "jini-data-table-sort", onClick: () => {
|
|
21
|
+
setSort({ key: column.key, direction: activeSort?.key === column.key && activeSort.direction === 'ascending'
|
|
22
|
+
? 'descending' : 'ascending' });
|
|
23
|
+
setPage(0);
|
|
24
|
+
}, children: [_jsx("span", { children: column.label }), _jsx("span", { "aria-hidden": "true", children: activeSort?.key === column.key ? (activeSort.direction === 'ascending' ? '↑' : '↓') : '↕' })] })) : column.label }, column.key))) }) }), _jsxs(TableBody, { children: [rows.length === 0 && _jsx(TableRow, { children: _jsx(TableCell, { className: "jini-data-table-empty", colSpan: columns.length, children: "No results." }) }), visibleRows.map(({ row, index }) => (_jsx(TableRow, { onClick: onRowClick ? () => onRowClick(row, index) : undefined, role: onRowClick ? 'button' : undefined, tabIndex: onRowClick ? 0 : undefined, onKeyDown: onRowClick ? (event) => {
|
|
25
|
+
if (event.target === event.currentTarget && (event.key === 'Enter' || event.key === ' ')) {
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
onRowClick(row, index);
|
|
28
|
+
}
|
|
29
|
+
} : undefined, children: columns.map((column) => (_jsx(TableCell, { "data-numeric": numericKeys.has(column.key), children: String(row[column.key] ?? '') }, column.key))) }, index)))] })] }), paging && (_jsxs("div", { className: "jini-data-table-pagination", children: [_jsxs("span", { "aria-live": "polite", children: [rows.length === 0 ? 0 : start + 1, "\u2013", Math.min(start + size, rows.length), " of ", rows.length, " rows"] }), _jsxs("div", { className: "jini-data-table-pagination-controls", children: [_jsx("button", { type: "button", "aria-label": "Previous page", disabled: page === 0, onClick: () => setPage(page - 1), children: "Previous" }), _jsx("button", { type: "button", "aria-label": "Next page", disabled: page >= pageCount - 1, onClick: () => setPage(page + 1), children: "Next" })] })] }))] }));
|
|
6
30
|
}
|
|
7
31
|
//# sourceMappingURL=data-table.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { InteractiveComponentManifest } from '../../types.js';
|
|
3
3
|
/**
|
|
4
|
-
* Same wire shape as `native.data-table` (see that manifest)
|
|
4
|
+
* Same required wire shape as `native.data-table` (see that manifest), plus optional local
|
|
5
|
+
* sorting/paging settings — shared capabilities, so
|
|
5
6
|
* `resolveByCapability('data-table')` returns both, proving the fallback-chain mechanic across
|
|
6
7
|
* two real providers. `.passthrough()` for the same reason as that manifest: an A2UI-embedded
|
|
7
8
|
* instance may carry an `action` prop this schema doesn't itself validate.
|
|
@@ -18,6 +19,8 @@ export declare const shadcnDataTablePropsSchema: z.ZodObject<{
|
|
|
18
19
|
label: string;
|
|
19
20
|
}>, "many">;
|
|
20
21
|
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
|
|
22
|
+
sortable: z.ZodOptional<z.ZodBoolean>;
|
|
23
|
+
pageSize: z.ZodOptional<z.ZodNumber>;
|
|
21
24
|
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
22
25
|
columns: z.ZodArray<z.ZodObject<{
|
|
23
26
|
key: z.ZodString;
|
|
@@ -30,6 +33,8 @@ export declare const shadcnDataTablePropsSchema: z.ZodObject<{
|
|
|
30
33
|
label: string;
|
|
31
34
|
}>, "many">;
|
|
32
35
|
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
|
|
36
|
+
sortable: z.ZodOptional<z.ZodBoolean>;
|
|
37
|
+
pageSize: z.ZodOptional<z.ZodNumber>;
|
|
33
38
|
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
34
39
|
columns: z.ZodArray<z.ZodObject<{
|
|
35
40
|
key: z.ZodString;
|
|
@@ -42,6 +47,8 @@ export declare const shadcnDataTablePropsSchema: z.ZodObject<{
|
|
|
42
47
|
label: string;
|
|
43
48
|
}>, "many">;
|
|
44
49
|
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
|
|
50
|
+
sortable: z.ZodOptional<z.ZodBoolean>;
|
|
51
|
+
pageSize: z.ZodOptional<z.ZodNumber>;
|
|
45
52
|
}, z.ZodTypeAny, "passthrough">>;
|
|
46
53
|
export declare const shadcnDataTableManifest: InteractiveComponentManifest;
|
|
47
54
|
//# sourceMappingURL=data-table.manifest.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
/**
|
|
3
|
-
* Same wire shape as `native.data-table` (see that manifest)
|
|
3
|
+
* Same required wire shape as `native.data-table` (see that manifest), plus optional local
|
|
4
|
+
* sorting/paging settings — shared capabilities, so
|
|
4
5
|
* `resolveByCapability('data-table')` returns both, proving the fallback-chain mechanic across
|
|
5
6
|
* two real providers. `.passthrough()` for the same reason as that manifest: an A2UI-embedded
|
|
6
7
|
* instance may carry an `action` prop this schema doesn't itself validate.
|
|
@@ -9,13 +10,15 @@ export const shadcnDataTablePropsSchema = z
|
|
|
9
10
|
.object({
|
|
10
11
|
columns: z.array(z.object({ key: z.string(), label: z.string() }).strict()).min(1),
|
|
11
12
|
rows: z.array(z.record(z.unknown())),
|
|
13
|
+
sortable: z.boolean().optional(),
|
|
14
|
+
pageSize: z.number().int().min(1).max(100).optional(),
|
|
12
15
|
})
|
|
13
16
|
.passthrough();
|
|
14
17
|
export const shadcnDataTableManifest = {
|
|
15
18
|
id: 'shadcn.data-table',
|
|
16
19
|
provider: 'shadcn',
|
|
17
|
-
capabilities: ['data-table', 'table', 'tabular-data'],
|
|
20
|
+
capabilities: ['data-table', 'table', 'tabular-data', 'spreadsheet', 'list-of-records'],
|
|
18
21
|
propsSchema: shadcnDataTablePropsSchema,
|
|
19
|
-
description: '
|
|
22
|
+
description: 'Preferred styled table for tabular data, spreadsheets and lists of records: columns + rows, optional row click. Rounded border, sticky muted headers, row hover, numeric alignment and empty state. Client-side column sorting (sortable defaults to true); optional pageSize (1–100) enables paging. Load @jini-ai/ui/interactive-ui.css in the renderer host.',
|
|
20
23
|
};
|
|
21
24
|
//# sourceMappingURL=data-table.manifest.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
interface TableData {
|
|
2
|
+
readonly columns: readonly {
|
|
3
|
+
readonly key: string;
|
|
4
|
+
}[];
|
|
5
|
+
readonly rows: readonly Record<string, unknown>[];
|
|
6
|
+
}
|
|
7
|
+
export interface TableSort {
|
|
8
|
+
readonly key: string;
|
|
9
|
+
readonly direction: 'ascending' | 'descending';
|
|
10
|
+
}
|
|
11
|
+
/** Infer numeric columns once for both providers; empty cells do not turn a numeric column into text.
|
|
12
|
+
* @complexity O(c × r), c columns and r rows; no per-cell rescans during rendering. */
|
|
13
|
+
export declare function getNumericColumnKeys({ columns, rows }: TableData, _options?: {}): ReadonlySet<string>;
|
|
14
|
+
/** Sort a copy while preserving source indexes for host actions; missing values stay last in both directions.
|
|
15
|
+
* @complexity O(r log r) time and O(r) space for r rows; the caller memoizes the result. */
|
|
16
|
+
export declare function sortTableRows({ rows, sort, numericKeys }: {
|
|
17
|
+
readonly rows: TableData['rows'];
|
|
18
|
+
readonly sort: TableSort | null;
|
|
19
|
+
readonly numericKeys: ReadonlySet<string>;
|
|
20
|
+
}, _options?: {}): {
|
|
21
|
+
row: Record<string, unknown>;
|
|
22
|
+
index: number;
|
|
23
|
+
}[];
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=table-data.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function isNumericValue(value) {
|
|
2
|
+
if (typeof value === 'number')
|
|
3
|
+
return Number.isFinite(value);
|
|
4
|
+
return typeof value === 'string' && /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(value.trim())
|
|
5
|
+
&& Number.isFinite(Number(value));
|
|
6
|
+
}
|
|
7
|
+
/** Infer numeric columns once for both providers; empty cells do not turn a numeric column into text.
|
|
8
|
+
* @complexity O(c × r), c columns and r rows; no per-cell rescans during rendering. */
|
|
9
|
+
export function getNumericColumnKeys({ columns, rows }, _options = {}) {
|
|
10
|
+
return new Set(columns.filter(({ key }) => {
|
|
11
|
+
const values = rows.map((row) => row[key]).filter((value) => value !== null && value !== undefined && value !== '');
|
|
12
|
+
return values.length > 0 && values.every(isNumericValue);
|
|
13
|
+
}).map(({ key }) => key));
|
|
14
|
+
}
|
|
15
|
+
/** Sort a copy while preserving source indexes for host actions; missing values stay last in both directions.
|
|
16
|
+
* @complexity O(r log r) time and O(r) space for r rows; the caller memoizes the result. */
|
|
17
|
+
export function sortTableRows({ rows, sort, numericKeys }, _options = {}) {
|
|
18
|
+
const indexed = rows.map((row, index) => ({ row, index }));
|
|
19
|
+
if (!sort)
|
|
20
|
+
return indexed;
|
|
21
|
+
const { key, direction } = sort;
|
|
22
|
+
const multiplier = direction === 'ascending' ? 1 : -1;
|
|
23
|
+
return indexed.sort((a, b) => {
|
|
24
|
+
const left = a.row[key];
|
|
25
|
+
const right = b.row[key];
|
|
26
|
+
const leftEmpty = left === null || left === undefined || left === '';
|
|
27
|
+
const rightEmpty = right === null || right === undefined || right === '';
|
|
28
|
+
if (leftEmpty || rightEmpty)
|
|
29
|
+
return Number(leftEmpty) - Number(rightEmpty);
|
|
30
|
+
const compared = numericKeys.has(key)
|
|
31
|
+
? Number(left) - Number(right)
|
|
32
|
+
: String(left).localeCompare(String(right));
|
|
33
|
+
return compared * multiplier;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=table-data.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { escapeHtml as sharedEscapeHtml } from "@jini-ai/core/text";
|
|
1
2
|
/**
|
|
2
3
|
* @module features/mcp-ui/escape
|
|
3
4
|
*
|
|
@@ -21,12 +22,7 @@
|
|
|
21
22
|
* @complexity O(n) in the value's length.
|
|
22
23
|
*/
|
|
23
24
|
export function escapeHtml(value) {
|
|
24
|
-
return value
|
|
25
|
-
.replace(/&/g, '&')
|
|
26
|
-
.replace(/</g, '<')
|
|
27
|
-
.replace(/>/g, '>')
|
|
28
|
-
.replace(/"/g, '"')
|
|
29
|
-
.replace(/'/g, ''');
|
|
25
|
+
return sharedEscapeHtml({ text: value });
|
|
30
26
|
}
|
|
31
27
|
/**
|
|
32
28
|
* Serializes a value as a JS literal safe to embed in an inline `<script>`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module features/mcp-ui/sandbox-proxy-link-forwarder
|
|
3
|
+
*
|
|
4
|
+
* Page-script source the sandbox proxy (`sandbox-proxy.ts`) embeds so a View's plain `<a href>`
|
|
5
|
+
* opens in a new tab. Inside the proxy's frame a link is useless on its own: `AppFrame`'s hardcoded
|
|
6
|
+
* sandbox has neither `allow-popups` nor `allow-top-navigation`, so a same-tab link navigates the
|
|
7
|
+
* card's own frame away from the View and a `target="_blank"` link is silently blocked. The one
|
|
8
|
+
* channel a View has to open anything is the `ui/open-link` request, answered by the Host's
|
|
9
|
+
* `onOpenLink` — so every qualifying link click becomes that request instead.
|
|
10
|
+
*
|
|
11
|
+
* Window, bubble phase, and skipped once `defaultPrevented`: it runs after every listener the View
|
|
12
|
+
* itself registered, so a View that already handles its own link (calling `openLink` itself and
|
|
13
|
+
* cancelling the click) is not opened twice.
|
|
14
|
+
*
|
|
15
|
+
* The request id is a string the View's own JSON-RPC ids (numbers) never use, so the Host's reply
|
|
16
|
+
* cannot resolve one of the View's pending requests. `link.href` is the resolved URL when the
|
|
17
|
+
* proxy has a real base; under the opaque `data:` proxy a relative href stays as written and the
|
|
18
|
+
* Host decides how to resolve it.
|
|
19
|
+
*
|
|
20
|
+
* Not re-exported from the package (`index.ts` re-exports `sandbox-proxy.ts` only); the tests
|
|
21
|
+
* evaluate this exact source.
|
|
22
|
+
*
|
|
23
|
+
* Expects `host` (the embedding window) and `hostOrigin` in scope — the proxy script's own vars.
|
|
24
|
+
*/
|
|
25
|
+
export declare const PROXY_LINK_FORWARDER_JS = "var openLinkSeq = 0;\n function forwardLinkClick(event) {\n if (event.defaultPrevented || event.button !== 0) return;\n var target = event.target;\n var link = target && target.closest ? target.closest(\"a[href]\") : null;\n if (!link || link.hasAttribute(\"download\")) return;\n var href = link.getAttribute(\"href\");\n if (!href || href.charAt(0) === \"#\" || /^\\s*javascript:/i.test(href)) return;\n event.preventDefault();\n openLinkSeq += 1;\n host.postMessage(\n { jsonrpc: \"2.0\", id: \"jini-proxy-open-link-\" + openLinkSeq, method: \"ui/open-link\", params: { url: link.href } },\n hostOrigin\n );\n }";
|
|
26
|
+
//# sourceMappingURL=sandbox-proxy-link-forwarder.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module features/mcp-ui/sandbox-proxy-link-forwarder
|
|
3
|
+
*
|
|
4
|
+
* Page-script source the sandbox proxy (`sandbox-proxy.ts`) embeds so a View's plain `<a href>`
|
|
5
|
+
* opens in a new tab. Inside the proxy's frame a link is useless on its own: `AppFrame`'s hardcoded
|
|
6
|
+
* sandbox has neither `allow-popups` nor `allow-top-navigation`, so a same-tab link navigates the
|
|
7
|
+
* card's own frame away from the View and a `target="_blank"` link is silently blocked. The one
|
|
8
|
+
* channel a View has to open anything is the `ui/open-link` request, answered by the Host's
|
|
9
|
+
* `onOpenLink` — so every qualifying link click becomes that request instead.
|
|
10
|
+
*
|
|
11
|
+
* Window, bubble phase, and skipped once `defaultPrevented`: it runs after every listener the View
|
|
12
|
+
* itself registered, so a View that already handles its own link (calling `openLink` itself and
|
|
13
|
+
* cancelling the click) is not opened twice.
|
|
14
|
+
*
|
|
15
|
+
* The request id is a string the View's own JSON-RPC ids (numbers) never use, so the Host's reply
|
|
16
|
+
* cannot resolve one of the View's pending requests. `link.href` is the resolved URL when the
|
|
17
|
+
* proxy has a real base; under the opaque `data:` proxy a relative href stays as written and the
|
|
18
|
+
* Host decides how to resolve it.
|
|
19
|
+
*
|
|
20
|
+
* Not re-exported from the package (`index.ts` re-exports `sandbox-proxy.ts` only); the tests
|
|
21
|
+
* evaluate this exact source.
|
|
22
|
+
*
|
|
23
|
+
* Expects `host` (the embedding window) and `hostOrigin` in scope — the proxy script's own vars.
|
|
24
|
+
*/
|
|
25
|
+
export const PROXY_LINK_FORWARDER_JS = `var openLinkSeq = 0;
|
|
26
|
+
function forwardLinkClick(event) {
|
|
27
|
+
if (event.defaultPrevented || event.button !== 0) return;
|
|
28
|
+
var target = event.target;
|
|
29
|
+
var link = target && target.closest ? target.closest("a[href]") : null;
|
|
30
|
+
if (!link || link.hasAttribute("download")) return;
|
|
31
|
+
var href = link.getAttribute("href");
|
|
32
|
+
if (!href || href.charAt(0) === "#" || /^\\s*javascript:/i.test(href)) return;
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
openLinkSeq += 1;
|
|
35
|
+
host.postMessage(
|
|
36
|
+
{ jsonrpc: "2.0", id: "jini-proxy-open-link-" + openLinkSeq, method: "ui/open-link", params: { url: link.href } },
|
|
37
|
+
hostOrigin
|
|
38
|
+
);
|
|
39
|
+
}`;
|
|
40
|
+
//# sourceMappingURL=sandbox-proxy-link-forwarder.js.map
|
|
@@ -127,8 +127,12 @@
|
|
|
127
127
|
* survives `document.open()` and would then apply to the guest HTML written in below, silently
|
|
128
128
|
* breaking any UIResource whose HTML this package did not build. Surfaces built by
|
|
129
129
|
* `surfaces/document.ts` already carry their own `SURFACE_CSP` meta tag for exactly that job.
|
|
130
|
+
*
|
|
131
|
+
* Once the guest HTML is written, the page also forwards the View's link clicks to the Host as
|
|
132
|
+
* `ui/open-link` (`sandbox-proxy-link-forwarder.ts`) — registered after `document.close()` because
|
|
133
|
+
* `document.open()` erases every listener registered before it.
|
|
130
134
|
*/
|
|
131
|
-
export declare const SANDBOX_PROXY_HTML = "<!doctype html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<title>MCP-UI sandbox proxy</title>\n<style>html, body { margin: 0; padding: 0; width: 100%; height: 100%; }</style>\n</head>\n<body>\n<script>\n(function () {\n \"use strict\";\n var host = window.parent;\n var hostOrigin = window.location.origin;\n if (host === window) return;\n\n function isFromHost(event) {\n return event.source === host && event.origin === hostOrigin;\n }\n\n window.addEventListener(\"message\", function (event) {\n if (!isFromHost(event)) return;\n var data = event.data;\n if (!data || typeof data !== \"object\") return;\n if (data.method !== \"ui/notifications/sandbox-resource-ready\") return;\n var html = data.params && data.params.html;\n if (typeof html !== \"string\") return;\n document.open();\n document.write(html);\n document.close();\n });\n\n host.postMessage({ method: \"ui/notifications/sandbox-proxy-ready\", params: {} }, hostOrigin);\n}());\n</script>\n</body>\n</html>";
|
|
135
|
+
export declare const SANDBOX_PROXY_HTML = "<!doctype html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<title>MCP-UI sandbox proxy</title>\n<style>html, body { margin: 0; padding: 0; width: 100%; height: 100%; }</style>\n</head>\n<body>\n<script>\n(function () {\n \"use strict\";\n var host = window.parent;\n var hostOrigin = window.location.origin;\n if (host === window) return;\n\n function isFromHost(event) {\n return event.source === host && event.origin === hostOrigin;\n }\n\n var openLinkSeq = 0;\n function forwardLinkClick(event) {\n if (event.defaultPrevented || event.button !== 0) return;\n var target = event.target;\n var link = target && target.closest ? target.closest(\"a[href]\") : null;\n if (!link || link.hasAttribute(\"download\")) return;\n var href = link.getAttribute(\"href\");\n if (!href || href.charAt(0) === \"#\" || /^\\s*javascript:/i.test(href)) return;\n event.preventDefault();\n openLinkSeq += 1;\n host.postMessage(\n { jsonrpc: \"2.0\", id: \"jini-proxy-open-link-\" + openLinkSeq, method: \"ui/open-link\", params: { url: link.href } },\n hostOrigin\n );\n }\n\n window.addEventListener(\"message\", function (event) {\n if (!isFromHost(event)) return;\n var data = event.data;\n if (!data || typeof data !== \"object\") return;\n if (data.method !== \"ui/notifications/sandbox-resource-ready\") return;\n var html = data.params && data.params.html;\n if (typeof html !== \"string\") return;\n document.open();\n document.write(html);\n document.close();\n window.addEventListener(\"click\", forwardLinkClick);\n });\n\n host.postMessage({ method: \"ui/notifications/sandbox-proxy-ready\", params: {} }, hostOrigin);\n}());\n</script>\n</body>\n</html>";
|
|
132
136
|
/**
|
|
133
137
|
* Same script as {@link SANDBOX_PROXY_HTML}, with one load-bearing difference: `hostOrigin` is the
|
|
134
138
|
* caller-supplied `hostOrigin` argument, baked in as a literal, instead of read from
|
|
@@ -109,6 +109,7 @@
|
|
|
109
109
|
* hardcodes any particular host — a consumer's origin is discovered at runtime from the page's own
|
|
110
110
|
* URL — and neither touches the single-hop `document.write` shape the section above depends on.
|
|
111
111
|
*/
|
|
112
|
+
import { PROXY_LINK_FORWARDER_JS } from './sandbox-proxy-link-forwarder.js';
|
|
112
113
|
/**
|
|
113
114
|
* The sandbox proxy page's complete, self-contained source — see this module's own doc for the
|
|
114
115
|
* protocol it implements and the security tradeoff serving it makes.
|
|
@@ -127,6 +128,10 @@
|
|
|
127
128
|
* survives `document.open()` and would then apply to the guest HTML written in below, silently
|
|
128
129
|
* breaking any UIResource whose HTML this package did not build. Surfaces built by
|
|
129
130
|
* `surfaces/document.ts` already carry their own `SURFACE_CSP` meta tag for exactly that job.
|
|
131
|
+
*
|
|
132
|
+
* Once the guest HTML is written, the page also forwards the View's link clicks to the Host as
|
|
133
|
+
* `ui/open-link` (`sandbox-proxy-link-forwarder.ts`) — registered after `document.close()` because
|
|
134
|
+
* `document.open()` erases every listener registered before it.
|
|
130
135
|
*/
|
|
131
136
|
export const SANDBOX_PROXY_HTML = `<!doctype html>
|
|
132
137
|
<html>
|
|
@@ -147,6 +152,8 @@ export const SANDBOX_PROXY_HTML = `<!doctype html>
|
|
|
147
152
|
return event.source === host && event.origin === hostOrigin;
|
|
148
153
|
}
|
|
149
154
|
|
|
155
|
+
${PROXY_LINK_FORWARDER_JS}
|
|
156
|
+
|
|
150
157
|
window.addEventListener("message", function (event) {
|
|
151
158
|
if (!isFromHost(event)) return;
|
|
152
159
|
var data = event.data;
|
|
@@ -157,6 +164,7 @@ export const SANDBOX_PROXY_HTML = `<!doctype html>
|
|
|
157
164
|
document.open();
|
|
158
165
|
document.write(html);
|
|
159
166
|
document.close();
|
|
167
|
+
window.addEventListener("click", forwardLinkClick);
|
|
160
168
|
});
|
|
161
169
|
|
|
162
170
|
host.postMessage({ method: "ui/notifications/sandbox-proxy-ready", params: {} }, hostOrigin);
|
|
@@ -220,6 +228,8 @@ export function buildIsolatedSandboxProxyHtml(hostOrigin) {
|
|
|
220
228
|
return event.source === host && event.origin === hostOrigin;
|
|
221
229
|
}
|
|
222
230
|
|
|
231
|
+
${PROXY_LINK_FORWARDER_JS}
|
|
232
|
+
|
|
223
233
|
window.addEventListener("message", function (event) {
|
|
224
234
|
if (!isFromHost(event)) return;
|
|
225
235
|
var data = event.data;
|
|
@@ -230,6 +240,7 @@ export function buildIsolatedSandboxProxyHtml(hostOrigin) {
|
|
|
230
240
|
document.open();
|
|
231
241
|
document.write(html);
|
|
232
242
|
document.close();
|
|
243
|
+
window.addEventListener("click", forwardLinkClick);
|
|
233
244
|
});
|
|
234
245
|
|
|
235
246
|
host.postMessage({ method: "ui/notifications/sandbox-proxy-ready", params: {} }, hostOrigin);
|
|
@@ -133,9 +133,15 @@ export function renderBridgeScript(spec) {
|
|
|
133
133
|
return requestWhenReady("ui/open-link", { url: url });
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
// Height only, never width: @mcp-ui/client's AppFrame writes a reported width onto the iframe as a
|
|
137
|
+
// fixed px style. A surface is fluid, so its scrollWidth is just the frame's width at that moment;
|
|
138
|
+
// reported while mounting in a hidden or narrow pane it froze the frame at min-content width for
|
|
139
|
+
// good (owner bug 2026-10-08: an ask-choice card stuck at 192px after a reload). Leaving width out
|
|
140
|
+
// keeps the frame at the host's width: 100%, and the ResizeObserver below re-reports height on resize.
|
|
136
141
|
function reportSize() {
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
// Root scrollHeight includes the iframe viewport, so a short card could never shrink.
|
|
143
|
+
// The body box includes the surface and its padding without that viewport floor.
|
|
144
|
+
notify("ui/notifications/size-changed", { height: Math.ceil(document.body.getBoundingClientRect().height) });
|
|
139
145
|
}
|
|
140
146
|
|
|
141
147
|
window.addEventListener("message", function (event) {
|
|
@@ -176,7 +182,7 @@ export function renderBridgeScript(spec) {
|
|
|
176
182
|
readyHandlers = [];
|
|
177
183
|
for (var i = 0; i < calls.length; i++) request(calls[i].method, calls[i].params).then(calls[i].resolve, calls[i].reject);
|
|
178
184
|
for (var j = 0; j < handlers.length; j++) handlers[j]();
|
|
179
|
-
if (typeof ResizeObserver === "function") new ResizeObserver(reportSize).observe(document.
|
|
185
|
+
if (typeof ResizeObserver === "function") new ResizeObserver(reportSize).observe(document.body);
|
|
180
186
|
}
|
|
181
187
|
|
|
182
188
|
function onHandshakeFailed(error) {
|