@seed-ship/mcp-ui-solid 6.21.0 → 6.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/components/ChartJSRenderer.cjs +244 -68
  3. package/dist/components/ChartJSRenderer.cjs.map +1 -1
  4. package/dist/components/ChartJSRenderer.d.ts +9 -0
  5. package/dist/components/ChartJSRenderer.d.ts.map +1 -1
  6. package/dist/components/ChartJSRenderer.js +246 -70
  7. package/dist/components/ChartJSRenderer.js.map +1 -1
  8. package/dist/components/ExpandableWrapper.cjs +9 -1
  9. package/dist/components/ExpandableWrapper.cjs.map +1 -1
  10. package/dist/components/ExpandableWrapper.d.ts.map +1 -1
  11. package/dist/components/ExpandableWrapper.js +9 -1
  12. package/dist/components/ExpandableWrapper.js.map +1 -1
  13. package/dist/components/LightboxOverlay.cjs +139 -16
  14. package/dist/components/LightboxOverlay.cjs.map +1 -1
  15. package/dist/components/LightboxOverlay.d.ts +6 -0
  16. package/dist/components/LightboxOverlay.d.ts.map +1 -1
  17. package/dist/components/LightboxOverlay.js +141 -18
  18. package/dist/components/LightboxOverlay.js.map +1 -1
  19. package/dist/context/MCPUIConfigContext.cjs +2 -1
  20. package/dist/context/MCPUIConfigContext.cjs.map +1 -1
  21. package/dist/context/MCPUIConfigContext.d.ts +60 -11
  22. package/dist/context/MCPUIConfigContext.d.ts.map +1 -1
  23. package/dist/context/MCPUIConfigContext.js +2 -1
  24. package/dist/context/MCPUIConfigContext.js.map +1 -1
  25. package/dist/context/MCPUIStringsContext.cjs +6 -1
  26. package/dist/context/MCPUIStringsContext.cjs.map +1 -1
  27. package/dist/context/MCPUIStringsContext.d.ts +22 -0
  28. package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
  29. package/dist/context/MCPUIStringsContext.js +6 -1
  30. package/dist/context/MCPUIStringsContext.js.map +1 -1
  31. package/dist/hooks/index.d.ts +2 -0
  32. package/dist/hooks/index.d.ts.map +1 -1
  33. package/dist/hooks.cjs +2 -0
  34. package/dist/hooks.cjs.map +1 -1
  35. package/dist/hooks.js +2 -0
  36. package/dist/hooks.js.map +1 -1
  37. package/dist/index.cjs +6 -0
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +2 -0
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +7 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/utils/iframe-coep.cjs.map +1 -1
  45. package/dist/utils/iframe-coep.d.ts +9 -2
  46. package/dist/utils/iframe-coep.d.ts.map +1 -1
  47. package/dist/utils/iframe-coep.js.map +1 -1
  48. package/dist/utils/pan-zoom.cjs +312 -0
  49. package/dist/utils/pan-zoom.cjs.map +1 -0
  50. package/dist/utils/pan-zoom.d.ts +235 -0
  51. package/dist/utils/pan-zoom.d.ts.map +1 -0
  52. package/dist/utils/pan-zoom.js +312 -0
  53. package/dist/utils/pan-zoom.js.map +1 -0
  54. package/package.json +5 -1
  55. package/src/components/ChartJSRenderer.tsx +396 -1
  56. package/src/components/ExpandableWrapper.tsx +26 -0
  57. package/src/components/LightboxOverlay.tsx +280 -4
  58. package/src/context/MCPUIConfigContext.tsx +62 -11
  59. package/src/context/MCPUIStringsContext.tsx +30 -0
  60. package/src/hooks/index.ts +13 -0
  61. package/src/index.ts +16 -0
  62. package/src/utils/iframe-coep.ts +9 -2
  63. package/src/utils/pan-zoom.ts +779 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,85 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [6.22.0] - 2026-09-18
