@webmcp-auto-ui/ui 2.5.19 → 2.5.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmcp-auto-ui/ui",
3
- "version": "2.5.19",
3
+ "version": "2.5.20",
4
4
  "description": "Svelte 5 UI components — primitives, widgets, window manager",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -50,6 +50,7 @@
50
50
  "@types/d3": "^7.4.3",
51
51
  "bits-ui": "^2.17.2",
52
52
  "clsx": "^2.1.1",
53
+ "html-to-image": "^1.11.13",
53
54
  "tailwind-merge": "^3.5.0",
54
55
  "tailwind-variants": "^3.2.2"
55
56
  }
@@ -3,6 +3,10 @@
3
3
  * Determines the best export format based on widget type and triggers a file download.
4
4
  */
5
5
 
6
+ import { toPng } from 'html-to-image';
7
+
8
+ const TARGET_PNG_WIDTH = 2048;
9
+
6
10
  // ── helpers ──────────────────────────────────────────────────────────────────
7
11
 
8
12
  function timestamp(): string {
@@ -81,54 +85,27 @@ function exportCsv(type: string, data: Record<string, unknown>): void {
81
85
  downloadFile(lines.join('\r\n'), filename(type, 'csv'), 'text/csv;charset=utf-8');
82
86
  }
83
87
 
84
- // ── PNG via SVG ───────────────────────────────────────────────────────────────
85
-
86
- function svgToPng(svgEl: SVGElement, name: string): void {
87
- // Inline computed styles on the SVG to improve fidelity when rendered off-DOM
88
- const svgData = new XMLSerializer().serializeToString(svgEl);
89
- const canvas = document.createElement('canvas');
90
- const ctx = canvas.getContext('2d')!;
91
- const img = new Image();
92
- const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
93
- const url = URL.createObjectURL(blob);
94
- img.onload = () => {
95
- canvas.width = img.naturalWidth || svgEl.clientWidth || 800;
96
- canvas.height = img.naturalHeight || svgEl.clientHeight || 600;
97
- ctx.drawImage(img, 0, 0);
98
- const pngUrl = canvas.toDataURL('image/png');
99
- downloadFile(pngUrl, name);
100
- URL.revokeObjectURL(url);
101
- };
102
- img.onerror = () => {
103
- // Fallback: try canvas.toDataURL directly if available
104
- URL.revokeObjectURL(url);
105
- };
106
- img.src = url;
107
- }
108
-
109
- function canvasToPng(canvasEl: HTMLCanvasElement, name: string): void {
110
- const url = canvasEl.toDataURL('image/png');
111
- downloadFile(url, name);
112
- }
88
+ // ── PNG via html-to-image ─────────────────────────────────────────────────────
113
89
 
114
- function exportPng(type: string, containerEl: HTMLElement): void {
90
+ async function exportPng(type: string, containerEl: HTMLElement): Promise<void> {
115
91
  const name = filename(type, 'png');
116
-
117
- // Prefer canvas (chart.js renders to <canvas>)
118
- const canvasEl = containerEl.querySelector('canvas');
119
- if (canvasEl) {
120
- canvasToPng(canvasEl, name);
121
- return;
92
+ const clientW = Math.max(containerEl.clientWidth, 1);
93
+ const clientH = Math.max(containerEl.clientHeight, 1);
94
+ const pixelRatio = TARGET_PNG_WIDTH / clientW;
95
+
96
+ try {
97
+ const dataUrl = await toPng(containerEl, {
98
+ pixelRatio,
99
+ cacheBust: true,
100
+ // Let the DOM speak for its own styles — no forced background
101
+ // so dark/light themes both render correctly.
102
+ });
103
+ downloadFile(dataUrl, name);
104
+ } catch (err) {
105
+ console.error('[exportWidget] PNG export failed for type', type, err);
106
+ alert(`Export PNG impossible : ${err instanceof Error ? err.message : String(err)}`);
122
107
  }
123
-
124
- // Fall back to SVG
125
- const svgEl = containerEl.querySelector('svg');
126
- if (svgEl) {
127
- svgToPng(svgEl as SVGElement, name);
128
- return;
129
- }
130
-
131
- console.warn('[exportWidget] No <canvas> or <svg> found in container for type:', type);
108
+ // pixelRatio used as-is; final canvas size = clientW*pixelRatio × clientH*pixelRatio = 2048 × (2048 * clientH / clientW)
132
109
  }
133
110
 
134
111
  // ── Markdown ──────────────────────────────────────────────────────────────────
@@ -209,53 +186,39 @@ const HTML_FMT: ExportFormat = { id: 'html', label: 'HTML', icon: '🌐' };
209
186
  * Return the list of export formats available for a given widget type.
210
187
  */
211
188
  export function getExportFormats(type: string, containerEl?: HTMLElement): ExportFormat[] {
189
+ const base: ExportFormat[] = [PNG_FMT, JSON_FMT];
212
190
  switch (type) {
213
191
  case 'data-table':
214
192
  case 'grid-data':
215
193
  case 'kv':
216
194
  case 'list':
217
- return [CSV_FMT, JSON_FMT];
218
-
219
- case 'chart':
220
- case 'chart-rich':
221
- case 'sankey':
222
- case 'd3':
223
- case 'hemicycle':
224
- case 'map':
225
- return [PNG_FMT, JSON_FMT];
226
-
195
+ return [CSV_FMT, ...base];
227
196
  case 'text':
228
197
  case 'code':
229
198
  case 'log':
230
- return [MD_FMT, JSON_FMT];
231
-
199
+ return [MD_FMT, ...base];
232
200
  case 'js-sandbox':
233
- return [HTML_FMT, JSON_FMT];
234
-
235
- case 'gallery':
236
- case 'carousel':
237
- return [JSON_FMT];
238
-
201
+ return [HTML_FMT, ...base];
239
202
  default:
240
- return [JSON_FMT];
203
+ return base;
241
204
  }
242
205
  }
243
206
 
244
207
  /**
245
208
  * Export a widget in a specific format chosen by the user.
246
209
  */
247
- export function exportWidgetAs(
210
+ export async function exportWidgetAs(
248
211
  format: string,
249
212
  type: string,
250
213
  data: Record<string, unknown>,
251
214
  containerEl?: HTMLElement
252
- ): void {
215
+ ): Promise<void> {
253
216
  switch (format) {
254
217
  case 'csv':
255
218
  exportCsv(type, data);
256
219
  break;
257
220
  case 'png':
258
- if (containerEl) exportPng(type, containerEl);
221
+ if (containerEl) await exportPng(type, containerEl);
259
222
  else console.warn('[exportWidgetAs] containerEl required for PNG export');
260
223
  break;
261
224
  case 'md':
@@ -281,11 +244,11 @@ export function exportWidgetAs(
281
244
  * @param data The widget's data object
282
245
  * @param containerEl Optional DOM container — required for PNG exports
283
246
  */
284
- export function exportWidget(
247
+ export async function exportWidget(
285
248
  type: string,
286
249
  data: Record<string, unknown>,
287
250
  containerEl?: HTMLElement
288
- ): void {
251
+ ): Promise<void> {
289
252
  switch (type) {
290
253
  // ── CSV ──
291
254
  case 'data-table':
@@ -303,7 +266,7 @@ export function exportWidget(
303
266
  case 'hemicycle':
304
267
  case 'map':
305
268
  if (containerEl) {
306
- exportPng(type, containerEl);
269
+ await exportPng(type, containerEl);
307
270
  } else {
308
271
  console.warn('[exportWidget] containerEl required for PNG export of type:', type);
309
272
  }