9
+
10
+ Wheel zoom and drag pan on the surfaces that could not be read when the content
11
+ was dense: the image lightbox, and Chart.js charts in their expanded view.
12
+
13
+ ### The rule this follows
14
+
15
+ **Wheel zoom is only ever enabled on a surface that owns its viewport** — a
16
+ fullscreen overlay or the expanded modal. A component sitting inline in a
17
+ scrolling page must not swallow the wheel, or a user scrolling the page finds
18
+ themselves zooming a chart instead. That is why chart zoom defaults to the
19
+ expanded view only, and why inline zoom is opt-in.
20
+
21
+ ### Added
22
+
23
+ - **`createPanZoom(options)`** (`src/utils/pan-zoom.ts`, exported from the root
24
+ and from the `./hooks` subpath) — a dependency-free cursor-anchored zoom and
25
+ drag-pan primitive: the point under the pointer stays under the pointer.
26
+ Handles two-finger pinch, clamps to `min`/`max`, and exposes
27
+ `cancelGesture()` for the case where the surface carrying the handlers is
28
+ torn down while a pointer is still down. The pure helpers behind it
29
+ (`clampScale`, `contentPointAt`, `viewportPointOf`, `zoomAtAnchor`) are
30
+ exported too.
31
+ - **`LightboxOverlay` zooms.** Wheel, drag to pan, double-click to toggle,
32
+ pinch on touch, and the keyboard shortcuts `+` / `-` / `0` alongside the
33
+ existing Escape and arrows. Zoom controls carry a level readout, and the zoom
34
+ resets when the displayed image changes.
35
+ - **Chart.js zoom and pan**, through `chartjs-plugin-zoom` as a new **optional**
36
+ peer dependency. Absent, charts render exactly as before — no warning, no
37
+ broken UI. A reset button appears in the toolbar only while the chart is
38
+ actually zoomed. A chart's own `options.plugins.zoom` is preserved: the
39
+ renderer's block is deep-merged into it, the caller's explicit values win
40
+ (`limits`, wheel `speed`, a pan `modifierKey`, `mode`), and a caller's
41
+ `onZoom` / `onPan` is *composed* with the renderer's rather than replacing
42
+ it, so the reset control stays in sync with the viewport either way. A key
43
+ the caller left `undefined` is read as an omission, not as an erasure. And
44
+ `options.plugins.zoom: false` — Chart.js's documented per-chart plugin
45
+ opt-out — beats `chartZoom`: the plugin is never downloaded and the toolbar
46
+ offers no controls.
47
+ - **`MCPUIConfig.chartZoom`**: `'expanded'` (default), `'always'` or `'never'`.
48
+ - Four string keys: `zoomIn`, `zoomOut`, `zoomReset`, `zoomLevel`
49
+ (`'Zoom {percent}%'`). Chrome key count 271 → 275.
50
+
51
+ ### Changed
52
+
53
+ - **Every `MCPUIConfig` field is now optional**, as `MCPUIStrings` has always
54
+ been. `MCPUIConfig` grows a key in a minor release each time the library
55
+ gains a policy switch — `iframeFallbackLink` in 6.21.0, `chartZoom` here —
56
+ and while the fields were required, each addition broke the build of anyone
57
+ holding a *complete* config: a typed constant, a helper returning one, or a
58
+ direct `<MCPUIConfigContext.Provider value={…}>`. Nothing about their
59
+ behaviour had changed; only `tsc` failed. Adding a key is additive now.
60
+
61
+ The completeness guarantee moved onto the values: `DEFAULT_MCPUI_CONFIG` is
62
+ typed `Required<MCPUIConfig>` (a new key without a default is a compile error
63
+ in the library), and `useMCPUIConfig()` returns `Required<MCPUIConfig>`, so
64
+ every renderer still reads a fully resolved policy and never null-checks one.
65
+ `MCPUIConfigContext` itself stays typed `MCPUIConfig`, which makes providing
66
+ it directly *more* permissive than before. Backward compatible in both
67
+ directions: a consumer already passing a field still type-checks.
68
+
69
+ ### Fixed
70
+
71
+ - **`ExpandableWrapper` no longer closes on a drag released over its backdrop.**
72
+ When a press and its release have different targets the browser dispatches
73
+ `click` on their nearest common ancestor — the backdrop — so panning a zoomed
74
+ chart, or selecting text in an expanded table, and releasing over the 16px
75
+ margin around the panel tore the modal down mid-gesture. The close now
76
+ requires the gesture to have *begun* on the backdrop. This was reachable
77
+ before this release for any drag inside an expanded view; chart panning just
78
+ made it easy to hit.
79
+
80
+ ### Not affected
81
+
82
+ Maps and graphs already zoomed: `MapRenderer` enables Leaflet's
83
+ `scrollWheelZoom` and `GraphRenderer` registers G6's `zoom-canvas` /
84
+ `drag-canvas`. Iframes (including a diagram served from `mermaid.ink`) are
85
+ cross-origin documents and cannot be transformed by the host at all.
86
+
8
87
  ## [6.21.0] - 2026-09-17
9
88
 
10
89
  The library's iframes now work on a host that serves
@@ -28,9 +28,10 @@ const ExpandableWrapper = require("./ExpandableWrapper.cjs");
28
28
  const DegradedFallback = require("./DegradedFallback.cjs");
29
29
  const chartDataTable = require("./chart-data-table.cjs");
30
30
  const MCPUITelemetryContext = require("../context/MCPUITelemetryContext.cjs");
31
+ const MCPUIConfigContext = require("../context/MCPUIConfigContext.cjs");
31
32
  const MCPUIStringsContext = require("../context/MCPUIStringsContext.cjs");
32
33
  const formatString = require("../utils/format-string.cjs");
33
- var _tmpl$ = /* @__PURE__ */ web.template(`<h3 class="text-sm font-semibold text-gray-900 dark:text-white">`), _tmpl$2 = /* @__PURE__ */ web.template(`<button type=button class="opacity-0 group-hover:opacity-60 hover:!opacity-100 disabled:!opacity-30 disabled:cursor-not-allowed px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"><svg class="w-3 h-3 text-gray-500 dark:text-gray-400"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">`), _tmpl$3 = /* @__PURE__ */ web.template(`<tr><td class="px-3 py-6 text-center text-gray-500 dark:text-gray-400">`), _tmpl$4 = /* @__PURE__ */ web.template(`<div><table class="w-full border-collapse text-left text-sm"><caption class=sr-only><!$><!/> — <!$><!/></caption><thead class="bg-gray-50 dark:bg-gray-900/40"><tr></tr></thead><tbody><!$><!/><!$><!/>`), _tmpl$5 = /* @__PURE__ */ web.template(`<div class="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80"><div class="flex flex-col items-center gap-2"><div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div><span class="text-sm text-gray-500 dark:text-gray-400">`), _tmpl$6 = /* @__PURE__ */ web.template(`<div><div class="flex items-center justify-between gap-3 mb-3 flex-shrink-0"><div class=min-w-0></div><div class="flex items-center gap-2"><div class="inline-flex rounded-md border border-gray-200 p-0.5 dark:border-gray-600"role=group><button type=button></button><button type=button></button></div><!$><!/></div></div><p class=sr-only><!$><!/>. <!$><!/></p><!$><!/><!$><!/><div><canvas role=img></canvas><!$><!/>`), _tmpl$7 = /* @__PURE__ */ web.template(`<th scope=col class="px-3 py-2 font-medium text-gray-900 dark:text-gray-100">`), _tmpl$8 = /* @__PURE__ */ web.template(`<tr class="border-t border-gray-100 dark:border-gray-700">`), _tmpl$9 = /* @__PURE__ */ web.template(`<td class="px-3 py-2 text-gray-700 dark:text-gray-300">`);
34
+ var _tmpl$ = /* @__PURE__ */ web.template(`<h3 class="text-sm font-semibold text-gray-900 dark:text-white">`), _tmpl$2 = /* @__PURE__ */ web.template(`<button type=button class="opacity-60 hover:opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"><svg class="w-3 h-3 text-gray-500 dark:text-gray-400"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15">`), _tmpl$3 = /* @__PURE__ */ web.template(`<div class="inline-flex items-center gap-1"><button type=button class="opacity-60 hover:opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"><svg class="w-3 h-3 text-gray-500 dark:text-gray-400"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607zM13.5 10.5h-6"></path></svg></button><button type=button class="opacity-60 hover:opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"><svg class="w-3 h-3 text-gray-500 dark:text-gray-400"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607zM10.5 7.5v6m3-3h-6"></path></svg></button><!$><!/>`), _tmpl$4 = /* @__PURE__ */ web.template(`<button type=button class="opacity-0 group-hover:opacity-60 hover:!opacity-100 disabled:!opacity-30 disabled:cursor-not-allowed px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"><svg class="w-3 h-3 text-gray-500 dark:text-gray-400"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">`), _tmpl$5 = /* @__PURE__ */ web.template(`<tr><td class="px-3 py-6 text-center text-gray-500 dark:text-gray-400">`), _tmpl$6 = /* @__PURE__ */ web.template(`<div><table class="w-full border-collapse text-left text-sm"><caption class=sr-only><!$><!/> — <!$><!/></caption><thead class="bg-gray-50 dark:bg-gray-900/40"><tr></tr></thead><tbody><!$><!/><!$><!/>`), _tmpl$7 = /* @__PURE__ */ web.template(`<div class="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80"><div class="flex flex-col items-center gap-2"><div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div><span class="text-sm text-gray-500 dark:text-gray-400">`), _tmpl$8 = /* @__PURE__ */ web.template(`<div><div class="flex items-center justify-between gap-3 mb-3 flex-shrink-0"><div class=min-w-0></div><div class="flex items-center gap-2"><div class="inline-flex rounded-md border border-gray-200 p-0.5 dark:border-gray-600"role=group><button type=button></button><button type=button></button></div><!$><!/><!$><!/></div></div><p class=sr-only><!$><!/>. <!$><!/></p><!$><!/><!$><!/><div><canvas role=img></canvas><!$><!/>`), _tmpl$9 = /* @__PURE__ */ web.template(`<th scope=col class="px-3 py-2 font-medium text-gray-900 dark:text-gray-100">`), _tmpl$0 = /* @__PURE__ */ web.template(`<tr class="border-t border-gray-100 dark:border-gray-700">`), _tmpl$1 = /* @__PURE__ */ web.template(`<td class="px-3 py-2 text-gray-700 dark:text-gray-300">`);
34
35
  let ChartJS = null;
35
36
  let chartJSLoadPromise = null;
36
37
  const loadChartJS = async () => {
@@ -54,16 +55,78 @@ async function isChartJSAvailable() {
54
55
  return false;
55
56
  }
56
57
  }
58
+ let zoomPluginPromise = null;
59
+ let zoomPluginRegistered = false;
60
+ const loadZoomPlugin = () => {
61
+ if (!zoomPluginPromise) {
62
+ zoomPluginPromise = import(
63
+ /* @vite-ignore */
64
+ "chartjs-plugin-zoom"
65
+ ).then((module2) => module2.default ?? module2).catch(() => null);
66
+ }
67
+ return zoomPluginPromise;
68
+ };
69
+ const ensureZoomPlugin = async () => {
70
+ try {
71
+ const plugin = await loadZoomPlugin();
72
+ if (!plugin) return null;
73
+ if (!zoomPluginRegistered) {
74
+ const Chart = await loadChartJS();
75
+ if (typeof (Chart == null ? void 0 : Chart.register) !== "function") return null;
76
+ Chart.register(plugin);
77
+ zoomPluginRegistered = true;
78
+ }
79
+ return plugin;
80
+ } catch {
81
+ return null;
82
+ }
83
+ };
84
+ const ZOOMABLE_CHART_TYPES = /* @__PURE__ */ new Set(["bar", "line", "scatter", "bubble"]);
85
+ const CHART_ZOOM_BUTTON_FACTOR = 1.5;
86
+ const isChartZoomed = (chart) => {
87
+ var _a;
88
+ if (typeof (chart == null ? void 0 : chart.isZoomedOrPanned) === "function") return Boolean(chart.isZoomedOrPanned());
89
+ const level = (_a = chart == null ? void 0 : chart.getZoomLevel) == null ? void 0 : _a.call(chart);
90
+ return typeof level === "number" ? level !== 1 : false;
91
+ };
92
+ const isPlainZoomObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
93
+ const mergeZoomOptions = (base, override) => {
94
+ if (!isPlainZoomObject(override)) return base;
95
+ const merged = {
96
+ ...base
97
+ };
98
+ for (const [key, value] of Object.entries(override)) {
99
+ if (value === void 0) continue;
100
+ const ours = merged[key];
101
+ if (isPlainZoomObject(ours) && isPlainZoomObject(value)) {
102
+ merged[key] = mergeZoomOptions(ours, value);
103
+ } else if (typeof ours === "function" && typeof value === "function") {
104
+ const rendererHook = ours;
105
+ const callerHook = value;
106
+ merged[key] = (...args) => {
107
+ rendererHook(...args);
108
+ return callerHook(...args);
109
+ };
110
+ } else {
111
+ merged[key] = value;
112
+ }
113
+ }
114
+ return merged;
115
+ };
57
116
  const ContextAwareChartLayout = (props) => props.render(ExpandableWrapper.useExpanded());
58
117
  const ChartJSRenderer = (props) => {
59
118
  const [isLoading, setIsLoading] = solidJs.createSignal(true);
60
119
  const [error, setError] = solidJs.createSignal();
61
120
  const [activeView, setActiveView] = solidJs.createSignal("chart");
121
+ const [isExpanded, setIsExpanded] = solidJs.createSignal(false);
122
+ const [zoomPlugin, setZoomPlugin] = solidJs.createSignal(null);
123
+ const [isZoomed, setIsZoomed] = solidJs.createSignal(false);
62
124
  let canvasRef;
63
125
  let chartInstance;
64
126
  let renderVersion = 0;
65
127
  const params = () => props.component.params;
66
128
  const telemetry = MCPUITelemetryContext.useTelemetry();
129
+ const config = MCPUIConfigContext.useMCPUIConfig();
67
130
  const strings = MCPUIStringsContext.useMCPUIStrings();
68
131
  const tableData = solidJs.createMemo(() => chartDataTable.chartToDataTable(params(), {
69
132
  series: strings.chartTableSeries,
@@ -78,6 +141,60 @@ const ChartJSRenderer = (props) => {
78
141
  type: params().type,
79
142
  data: params().data
80
143
  }, null, 2);
144
+ const zoomWanted = solidJs.createMemo(() => {
145
+ var _a, _b;
146
+ if (!ZOOMABLE_CHART_TYPES.has(params().type)) return false;
147
+ const hostZoom = (_b = (_a = params().options) == null ? void 0 : _a.plugins) == null ? void 0 : _b.zoom;
148
+ if (hostZoom === false || hostZoom === null) return false;
149
+ const mode = config.chartZoom;
150
+ if (mode === "never") return false;
151
+ if (mode === "always") return true;
152
+ return isExpanded();
153
+ });
154
+ solidJs.createEffect(() => {
155
+ if (!zoomWanted() || solidJs.untrack(zoomPlugin)) return;
156
+ void ensureZoomPlugin().then((plugin) => {
157
+ if (plugin) setZoomPlugin(() => plugin);
158
+ });
159
+ });
160
+ const zoomPluginOptions = {
161
+ zoom: {
162
+ wheel: {
163
+ enabled: true
164
+ },
165
+ pinch: {
166
+ enabled: true
167
+ },
168
+ // Drag is reserved for panning below; a drag-to-select zoom would make
169
+ // the two gestures fight over the same mouse button.
170
+ drag: {
171
+ enabled: false
172
+ },
173
+ mode: "xy",
174
+ onZoom: ({
175
+ chart
176
+ }) => setIsZoomed(isChartZoomed(chart))
177
+ },
178
+ pan: {
179
+ enabled: true,
180
+ mode: "xy",
181
+ onPan: ({
182
+ chart
183
+ }) => setIsZoomed(isChartZoomed(chart))
184
+ }
185
+ };
186
+ const zoomOptions = solidJs.createMemo(() => zoomWanted() && zoomPlugin() ? zoomPluginOptions : null);
187
+ const zoomControlsShown = () => Boolean(zoomOptions()) && activeView() === "chart" && !error();
188
+ const handleZoomBy = (factor) => {
189
+ var _a;
190
+ (_a = chartInstance == null ? void 0 : chartInstance.zoom) == null ? void 0 : _a.call(chartInstance, factor);
191
+ setIsZoomed(isChartZoomed(chartInstance));
192
+ };
193
+ const handleResetZoom = () => {
194
+ var _a;
195
+ (_a = chartInstance == null ? void 0 : chartInstance.resetZoom) == null ? void 0 : _a.call(chartInstance);
196
+ setIsZoomed(false);
197
+ };
81
198
  const handleExportPNG = () => {
82
199
  if (!canvasRef) return;
83
200
  const url = canvasRef.toDataURL("image/png");
@@ -87,9 +204,10 @@ const ChartJSRenderer = (props) => {
87
204
  a.click();
88
205
  };
89
206
  solidJs.createEffect(async () => {
90
- var _a, _b, _c, _d, _e, _f;
207
+ var _a, _b, _c, _d, _e, _f, _g, _h;
91
208
  if (!canvasRef) return;
92
209
  const chartParams = params();
210
+ const zoom = zoomOptions();
93
211
  const version = ++renderVersion;
94
212
  setIsLoading(true);
95
213
  setError(void 0);
@@ -100,17 +218,28 @@ const ChartJSRenderer = (props) => {
100
218
  chartInstance.destroy();
101
219
  chartInstance = null;
102
220
  }
221
+ const hostZoomOptions = (_b = (_a = chartParams.options) == null ? void 0 : _a.plugins) == null ? void 0 : _b.zoom;
103
222
  const baseOptions = {
104
223
  responsive: true,
105
224
  maintainAspectRatio: false,
106
225
  ...chartParams.options,
107
226
  plugins: {
108
- ...(_a = chartParams.options) == null ? void 0 : _a.plugins,
227
+ ...(_c = chartParams.options) == null ? void 0 : _c.plugins,
109
228
  legend: {
110
229
  display: true,
111
230
  position: "bottom",
112
- ...(_c = (_b = chartParams.options) == null ? void 0 : _b.plugins) == null ? void 0 : _c.legend
113
- }
231
+ ...(_e = (_d = chartParams.options) == null ? void 0 : _d.plugins) == null ? void 0 : _e.legend
232
+ },
233
+ // Present only once the optional peer has loaded AND the current
234
+ // surface wants zoom. Merged rather than assigned, so a consumer's
235
+ // own `plugins.zoom` survives; when zoom is off, the spread above
236
+ // has already left their block exactly as they wrote it.
237
+ // An explicit `plugins.zoom: false` never reaches here: `zoomWanted`
238
+ // already returned false for it, so `zoom` is null and the spread
239
+ // above carries the host's own value through untouched.
240
+ ...zoom ? {
241
+ zoom: mergeZoomOptions(zoom, hostZoomOptions)
242
+ } : {}
114
243
  }
115
244
  };
116
245
  if (chartParams.timeAxis) {
@@ -118,7 +247,7 @@ const ChartJSRenderer = (props) => {
118
247
  baseOptions.scales = {
119
248
  ...baseOptions.scales,
120
249
  x: {
121
- ...(_d = baseOptions.scales) == null ? void 0 : _d.x,
250
+ ...(_f = baseOptions.scales) == null ? void 0 : _f.x,
122
251
  type: "time",
123
252
  time: {
124
253
  parser: ta.parser,
@@ -139,6 +268,7 @@ const ChartJSRenderer = (props) => {
139
268
  data: chartParams.data,
140
269
  options: baseOptions
141
270
  });
271
+ setIsZoomed(false);
142
272
  setIsLoading(false);
143
273
  } catch (err) {
144
274
  if (version !== renderVersion) return;
@@ -148,11 +278,11 @@ const ChartJSRenderer = (props) => {
148
278
  telemetry == null ? void 0 : telemetry.dispatch({
149
279
  type: "render:error",
150
280
  errorMessage: error2.message,
151
- id: ((_e = props.component) == null ? void 0 : _e.id) ?? "",
281
+ id: ((_g = props.component) == null ? void 0 : _g.id) ?? "",
152
282
  componentType: "chart",
153
283
  ts: Date.now()
154
284
  });
155
- (_f = props.onError) == null ? void 0 : _f.call(props, error2);
285
+ (_h = props.onError) == null ? void 0 : _h.call(props, error2);
156
286
  }
157
287
  });
158
288
  solidJs.createEffect(() => {
@@ -182,10 +312,11 @@ const ChartJSRenderer = (props) => {
182
312
  get toolbarVariant() {
183
313
  return props.toolbarVariant;
184
314
  },
315
+ onExpandedChange: setIsExpanded,
185
316
  get children() {
186
317
  return web.createComponent(ContextAwareChartLayout, {
187
- render: (isExpanded) => (() => {
188
- var _el$ = web.getNextElement(_tmpl$6), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$5 = _el$3.nextSibling, _el$6 = _el$5.firstChild, _el$7 = _el$6.firstChild, _el$8 = _el$7.nextSibling, _el$0 = _el$6.nextSibling, [_el$1, _co$] = web.getNextMarker(_el$0.nextSibling), _el$10 = _el$2.nextSibling, _el$12 = _el$10.firstChild, [_el$13, _co$2] = web.getNextMarker(_el$12.nextSibling), _el$11 = _el$13.nextSibling, _el$14 = _el$11.nextSibling, [_el$15, _co$3] = web.getNextMarker(_el$14.nextSibling), _el$41 = _el$10.nextSibling, [_el$42, _co$9] = web.getNextMarker(_el$41.nextSibling), _el$43 = _el$42.nextSibling, [_el$44, _co$0] = web.getNextMarker(_el$43.nextSibling), _el$33 = _el$44.nextSibling, _el$34 = _el$33.firstChild, _el$39 = _el$34.nextSibling, [_el$40, _co$8] = web.getNextMarker(_el$39.nextSibling);
318
+ render: (isExpanded2) => (() => {
319
+ var _el$ = web.getNextElement(_tmpl$8), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$5 = _el$3.nextSibling, _el$6 = _el$5.firstChild, _el$7 = _el$6.firstChild, _el$8 = _el$7.nextSibling, _el$14 = _el$6.nextSibling, [_el$15, _co$2] = web.getNextMarker(_el$14.nextSibling), _el$16 = _el$15.nextSibling, [_el$17, _co$3] = web.getNextMarker(_el$16.nextSibling), _el$18 = _el$2.nextSibling, _el$20 = _el$18.firstChild, [_el$21, _co$4] = web.getNextMarker(_el$20.nextSibling), _el$19 = _el$21.nextSibling, _el$22 = _el$19.nextSibling, [_el$23, _co$5] = web.getNextMarker(_el$22.nextSibling), _el$49 = _el$18.nextSibling, [_el$50, _co$1] = web.getNextMarker(_el$49.nextSibling), _el$51 = _el$50.nextSibling, [_el$52, _co$10] = web.getNextMarker(_el$51.nextSibling), _el$41 = _el$52.nextSibling, _el$42 = _el$41.firstChild, _el$47 = _el$42.nextSibling, [_el$48, _co$0] = web.getNextMarker(_el$47.nextSibling);
189
320
  web.insert(_el$3, web.createComponent(solidJs.Show, {
190
321
  get when() {
191
322
  return params().title;
@@ -200,18 +331,63 @@ const ChartJSRenderer = (props) => {
200
331
  web.insert(_el$7, () => strings.chartView);
201
332
  _el$8.$$click = () => setActiveView("data");
202
333
  web.insert(_el$8, () => strings.chartDataView);
334
+ web.insert(_el$5, web.createComponent(solidJs.Show, {
335
+ get when() {
336
+ return zoomControlsShown();
337
+ },
338
+ get children() {
339
+ var _el$9 = web.getNextElement(_tmpl$3), _el$0 = _el$9.firstChild, _el$1 = _el$0.nextSibling, _el$11 = _el$1.nextSibling, [_el$12, _co$] = web.getNextMarker(_el$11.nextSibling);
340
+ _el$0.$$click = () => handleZoomBy(1 / CHART_ZOOM_BUTTON_FACTOR);
341
+ _el$1.$$click = () => handleZoomBy(CHART_ZOOM_BUTTON_FACTOR);
342
+ web.insert(_el$9, web.createComponent(solidJs.Show, {
343
+ get when() {
344
+ return isZoomed();
345
+ },
346
+ get children() {
347
+ var _el$10 = web.getNextElement(_tmpl$2);
348
+ _el$10.$$click = handleResetZoom;
349
+ web.effect((_p$) => {
350
+ var _v$ = strings.zoomReset, _v$2 = strings.zoomReset;
351
+ _v$ !== _p$.e && web.setAttribute(_el$10, "title", _p$.e = _v$);
352
+ _v$2 !== _p$.t && web.setAttribute(_el$10, "aria-label", _p$.t = _v$2);
353
+ return _p$;
354
+ }, {
355
+ e: void 0,
356
+ t: void 0
357
+ });
358
+ web.runHydrationEvents();
359
+ return _el$10;
360
+ }
361
+ }), _el$12, _co$);
362
+ web.effect((_p$) => {
363
+ var _v$3 = strings.zoomOut, _v$4 = strings.zoomOut, _v$5 = strings.zoomIn, _v$6 = strings.zoomIn;
364
+ _v$3 !== _p$.e && web.setAttribute(_el$0, "title", _p$.e = _v$3);
365
+ _v$4 !== _p$.t && web.setAttribute(_el$0, "aria-label", _p$.t = _v$4);
366
+ _v$5 !== _p$.a && web.setAttribute(_el$1, "title", _p$.a = _v$5);
367
+ _v$6 !== _p$.o && web.setAttribute(_el$1, "aria-label", _p$.o = _v$6);
368
+ return _p$;
369
+ }, {
370
+ e: void 0,
371
+ t: void 0,
372
+ a: void 0,
373
+ o: void 0
374
+ });
375
+ web.runHydrationEvents();
376
+ return _el$9;
377
+ }
378
+ }), _el$15, _co$2);
203
379
  web.insert(_el$5, web.createComponent(solidJs.Show, {
204
380
  get when() {
205
381
  return exportEnabled();
206
382
  },
207
383
  get children() {
208
- var _el$9 = web.getNextElement(_tmpl$2);
209
- _el$9.$$click = handleExportPNG;
384
+ var _el$13 = web.getNextElement(_tmpl$4);
385
+ _el$13.$$click = handleExportPNG;
210
386
  web.effect((_p$) => {
211
- var _v$ = activeView() !== "chart" || isLoading() || Boolean(error()), _v$2 = strings.chartDownloadPng, _v$3 = strings.chartDownloadPngAria;
212
- _v$ !== _p$.e && web.setProperty(_el$9, "disabled", _p$.e = _v$);
213
- _v$2 !== _p$.t && web.setAttribute(_el$9, "title", _p$.t = _v$2);
214
- _v$3 !== _p$.a && web.setAttribute(_el$9, "aria-label", _p$.a = _v$3);
387
+ var _v$7 = activeView() !== "chart" || isLoading() || Boolean(error()), _v$8 = strings.chartDownloadPng, _v$9 = strings.chartDownloadPngAria;
388
+ _v$7 !== _p$.e && web.setProperty(_el$13, "disabled", _p$.e = _v$7);
389
+ _v$8 !== _p$.t && web.setAttribute(_el$13, "title", _p$.t = _v$8);
390
+ _v$9 !== _p$.a && web.setAttribute(_el$13, "aria-label", _p$.a = _v$9);
215
391
  return _p$;
216
392
  }, {
217
393
  e: void 0,
@@ -219,12 +395,12 @@ const ChartJSRenderer = (props) => {
219
395
  a: void 0
220
396
  });
221
397
  web.runHydrationEvents();
222
- return _el$9;
398
+ return _el$13;
223
399
  }
224
- }), _el$1, _co$);
225
- web.setAttribute(_el$10, "id", descriptionId);
226
- web.insert(_el$10, title, _el$13, _co$2);
227
- web.insert(_el$10, () => strings.chartDataSummary, _el$15, _co$3);
400
+ }), _el$17, _co$3);
401
+ web.setAttribute(_el$18, "id", descriptionId);
402
+ web.insert(_el$18, title, _el$21, _co$4);
403
+ web.insert(_el$18, () => strings.chartDataSummary, _el$23, _co$5);
228
404
  web.insert(_el$, web.createComponent(solidJs.Show, {
229
405
  get when() {
230
406
  return web.memo(() => !!error())() && activeView() === "chart";
@@ -241,103 +417,103 @@ const ChartJSRenderer = (props) => {
241
417
  }
242
418
  }, tableData));
243
419
  }
244
- }), _el$42, _co$9);
420
+ }), _el$50, _co$1);
245
421
  web.insert(_el$, web.createComponent(solidJs.Show, {
246
422
  get when() {
247
423
  return activeView() === "data";
248
424
  },
249
425
  get children() {
250
- var _el$16 = web.getNextElement(_tmpl$4), _el$17 = _el$16.firstChild, _el$18 = _el$17.firstChild, _el$20 = _el$18.firstChild, [_el$21, _co$4] = web.getNextMarker(_el$20.nextSibling), _el$19 = _el$21.nextSibling, _el$22 = _el$19.nextSibling, [_el$23, _co$5] = web.getNextMarker(_el$22.nextSibling), _el$24 = _el$18.nextSibling, _el$25 = _el$24.firstChild, _el$26 = _el$24.nextSibling, _el$29 = _el$26.firstChild, [_el$30, _co$6] = web.getNextMarker(_el$29.nextSibling), _el$31 = _el$30.nextSibling, [_el$32, _co$7] = web.getNextMarker(_el$31.nextSibling);
251
- web.insert(_el$18, title, _el$21, _co$4);
252
- web.insert(_el$18, () => strings.chartDataTable, _el$23, _co$5);
253
- web.insert(_el$25, web.createComponent(solidJs.For, {
426
+ var _el$24 = web.getNextElement(_tmpl$6), _el$25 = _el$24.firstChild, _el$26 = _el$25.firstChild, _el$28 = _el$26.firstChild, [_el$29, _co$6] = web.getNextMarker(_el$28.nextSibling), _el$27 = _el$29.nextSibling, _el$30 = _el$27.nextSibling, [_el$31, _co$7] = web.getNextMarker(_el$30.nextSibling), _el$32 = _el$26.nextSibling, _el$33 = _el$32.firstChild, _el$34 = _el$32.nextSibling, _el$37 = _el$34.firstChild, [_el$38, _co$8] = web.getNextMarker(_el$37.nextSibling), _el$39 = _el$38.nextSibling, [_el$40, _co$9] = web.getNextMarker(_el$39.nextSibling);
427
+ web.insert(_el$26, title, _el$29, _co$6);
428
+ web.insert(_el$26, () => strings.chartDataTable, _el$31, _co$7);
429
+ web.insert(_el$33, web.createComponent(solidJs.For, {
254
430
  get each() {
255
431
  return tableData().columns;
256
432
  },
257
433
  children: (column) => (() => {
258
- var _el$45 = web.getNextElement(_tmpl$7);
259
- web.insert(_el$45, column);
260
- return _el$45;
434
+ var _el$53 = web.getNextElement(_tmpl$9);
435
+ web.insert(_el$53, column);
436
+ return _el$53;
261
437
  })()
262
438
  }));
263
- web.insert(_el$26, web.createComponent(solidJs.For, {
439
+ web.insert(_el$34, web.createComponent(solidJs.For, {
264
440
  get each() {
265
441
  return tableData().rows;
266
442
  },
267
443
  children: (row) => (() => {
268
- var _el$46 = web.getNextElement(_tmpl$8);
269
- web.insert(_el$46, web.createComponent(solidJs.For, {
444
+ var _el$54 = web.getNextElement(_tmpl$0);
445
+ web.insert(_el$54, web.createComponent(solidJs.For, {
270
446
  get each() {
271
447
  return tableData().columns;
272
448
  },
273
449
  children: (_column, index) => (() => {
274
- var _el$47 = web.getNextElement(_tmpl$9);
275
- web.insert(_el$47, () => String(row[index()] ?? ""));
276
- return _el$47;
450
+ var _el$55 = web.getNextElement(_tmpl$1);
451
+ web.insert(_el$55, () => String(row[index()] ?? ""));
452
+ return _el$55;
277
453
  })()
278
454
  }));
279
- return _el$46;
455
+ return _el$54;
280
456
  })()
281
- }), _el$30, _co$6);
282
- web.insert(_el$26, web.createComponent(solidJs.Show, {
457
+ }), _el$38, _co$8);
458
+ web.insert(_el$34, web.createComponent(solidJs.Show, {
283
459
  get when() {
284
460
  return tableData().rows.length === 0;
285
461
  },
286
462
  get children() {
287
- var _el$27 = web.getNextElement(_tmpl$3), _el$28 = _el$27.firstChild;
288
- web.insert(_el$28, () => strings.chartNoData);
289
- web.effect(() => web.setAttribute(_el$28, "colspan", Math.max(1, tableData().columns.length)));
290
- return _el$27;
463
+ var _el$35 = web.getNextElement(_tmpl$5), _el$36 = _el$35.firstChild;
464
+ web.insert(_el$36, () => strings.chartNoData);
465
+ web.effect(() => web.setAttribute(_el$36, "colspan", Math.max(1, tableData().columns.length)));
466
+ return _el$35;
291
467
  }
292
- }), _el$32, _co$7);
468
+ }), _el$40, _co$9);
293
469
  web.effect((_p$) => {
294
- var _v$4 = `w-full overflow-auto rounded border border-gray-200 dark:border-gray-700 ${isExpanded() ? "flex-1 min-h-0" : ""}`, _v$5 = isExpanded() ? void 0 : {
470
+ var _v$0 = `w-full overflow-auto rounded border border-gray-200 dark:border-gray-700 ${isExpanded2() ? "flex-1 min-h-0" : ""}`, _v$1 = isExpanded2() ? void 0 : {
295
471
  height: params().height || "250px",
296
472
  "max-height": "70vh"
297
473
  };
298
- _v$4 !== _p$.e && web.className(_el$16, _p$.e = _v$4);
299
- _p$.t = web.style(_el$16, _v$5, _p$.t);
474
+ _v$0 !== _p$.e && web.className(_el$24, _p$.e = _v$0);
475
+ _p$.t = web.style(_el$24, _v$1, _p$.t);
300
476
  return _p$;
301
477
  }, {
302
478
  e: void 0,
303
479
  t: void 0
304
480
  });
305
- return _el$16;
481
+ return _el$24;
306
482
  }
307
- }), _el$44, _co$0);
483
+ }), _el$52, _co$10);
308
484
  var _ref$ = canvasRef;
309
- typeof _ref$ === "function" ? web.use(_ref$, _el$34) : canvasRef = _el$34;
310
- web.setAttribute(_el$34, "aria-describedby", descriptionId);
311
- web.insert(_el$33, web.createComponent(solidJs.Show, {
485
+ typeof _ref$ === "function" ? web.use(_ref$, _el$42) : canvasRef = _el$42;
486
+ web.setAttribute(_el$42, "aria-describedby", descriptionId);
487
+ web.insert(_el$41, web.createComponent(solidJs.Show, {
312
488
  get when() {
313
489
  return web.memo(() => !!isLoading())() && activeView() === "chart";
314
490
  },
315
491
  get children() {
316
- var _el$35 = web.getNextElement(_tmpl$5), _el$36 = _el$35.firstChild, _el$37 = _el$36.firstChild, _el$38 = _el$37.nextSibling;
317
- web.insert(_el$38, () => strings.chartLoading);
318
- return _el$35;
492
+ var _el$43 = web.getNextElement(_tmpl$7), _el$44 = _el$43.firstChild, _el$45 = _el$44.firstChild, _el$46 = _el$45.nextSibling;
493
+ web.insert(_el$46, () => strings.chartLoading);
494
+ return _el$43;
319
495
  }
320
- }), _el$40, _co$8);
496
+ }), _el$48, _co$0);
321
497
  web.effect((_p$) => {
322
- var _v$6 = `relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${isExpanded() ? "flex-1 min-h-0 flex flex-col" : ""} ${params().className || ""}`, _v$7 = strings.chartViewSelector, _v$8 = activeView() === "chart", _v$9 = `rounded px-2 py-1 text-xs font-medium transition-colors ${activeView() === "chart" ? "bg-gray-900 text-white dark:bg-gray-100 dark:text-gray-900" : "text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700"}`, _v$0 = activeView() === "data", _v$1 = `rounded px-2 py-1 text-xs font-medium transition-colors ${activeView() === "data" ? "bg-gray-900 text-white dark:bg-gray-100 dark:text-gray-900" : "text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700"}`, _v$10 = `relative w-full ${isExpanded() ? "flex-1 min-h-0" : ""}`, _v$11 = error() || activeView() === "data" ? {
498
+ var _v$10 = `relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${isExpanded2() ? "flex-1 min-h-0 flex flex-col" : ""} ${params().className || ""}`, _v$11 = strings.chartViewSelector, _v$12 = activeView() === "chart", _v$13 = `rounded px-2 py-1 text-xs font-medium transition-colors ${activeView() === "chart" ? "bg-gray-900 text-white dark:bg-gray-100 dark:text-gray-900" : "text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700"}`, _v$14 = activeView() === "data", _v$15 = `rounded px-2 py-1 text-xs font-medium transition-colors ${activeView() === "data" ? "bg-gray-900 text-white dark:bg-gray-100 dark:text-gray-900" : "text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700"}`, _v$16 = `relative w-full ${isExpanded2() ? "flex-1 min-h-0" : ""}`, _v$17 = error() || activeView() === "data" ? {
323
499
  display: "none"
324
- } : isExpanded() ? {
500
+ } : isExpanded2() ? {
325
501
  height: "100%",
326
502
  display: "block"
327
503
  } : {
328
504
  height: params().height || "250px",
329
505
  display: "block"
330
- }, _v$12 = title(), _v$13 = activeView() !== "chart" || Boolean(error());
331
- _v$6 !== _p$.e && web.className(_el$, _p$.e = _v$6);
332
- _v$7 !== _p$.t && web.setAttribute(_el$6, "aria-label", _p$.t = _v$7);
333
- _v$8 !== _p$.a && web.setAttribute(_el$7, "aria-pressed", _p$.a = _v$8);
334
- _v$9 !== _p$.o && web.className(_el$7, _p$.o = _v$9);
335
- _v$0 !== _p$.i && web.setAttribute(_el$8, "aria-pressed", _p$.i = _v$0);
336
- _v$1 !== _p$.n && web.className(_el$8, _p$.n = _v$1);
337
- _v$10 !== _p$.s && web.className(_el$33, _p$.s = _v$10);
338
- _p$.h = web.style(_el$33, _v$11, _p$.h);
339
- _v$12 !== _p$.r && web.setAttribute(_el$34, "aria-label", _p$.r = _v$12);
340
- _v$13 !== _p$.d && web.setAttribute(_el$34, "aria-hidden", _p$.d = _v$13);
506
+ }, _v$18 = title(), _v$19 = activeView() !== "chart" || Boolean(error());
507
+ _v$10 !== _p$.e && web.className(_el$, _p$.e = _v$10);
508
+ _v$11 !== _p$.t && web.setAttribute(_el$6, "aria-label", _p$.t = _v$11);
509
+ _v$12 !== _p$.a && web.setAttribute(_el$7, "aria-pressed", _p$.a = _v$12);
510
+ _v$13 !== _p$.o && web.className(_el$7, _p$.o = _v$13);
511
+ _v$14 !== _p$.i && web.setAttribute(_el$8, "aria-pressed", _p$.i = _v$14);
512
+ _v$15 !== _p$.n && web.className(_el$8, _p$.n = _v$15);
513
+ _v$16 !== _p$.s && web.className(_el$41, _p$.s = _v$16);
514
+ _p$.h = web.style(_el$41, _v$17, _p$.h);
515
+ _v$18 !== _p$.r && web.setAttribute(_el$42, "aria-label", _p$.r = _v$18);
516
+ _v$19 !== _p$.d && web.setAttribute(_el$42, "aria-hidden", _p$.d = _v$19);
341
517
  return _p$;
342
518
  }, {
343
519
  e: void 0,