ok-canvas-export 1.0.6 → 1.1.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.
package/README.md CHANGED
@@ -1,32 +1,54 @@
1
- # OK-CANVAS-EXPORT LIBRARY
1
+ # OK-CANVAS-EXPORT
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/ok-canvas-export.svg)](https://www.npmjs.com/package/ok-canvas-export)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
3
+ Export complete SVG diagrams and canvases as high-quality PNG or SVG files, including content outside the visible viewport.
5
4
 
6
- Export SVG diagrams and canvases as high-quality images with full render pass support.
5
+ [![npm version](https://badge.fury.io/js/ok-canvas-export.svg)](https://www.npmjs.com/package/ok-canvas-export)
7
6
 
8
7
  ## Features
9
8
 
10
- - Supports PNG and SVG export formats
11
- - High-quality rendering with adjustable scale
9
+ - Export diagrams as PNG or SVG
10
+ - Export complete SVG content, even when parts are outside the visible viewport
11
+ - High-quality PNG rendering with configurable scale
12
+ - Configurable export margin
13
+ - Custom background colors
14
+ - Transparent background support
15
+ - Automatic CSS custom property resolution
12
16
  - TypeScript support
13
- - Zero dependencies (except React)
17
+ - React hook for simple integration
18
+ - Works with Vite, Next.js, Create React App, react-scripts, Bun, npm, pnpm and Yarn
19
+ - Zero runtime dependencies except React
14
20
 
15
21
  ## Installation
16
22
 
23
+ ### Bun
17
24
  ```bash
18
25
  bun add ok-canvas-export
19
26
  ```
20
27
 
28
+ ### npm
29
+ ```bash
30
+ npm install ok-canvas-export
31
+ ```
32
+
33
+ ### pnpm
34
+ ```bash
35
+ pnpm add ok-canvas-export
36
+ ```
37
+
38
+ ### Yarn
39
+ ```bash
40
+ yarn add ok-canvas-export
41
+ ```
42
+
21
43
  ## Quick Start
22
44
 
23
- ```typescript
24
- import { useDiagramExport } from 'ok-canvas-export';
45
+ ```tsx
25
46
  import { useRef } from 'react';
47
+ import { useDiagramExport } from 'ok-canvas-export';
26
48
 
27
49
  const MyDiagram = () => {
28
50
  const svgRef = useRef<SVGSVGElement>(null);
29
-
51
+
30
52
  const {
31
53
  exportDiagram,
32
54
  isExporting,
@@ -41,7 +63,7 @@ const MyDiagram = () => {
41
63
  const handleExport = async () => {
42
64
  await exportDiagram({
43
65
  format: 'png',
44
- background: 'white',
66
+ background: '#ffffff',
45
67
  scale: 2,
46
68
  margin: 100,
47
69
  onProgress: (status) => {
@@ -55,7 +77,11 @@ const MyDiagram = () => {
55
77
  <svg ref={svgRef}>
56
78
  {/* Your diagram content */}
57
79
  </svg>
58
- <button onClick={handleExport} disabled={isExporting}>
80
+
81
+ <button
82
+ onClick={handleExport}
83
+ disabled={isExporting}
84
+ >
59
85
  Export Diagram
60
86
  </button>
61
87
  </>
@@ -63,51 +89,208 @@ const MyDiagram = () => {
63
89
  };
64
90
  ```
65
91
 
92
+ ## Background Colors
93
+
94
+ The background option supports:
95
+
96
+ - `white`
97
+ - `transparent`
98
+ - Any valid CSS color string
99
+
100
+ ### Examples
101
+ ```ts
102
+ background: '#C300FF'
103
+ background: '#1a1a1a'
104
+ background: 'rgb(30, 30, 30)'
105
+ background: 'hsl(260, 80%, 50%)'
106
+ background: 'rgba(0, 0, 0, 0.8)'
107
+ background: 'transparent'
108
+ ```
109
+
66
110
  ## API
67
111
 
68
112
  ### `useDiagramExport`
69
113
 
70
- Hook that provides diagram export functionality.
114
+ React hook that provides complete diagram export functionality.
115
+
116
+ #### Parameters
71
117
 
72
- **Parameters:**
73
- - `svgRef`: React ref to the SVG element
74
- - `diagramData`: Object containing tables and relationships
75
- - `getTableHeight`: Function that returns table height based on fields
76
- - `projectName`: Optional project name for filename
118
+ | Parameter | Type | Required | Description |
119
+ |-----------|------|----------|-------------|
120
+ | `svgRef` | `React.RefObject<SVGSVGElement>` | Yes | Reference to the SVG element that should be exported |
121
+ | `diagramData` | `DiagramData` | No | Optional diagram data used by the compatibility APIs |
122
+ | `getTableHeight` | `GetTableHeight` | No | Function that returns the height of a table |
123
+ | `projectName` | `string` | No | Project name used when generating the exported filename |
77
124
 
78
- **Returns:**
79
- - `isExporting`: Boolean indicating if export is in progress
80
- - `exportDiagram`: Function to trigger export
81
- - `exportAllTables`: Boolean flag for full render mode
82
- - `setExportAllTables`: Function to toggle full render mode
125
+ #### Returns
126
+
127
+ | Property | Type | Description |
128
+ |----------|------|-------------|
129
+ | `isExporting` | `boolean` | Boolean indicating whether an export is currently running |
130
+ | `exportDiagram` | `Function` | Function used to start an export |
131
+ | `exportAllTables` | `boolean` | Boolean indicating whether full rendering mode is enabled |
132
+ | `setExportAllTables` | `Function` | Function used to enable or disable full rendering mode |
83
133
 
84
134
  ### `exportDiagram`
85
135
 
86
- Core export function.
136
+ The core export function.
137
+
138
+ #### Options
139
+
140
+ | Option | Type | Default | Description |
141
+ |--------|------|---------|-------------|
142
+ | `format` | `'png' \| 'svg'` | `'png'` | Defines the output format |
143
+ | `background` | `string` | `'white'` | Defines the background color. Supports: `white`, `transparent`, or any valid CSS color |
144
+ | `scale` | `number` | `2` | Controls PNG rendering resolution |
145
+ | `margin` | `number` | `100` | Defines the space around the exported content |
146
+ | `fileName` | `string` | Auto-generated | Allows specifying a custom filename |
147
+ | `onProgress` | `Function` | - | Function called whenever the export process changes state |
148
+
149
+ ## PNG Export
87
150
 
88
- **Options:**
89
- - `format`: `'png'` | `'svg'` (default: `'png'`)
90
- - `background`: `'white'` | `'transparent'` (default: `'white'`)
91
- - `scale`: Number for quality scaling (default: `2`)
92
- - `margin`: Number for padding around content (default: `100`)
93
- - `fileName`: Custom filename override
94
- - `onProgress`: Callback for export status updates
151
+ PNG files are rendered through a canvas.
152
+
153
+ ### Example
154
+ ```ts
155
+ await exportDiagram({
156
+ format: 'png',
157
+ background: '#ffffff',
158
+ scale: 3,
159
+ });
160
+ ```
161
+
162
+ Higher scale values generally produce larger and higher-resolution PNG files.
163
+
164
+ ## SVG Export
165
+
166
+ SVG exports preserve the SVG structure and use the calculated graphical bounds.
167
+
168
+ ### Example
169
+ ```ts
170
+ await exportDiagram({
171
+ format: 'svg',
172
+ background: 'transparent',
173
+ });
174
+ ```
95
175
 
96
176
  ## How It Works
97
177
 
98
- 1. Activates full render mode to bypass viewport culling
99
- 2. Prepares clean SVG clone with proper dimensions
100
- 3. Serializes SVG with CSS variables resolved
101
- 4. Renders to high-quality canvas with proper scaling
102
- 5. Downloads as PNG or SVG file
178
+ The export process is designed to work with diagrams that use viewport culling or virtualization.
103
179
 
104
- ## Configuration
180
+ 1. Enables full render mode
181
+ 2. Waits for the complete diagram to render
182
+ 3. Calculates the real graphical bounds of the SVG
183
+ 4. Creates an independent SVG clone
184
+ 5. Removes artificial canvas backgrounds
185
+ 6. Applies the requested background
186
+ 7. Creates a viewBox covering the complete diagram
187
+ 8. Resolves CSS custom properties
188
+ 9. Serializes the SVG
189
+ 10. Converts the SVG to a high-quality PNG when requested
190
+ 11. Downloads the generated file
191
+ 12. Restores the original rendering state
105
192
 
106
- The library expects tables to have:
107
- - `id`: `string`
108
- - `x`: `number`
109
- - `y`: `number`
110
- - `fields`: `array`
193
+ The exporter does not depend on the visible SVG viewport or scroll container to determine the diagram size.
194
+
195
+ ## Viewport Culling
196
+
197
+ The library is especially useful for diagram editors that only render elements currently visible in the viewport.
198
+
199
+ ### Example
200
+ ```tsx
201
+ {tables.map((table) => {
202
+ if (!exportAllTables && !isVisible(table)) {
203
+ return null;
204
+ }
205
+
206
+ return <Table key={table.id} {...table} />;
207
+ })}
208
+ ```
209
+
210
+ During export, `exportAllTables` can be used to render the complete diagram before the SVG bounds are calculated.
211
+
212
+ ## TypeScript
213
+
214
+ The package includes TypeScript declarations.
215
+
216
+ ### Example
217
+ ```ts
218
+ import {
219
+ useDiagramExport,
220
+ exportDiagram,
221
+ calculateSvgDimensions,
222
+ } from 'ok-canvas-export';
223
+ ```
224
+
225
+ ## Low-Level API
226
+
227
+ The package also exposes lower-level utilities.
228
+
229
+ | Function | Description |
230
+ |----------|-------------|
231
+ | `calculateDimensions` | Calculates dimensions from diagram data |
232
+ | `calculateSvgDimensions` | Calculates the real graphical bounds of an SVG |
233
+ | `getSvgContentBounds` | Returns the graphical bounds of an SVG |
234
+ | `getScrollDimensions` | Returns dimensions based on the SVG and its scroll container |
235
+ | `renderHighQuality` | Renders an SVG image into a high-resolution canvas |
236
+ | `serializeSvg` | Serializes an SVG element into SVG markup while resolving CSS custom properties |
237
+ | `prepareSvgForExport` | Creates a clean SVG clone configured for export |
238
+ | `canvasToBlob` | Converts a canvas into a Blob |
239
+ | `downloadBlob` | Downloads a Blob using a generated or custom filename |
240
+ | `generateFileName` | Generates a filename for exported files |
241
+
242
+ ## Diagram Data
243
+
244
+ The original diagram-data compatibility API expects tables with the following structure:
245
+
246
+ ```ts
247
+ interface DiagramData {
248
+ tables: Array<{
249
+ id: string;
250
+ x: number;
251
+ y: number;
252
+ fields: any[];
253
+ }>;
254
+ relationships: any[];
255
+ }
256
+ ```
257
+
258
+ The `diagramData` and `getTableHeight` parameters are optional when using the exporter as a generic SVG exporter.
259
+
260
+ ## Browser Support
261
+
262
+ The library relies on browser APIs including:
263
+
264
+ - SVG
265
+ - `SVGElement.getBBox()`
266
+ - Canvas
267
+ - Blob
268
+ - `URL.createObjectURL()`
269
+ - Image
270
+
271
+ The library is intended to run in browser environments.
272
+
273
+ ## Framework Compatibility
274
+
275
+ The package is designed to work with standard React applications and common React build environments.
276
+
277
+ Supported environments include:
278
+
279
+ - Vite
280
+ - Next.js
281
+ - Create React App
282
+ - react-scripts
283
+ - Bun
284
+ - npm
285
+ - pnpm
286
+ - Yarn
287
+
288
+ The package provides both ESM and CommonJS builds to support modern bundlers as well as environments such as Create React App and react-scripts.
289
+
290
+ ## Requirements
291
+
292
+ - React >= 18
293
+ - Browser environment for performing exports
111
294
 
112
295
  ## License
113
296
 
@@ -115,4 +298,6 @@ MIT
115
298
 
116
299
  ## Contributing
117
300
 
118
- Contributions are welcome! Please feel free to submit a Pull Request.
301
+ Contributions, bug reports and pull requests are welcome.
302
+
303
+ If you find a problem or have an idea for improving the exporter, feel free to open an issue or submit a Pull Request.
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var T=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var W=Object.prototype.hasOwnProperty;var X=(r,o)=>{for(var e in o)T(r,e,{get:o[e],enumerable:!0})},Q=(r,o,e,t)=>{if(o&&typeof o=="object"||typeof o=="function")for(let i of j(o))!W.call(r,i)&&i!==e&&T(r,i,{get:()=>o[i],enumerable:!(t=z(o,i))||t.enumerable});return r};var q=r=>Q(T({},"__esModule",{value:!0}),r);var Y={};X(Y,{calculateDimensions:()=>L,calculateSvgDimensions:()=>y,canvasToBlob:()=>A,createSvgUrl:()=>v,default:()=>B,downloadBlob:()=>h,exportDiagram:()=>G,generateFileName:()=>x,getScrollDimensions:()=>R,getSvgContentBounds:()=>M,prepareSvgForExport:()=>D,renderHighQuality:()=>S,serializeSvg:()=>E,useDiagramExport:()=>B});module.exports=q(Y);var f=require("react");var L=(r,o,e=100)=>{if(!r?.tables?.length)return{width:100+e*2,height:100+e*2,x:-e,y:-e};let t=Math.min(...r.tables.map(n=>n.x)),i=Math.min(...r.tables.map(n=>n.y)),s=Math.max(...r.tables.map(n=>n.x+280+40)),l=Math.max(...r.tables.map(n=>n.y+o(n.fields)+40));return{x:t-e,y:i-e,width:Math.max(100,s-t+e*2),height:Math.max(100,l-i+e*2)}},M=r=>{let o=Array.from(r.querySelectorAll("g, path, rect, circle, ellipse, line, polyline, polygon, text, use, image")),e=1/0,t=1/0,i=-1/0,s=-1/0,l=(n,m,a,c)=>{!Number.isFinite(n)||!Number.isFinite(m)||!Number.isFinite(a)||!Number.isFinite(c)||a<0||c<0||(e=Math.min(e,n),t=Math.min(t,m),i=Math.max(i,n+a),s=Math.max(s,m+c))};for(let n of o)if(!(n.matches('[data-export-bg="canvas"]')||n.closest('[data-export-bg="canvas"]')))try{let m=n.getBBox();l(m.x,m.y,m.width,m.height)}catch{}return!Number.isFinite(e)||!Number.isFinite(t)||!Number.isFinite(i)||!Number.isFinite(s)?null:{x:e,y:t,width:Math.max(1,i-e),height:Math.max(1,s-t)}},y=(r,o=100)=>{let e=M(r);if(e)return{x:e.x-o,y:e.y-o,width:e.width+o*2,height:e.height+o*2};let t=r.viewBox?.baseVal;if(t&&Number.isFinite(t.width)&&Number.isFinite(t.height)&&t.width>0&&t.height>0)return{x:t.x-o,y:t.y-o,width:t.width+o*2,height:t.height+o*2};let i=Number(r.getAttribute("width"))||r.clientWidth||1,s=Number(r.getAttribute("height"))||r.clientHeight||1;return{x:-o,y:-o,width:i+o*2,height:s+o*2}},R=r=>{let o=r.parentElement||r.closest(".overflow-auto")||r.closest(".overflow-scroll"),e=Number(r.getAttribute("width"))||r.clientWidth||1,t=Number(r.getAttribute("height"))||r.clientHeight||1;return o&&(e=Math.max(e,o.scrollWidth),t=Math.max(t,o.scrollHeight)),{x:0,y:0,width:e,height:t}};var S=async(r,o,e,t=2,i="transparent")=>{let l=Math.max(1,o),n=Math.max(1,e),m=Math.max(.1,t),a=Math.min(m,16384/Math.max(l,n,1)),c=document.createElement("canvas");c.width=Math.max(1,Math.floor(l*a)),c.height=Math.max(1,Math.floor(n*a));let p=c.getContext("2d",{alpha:i!=="white"});if(!p)throw new Error("Unable to create 2D canvas context");return p.imageSmoothingEnabled=!0,p.imageSmoothingQuality="high",i==="white"?(p.fillStyle="#ffffff",p.fillRect(0,0,c.width,c.height)):p.clearRect(0,0,c.width,c.height),p.drawImage(r,0,0,l*a,n*a),c};var E=r=>{let o=new XMLSerializer,e=typeof document<"u"?getComputedStyle(document.documentElement):null,t=o.serializeToString(r);return e&&(t=t.replace(/var\(--([^)]+)\)/g,(i,s)=>e.getPropertyValue(`--${s}`)?.trim()||`var(--${s})`)),t.includes('xmlns="http://www.w3.org/2000/svg"')||(t=t.replace("<svg",'<svg xmlns="http://www.w3.org/2000/svg"')),t},v=r=>URL.createObjectURL(new Blob([r],{type:"image/svg+xml;charset=utf-8"})),D=(r,o,e,t,i=0,s=0,l=0)=>{let n=r.cloneNode(!0);if(n.querySelectorAll('[data-export-bg="canvas"]').forEach(a=>{a.remove()}),n.setAttribute("width",String(o)),n.setAttribute("height",String(e)),n.setAttribute("viewBox",`${s} ${l} ${o} ${e}`),n.setAttribute("preserveAspectRatio","xMinYMin meet"),t==="white"){let a=document.createElementNS("http://www.w3.org/2000/svg","rect");a.setAttribute("x",String(s)),a.setAttribute("y",String(l)),a.setAttribute("width",String(o)),a.setAttribute("height",String(e)),a.setAttribute("fill","#ffffff"),n.insertBefore(a,n.firstChild)}if(i>0&&s===0&&l===0){let a=n.querySelector('g[transform*="scale"]');if(a){let c=a.getAttribute("transform")||"";a.setAttribute("transform",`translate(${i/2}, ${i/2}) ${c}`)}}return n};var h=(r,o)=>{let e=URL.createObjectURL(r),t=document.createElement("a");t.href=e,t.download=o;try{document.body?.appendChild(t),t.click(),document.body?.removeChild(t)}catch{t.click()}setTimeout(()=>{URL.revokeObjectURL(e)},100)},x=(r,o)=>`${(r||"diagram").replace(/\s+/g,"_").toLowerCase().replace(/[^a-z0-9_-]/g,"")}.${o}`,A=r=>new Promise((o,e)=>{try{if(typeof r.toBlob!="function"){e(new Error("Canvas.toBlob is not supported"));return}r.toBlob(t=>{if(!t){e(new Error("Failed to create PNG blob"));return}o(t)},"image/png")}catch(t){e(t)}});var G=async(r,o={})=>{let{svgRef:e,diagramData:t,getTableHeight:i,projectName:s,setExportAllTables:l,showToast:n}=r,{format:m="png",background:a="white",scale:c=2,margin:p=100,fileName:V,onProgress:u}=o;if(!e.current){u?.({type:"error",message:"SVG reference not available"});return}let b=null;try{u?.({type:"preparing"}),n?.("Preparing export...","info"),l?.(!0),await new Promise(g=>{setTimeout(g,300)}),u?.({type:"rendering"});let d=e.current,P=y(d,p),{x:I,y:U,width:F,height:H}=P,_=D(d,F,H,a,p,I,U);u?.({type:"serializing"});let C=E(_);if(m==="svg"){let g=new Blob([C],{type:"image/svg+xml;charset=utf-8"}),N=V||x(s,"svg");h(g,N),u?.({type:"complete"}),n?.("Export completed successfully","success");return}let w=new Image;b=v(C),await new Promise((g,N)=>{w.onload=()=>{g()},w.onerror=()=>{N(new Error("Failed to load SVG image"))},w.src=b});let O=await S(w,F,H,c,a);u?.({type:"downloading"});let $=await A(O),k=V||x(s,"png");h($,k),u?.({type:"complete"}),n?.("Export completed successfully","success")}catch(d){console.error("Export error:",d),u?.({type:"error",message:"Export failed"}),n?.("Failed to export file","error")}finally{l?.(!1),b&&URL.revokeObjectURL(b)}};var B=({svgRef:r,diagramData:o,getTableHeight:e,projectName:t})=>{let[i,s]=(0,f.useState)(!1),[l,n]=(0,f.useState)(!1),m=(0,f.useCallback)(async(a={})=>{s(!0);try{await G({svgRef:r,diagramData:o,getTableHeight:e,projectName:t,setExportAllTables:n},a)}finally{s(!1),n(!1)}},[r,o,e,t]);return{isExporting:i,exportDiagram:m,exportAllTables:l,setExportAllTables:n}};0&&(module.exports={calculateDimensions,calculateSvgDimensions,canvasToBlob,createSvgUrl,downloadBlob,exportDiagram,generateFileName,getScrollDimensions,getSvgContentBounds,prepareSvgForExport,renderHighQuality,serializeSvg,useDiagramExport});
1
+ var T=Object.defineProperty;var $=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var W=Object.prototype.hasOwnProperty;var X=(r,o)=>{for(var t in o)T(r,t,{get:o[t],enumerable:!0})},Q=(r,o,t,e)=>{if(o&&typeof o=="object"||typeof o=="function")for(let i of j(o))!W.call(r,i)&&i!==t&&T(r,i,{get:()=>o[i],enumerable:!(e=$(o,i))||e.enumerable});return r};var Y=r=>Q(T({},"__esModule",{value:!0}),r);var q={};X(q,{calculateDimensions:()=>L,calculateSvgDimensions:()=>w,canvasToBlob:()=>A,createSvgUrl:()=>v,default:()=>B,downloadBlob:()=>h,exportDiagram:()=>N,generateFileName:()=>x,getScrollDimensions:()=>R,getSvgContentBounds:()=>M,prepareSvgForExport:()=>D,renderHighQuality:()=>S,serializeSvg:()=>E,useDiagramExport:()=>B});module.exports=Y(q);var b=require("react");var L=(r,o,t=100)=>{if(!r?.tables?.length)return{width:100+t*2,height:100+t*2,x:-t,y:-t};let e=Math.min(...r.tables.map(n=>n.x)),i=Math.min(...r.tables.map(n=>n.y)),s=Math.max(...r.tables.map(n=>n.x+280+40)),l=Math.max(...r.tables.map(n=>n.y+o(n.fields)+40));return{x:e-t,y:i-t,width:Math.max(100,s-e+t*2),height:Math.max(100,l-i+t*2)}},M=r=>{let o=Array.from(r.querySelectorAll("g, path, rect, circle, ellipse, line, polyline, polygon, text, use, image")),t=1/0,e=1/0,i=-1/0,s=-1/0,l=(n,m,a,c)=>{!Number.isFinite(n)||!Number.isFinite(m)||!Number.isFinite(a)||!Number.isFinite(c)||a<0||c<0||(t=Math.min(t,n),e=Math.min(e,m),i=Math.max(i,n+a),s=Math.max(s,m+c))};for(let n of o)if(!(n.matches('[data-export-bg="canvas"]')||n.closest('[data-export-bg="canvas"]')))try{let m=n.getBBox();l(m.x,m.y,m.width,m.height)}catch{}return!Number.isFinite(t)||!Number.isFinite(e)||!Number.isFinite(i)||!Number.isFinite(s)?null:{x:t,y:e,width:Math.max(1,i-t),height:Math.max(1,s-e)}},w=(r,o=100)=>{let t=M(r);if(t)return{x:t.x-o,y:t.y-o,width:t.width+o*2,height:t.height+o*2};let e=r.viewBox?.baseVal;if(e&&Number.isFinite(e.width)&&Number.isFinite(e.height)&&e.width>0&&e.height>0)return{x:e.x-o,y:e.y-o,width:e.width+o*2,height:e.height+o*2};let i=Number(r.getAttribute("width"))||r.clientWidth||1,s=Number(r.getAttribute("height"))||r.clientHeight||1;return{x:-o,y:-o,width:i+o*2,height:s+o*2}},R=r=>{let o=r.parentElement||r.closest(".overflow-auto")||r.closest(".overflow-scroll"),t=Number(r.getAttribute("width"))||r.clientWidth||1,e=Number(r.getAttribute("height"))||r.clientHeight||1;return o&&(t=Math.max(t,o.scrollWidth),e=Math.max(e,o.scrollHeight)),{x:0,y:0,width:t,height:e}};var S=async(r,o,t,e=2,i="transparent")=>{let l=Math.max(1,o),n=Math.max(1,t),m=Math.max(.1,e),a=Math.min(m,16384/Math.max(l,n,1)),c=document.createElement("canvas");c.width=Math.max(1,Math.floor(l*a)),c.height=Math.max(1,Math.floor(n*a));let p=c.getContext("2d",{alpha:!0});if(!p)throw new Error("Unable to create 2D canvas context");return p.imageSmoothingEnabled=!0,p.imageSmoothingQuality="high",i!=="transparent"?(p.fillStyle=i,p.fillRect(0,0,c.width,c.height)):p.clearRect(0,0,c.width,c.height),p.drawImage(r,0,0,l*a,n*a),c};var E=r=>{let o=new XMLSerializer,t=typeof document<"u"?getComputedStyle(document.documentElement):null,e=o.serializeToString(r);return t&&(e=e.replace(/var\(--([^)]+)\)/g,(i,s)=>t.getPropertyValue(`--${s}`)?.trim()||`var(--${s})`)),e.includes('xmlns="http://www.w3.org/2000/svg"')||(e=e.replace("<svg",'<svg xmlns="http://www.w3.org/2000/svg"')),e},v=r=>URL.createObjectURL(new Blob([r],{type:"image/svg+xml;charset=utf-8"})),D=(r,o,t,e="transparent",i=0,s=0,l=0)=>{let n=r.cloneNode(!0);if(n.querySelectorAll('[data-export-bg="canvas"]').forEach(a=>{a.remove()}),n.setAttribute("width",String(o)),n.setAttribute("height",String(t)),n.setAttribute("viewBox",`${s} ${l} ${o} ${t}`),n.setAttribute("preserveAspectRatio","xMinYMin meet"),e!=="transparent"){let a=document.createElementNS("http://www.w3.org/2000/svg","rect");a.setAttribute("x",String(s)),a.setAttribute("y",String(l)),a.setAttribute("width",String(o)),a.setAttribute("height",String(t)),a.setAttribute("fill",e),n.insertBefore(a,n.firstChild)}return n};var h=(r,o)=>{let t=URL.createObjectURL(r),e=document.createElement("a");e.href=t,e.download=o;try{document.body?.appendChild(e),e.click(),document.body?.removeChild(e)}catch{e.click()}setTimeout(()=>{URL.revokeObjectURL(t)},100)},x=(r,o)=>`${(r||"diagram").replace(/\s+/g,"_").toLowerCase().replace(/[^a-z0-9_-]/g,"")}.${o}`,A=r=>new Promise((o,t)=>{try{if(typeof r.toBlob!="function"){t(new Error("Canvas.toBlob is not supported"));return}r.toBlob(e=>{if(!e){t(new Error("Failed to create PNG blob"));return}o(e)},"image/png")}catch(e){t(e)}});var N=async(r,o={})=>{let{svgRef:t,diagramData:e,getTableHeight:i,projectName:s,setExportAllTables:l,showToast:n}=r,{format:m="png",background:a="white",scale:c=2,margin:p=100,fileName:V,onProgress:u}=o;if(!t.current){u?.({type:"error",message:"SVG reference not available"});return}let f=null;try{u?.({type:"preparing"}),n?.("Preparing export...","info"),l?.(!0),await new Promise(g=>{setTimeout(g,300)}),u?.({type:"rendering"});let d=t.current,P=w(d,p),{x:I,y:U,width:F,height:H}=P,_=D(d,F,H,a,p,I,U);u?.({type:"serializing"});let C=E(_);if(m==="svg"){let g=new Blob([C],{type:"image/svg+xml;charset=utf-8"}),G=V||x(s,"svg");h(g,G),u?.({type:"complete"}),n?.("Export completed successfully","success");return}let y=new Image;f=v(C),await new Promise((g,G)=>{y.onload=()=>{g()},y.onerror=()=>{G(new Error("Failed to load SVG image"))},y.src=f});let O=await S(y,F,H,c,a);u?.({type:"downloading"});let k=await A(O),z=V||x(s,"png");h(k,z),u?.({type:"complete"}),n?.("Export completed successfully","success")}catch(d){console.error("Export error:",d),u?.({type:"error",message:"Export failed"}),n?.("Failed to export file","error")}finally{l?.(!1),f&&URL.revokeObjectURL(f)}};var B=({svgRef:r,diagramData:o,getTableHeight:t,projectName:e})=>{let[i,s]=(0,b.useState)(!1),[l,n]=(0,b.useState)(!1),m=(0,b.useCallback)(async(a={})=>{s(!0);try{await N({svgRef:r,diagramData:o,getTableHeight:t,projectName:e,setExportAllTables:n},a)}finally{s(!1),n(!1)}},[r,o,t,e]);return{isExporting:i,exportDiagram:m,exportAllTables:l,setExportAllTables:n}};0&&(module.exports={calculateDimensions,calculateSvgDimensions,canvasToBlob,createSvgUrl,downloadBlob,exportDiagram,generateFileName,getScrollDimensions,getSvgContentBounds,prepareSvgForExport,renderHighQuality,serializeSvg,useDiagramExport});
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/useDiagramExport.ts","../src/calculateDimensions.ts","../src/renderHighQuality.ts","../src/utils/serializer.ts","../src/utils/download.ts","../src/exportDiagram.ts"],"sourcesContent":["export {\n useDiagramExport,\n} from './useDiagramExport';\n\nexport {\n exportDiagram,\n} from './exportDiagram';\n\nexport {\n calculateDimensions,\n calculateSvgDimensions,\n getSvgContentBounds,\n getScrollDimensions,\n} from './calculateDimensions';\n\nexport {\n renderHighQuality,\n} from './renderHighQuality';\n\nexport {\n serializeSvg,\n createSvgUrl,\n prepareSvgForExport,\n} from './utils/serializer';\n\nexport {\n downloadBlob,\n generateFileName,\n canvasToBlob,\n} from './utils/download';\n\nexport type {\n ExportOptions,\n ExportStatus,\n ExportContext,\n DiagramData,\n GetTableHeight,\n} from './types';\n\nexport {\n useDiagramExport as default,\n} from './useDiagramExport';","import {\n useState,\n useCallback,\n} from 'react';\n\nimport type {\n ExportOptions,\n ExportContext,\n DiagramData,\n GetTableHeight,\n} from './types';\n\nimport {\n exportDiagram,\n} from './exportDiagram';\n\nexport interface UseDiagramExportParams {\n svgRef: React.RefObject<SVGSVGElement>;\n\n diagramData?:\n | DiagramData\n | null;\n\n getTableHeight?:\n GetTableHeight;\n\n projectName?: string;\n}\n\n/**\n * Hook that provides SVG diagram export functionality.\n *\n * The exporter automatically calculates the real SVG content bounds,\n * so diagramData is optional for generic SVG usage.\n */\nexport const useDiagramExport = ({\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n}: UseDiagramExportParams): ExportContext & {\n exportAllTables: boolean;\n setExportAllTables: (\n value: boolean,\n ) => void;\n} => {\n const [\n isExporting,\n setIsExporting,\n ] = useState(false);\n\n const [\n exportAllTables,\n setExportAllTables,\n ] = useState(false);\n\n const handleExport =\n useCallback(\n async (\n options: ExportOptions = {},\n ) => {\n setIsExporting(true);\n\n try {\n await exportDiagram(\n {\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n setExportAllTables,\n },\n options,\n );\n } finally {\n setIsExporting(false);\n\n setExportAllTables(\n false,\n );\n }\n },\n [\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n ],\n );\n\n return {\n isExporting,\n exportDiagram:\n handleExport,\n exportAllTables,\n setExportAllTables,\n };\n};","import type { DiagramData, GetTableHeight } from './types';\n\nconst TABLE_WIDTH = 280;\nconst TABLE_PADDING = 40;\n\nexport interface Dimensions {\n width: number;\n height: number;\n x: number;\n y: number;\n}\n\n/**\n * Calculates dimensions based on diagram data.\n *\n * This function is kept compatible with the original API and tests.\n *\n * It calculates the total diagram size from the table coordinates,\n * assuming the table width is 280px plus the configured padding.\n */\nexport const calculateDimensions = (\n diagramData: DiagramData | undefined | null,\n getTableHeight: GetTableHeight,\n margin: number = 100,\n): Dimensions => {\n if (!diagramData?.tables?.length) {\n return {\n width: 100 + margin * 2,\n height: 100 + margin * 2,\n x: -margin,\n y: -margin,\n };\n }\n\n const minX = Math.min(\n ...diagramData.tables.map((table) => table.x),\n );\n\n const minY = Math.min(\n ...diagramData.tables.map((table) => table.y),\n );\n\n const maxX = Math.max(\n ...diagramData.tables.map(\n (table) => table.x + TABLE_WIDTH + TABLE_PADDING,\n ),\n );\n\n const maxY = Math.max(\n ...diagramData.tables.map(\n (table) =>\n table.y +\n getTableHeight(table.fields) +\n TABLE_PADDING,\n ),\n );\n\n /*\n * Keep the original expected behavior:\n *\n * width = maxX - minX\n * height = maxY - minY\n *\n * with margin added on both sides.\n */\n return {\n x: minX - margin,\n y: minY - margin,\n width: Math.max(\n 100,\n maxX - minX + margin * 2,\n ),\n height: Math.max(\n 100,\n maxY - minY + margin * 2,\n ),\n };\n};\n\n/**\n * Calculates the visible bounds of an SVG's graphical content.\n *\n * This function is intended for generic SVG exports.\n *\n * It does NOT rely on:\n *\n * - width\n * - height\n * - viewport\n * - scroll container\n *\n * Instead, it attempts to determine the real graphical bounds.\n */\nexport const getSvgContentBounds = (\n svg: SVGSVGElement,\n): Dimensions | null => {\n const elements = Array.from(\n svg.querySelectorAll<SVGGraphicsElement>(\n 'g, path, rect, circle, ellipse, line, polyline, polygon, text, use, image',\n ),\n );\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n const updateBounds = (\n x: number,\n y: number,\n width: number,\n height: number,\n ) => {\n if (\n !Number.isFinite(x) ||\n !Number.isFinite(y) ||\n !Number.isFinite(width) ||\n !Number.isFinite(height)\n ) {\n return;\n }\n\n if (width < 0 || height < 0) {\n return;\n }\n\n minX = Math.min(minX, x);\n minY = Math.min(minY, y);\n maxX = Math.max(maxX, x + width);\n maxY = Math.max(maxY, y + height);\n };\n\n for (const element of elements) {\n /*\n * Ignore the artificial canvas background.\n */\n if (\n element.matches('[data-export-bg=\"canvas\"]') ||\n element.closest('[data-export-bg=\"canvas\"]')\n ) {\n continue;\n }\n\n try {\n const bbox = element.getBBox();\n\n updateBounds(\n bbox.x,\n bbox.y,\n bbox.width,\n bbox.height,\n );\n } catch {\n /*\n * getBBox() can fail in environments where the SVG\n * has not been rendered yet.\n */\n }\n }\n\n if (\n !Number.isFinite(minX) ||\n !Number.isFinite(minY) ||\n !Number.isFinite(maxX) ||\n !Number.isFinite(maxY)\n ) {\n return null;\n }\n\n return {\n x: minX,\n y: minY,\n width: Math.max(1, maxX - minX),\n height: Math.max(1, maxY - minY),\n };\n};\n\n/**\n * Calculates the dimensions that should be used for a real SVG export.\n *\n * The content bounds are preferred.\n *\n * The returned x/y values are important because the content does not\n * necessarily start at 0,0.\n */\nexport const calculateSvgDimensions = (\n svg: SVGSVGElement,\n margin: number = 100,\n): Dimensions => {\n const bounds = getSvgContentBounds(svg);\n\n if (bounds) {\n return {\n x: bounds.x - margin,\n y: bounds.y - margin,\n width: bounds.width + margin * 2,\n height: bounds.height + margin * 2,\n };\n }\n\n /*\n * Fallback to the SVG viewBox.\n */\n const viewBox = svg.viewBox?.baseVal;\n\n if (\n viewBox &&\n Number.isFinite(viewBox.width) &&\n Number.isFinite(viewBox.height) &&\n viewBox.width > 0 &&\n viewBox.height > 0\n ) {\n return {\n x: viewBox.x - margin,\n y: viewBox.y - margin,\n width: viewBox.width + margin * 2,\n height: viewBox.height + margin * 2,\n };\n }\n\n /*\n * Final fallback to width/height attributes.\n */\n const width =\n Number(svg.getAttribute('width')) ||\n svg.clientWidth ||\n 1;\n\n const height =\n Number(svg.getAttribute('height')) ||\n svg.clientHeight ||\n 1;\n\n return {\n x: -margin,\n y: -margin,\n width: width + margin * 2,\n height: height + margin * 2,\n };\n};\n\n/**\n * Calculates dimensions based on an SVG element and its scroll container.\n *\n * Kept for backwards compatibility.\n */\nexport const getScrollDimensions = (\n svg: SVGSVGElement,\n): Dimensions => {\n const scrollContainer =\n svg.parentElement ||\n svg.closest('.overflow-auto') ||\n svg.closest('.overflow-scroll');\n\n let width =\n Number(svg.getAttribute('width')) ||\n svg.clientWidth ||\n 1;\n\n let height =\n Number(svg.getAttribute('height')) ||\n svg.clientHeight ||\n 1;\n\n if (scrollContainer) {\n width = Math.max(\n width,\n scrollContainer.scrollWidth,\n );\n\n height = Math.max(\n height,\n scrollContainer.scrollHeight,\n );\n }\n\n return {\n x: 0,\n y: 0,\n width,\n height,\n };\n};","/**\n * Renders an SVG image to a high-quality canvas.\n */\nexport const renderHighQuality = async (\n image: HTMLImageElement,\n width: number,\n height: number,\n scale: number = 2,\n background: 'white' | 'transparent' = 'transparent',\n): Promise<HTMLCanvasElement> => {\n const MAX_CANVAS_SIZE = 16384;\n\n const safeWidth =\n Math.max(1, width);\n\n const safeHeight =\n Math.max(1, height);\n\n const safeScale =\n Math.max(0.1, scale);\n\n const effectiveScale = Math.min(\n safeScale,\n MAX_CANVAS_SIZE /\n Math.max(\n safeWidth,\n safeHeight,\n 1,\n ),\n );\n\n const canvas =\n document.createElement(\n 'canvas',\n );\n\n canvas.width = Math.max(\n 1,\n Math.floor(\n safeWidth *\n effectiveScale,\n ),\n );\n\n canvas.height = Math.max(\n 1,\n Math.floor(\n safeHeight *\n effectiveScale,\n ),\n );\n\n const context =\n canvas.getContext(\n '2d',\n {\n alpha:\n background !== 'white',\n },\n );\n\n if (!context) {\n throw new Error(\n 'Unable to create 2D canvas context',\n );\n }\n\n context.imageSmoothingEnabled =\n true;\n\n context.imageSmoothingQuality =\n 'high';\n\n /*\n * Background.\n */\n if (\n background === 'white'\n ) {\n context.fillStyle =\n '#ffffff';\n\n context.fillRect(\n 0,\n 0,\n canvas.width,\n canvas.height,\n );\n } else {\n context.clearRect(\n 0,\n 0,\n canvas.width,\n canvas.height,\n );\n }\n\n /*\n * The canvas already contains the scaled dimensions,\n * so we draw using the original SVG dimensions.\n */\n context.drawImage(\n image,\n 0,\n 0,\n safeWidth *\n effectiveScale,\n safeHeight *\n effectiveScale,\n );\n\n return canvas;\n};","/**\n * Downloads are handled elsewhere.\n *\n * This file contains SVG serialization and preparation utilities.\n */\n\n/**\n * Serializes an SVG element to a string.\n *\n * CSS custom properties are resolved using the current document styles.\n */\nexport const serializeSvg = (\n svg: SVGSVGElement,\n): string => {\n const serializer =\n new XMLSerializer();\n\n const styles =\n typeof document !== 'undefined'\n ? getComputedStyle(\n document.documentElement,\n )\n : null;\n\n let markup =\n serializer.serializeToString(\n svg,\n );\n\n /*\n * Resolve CSS custom properties.\n */\n if (styles) {\n markup = markup.replace(\n /var\\(--([^)]+)\\)/g,\n (\n _match,\n name: string,\n ) => {\n const value =\n styles\n .getPropertyValue(\n `--${name}`,\n )\n ?.trim();\n\n return (\n value ||\n `var(--${name})`\n );\n },\n );\n }\n\n /*\n * Ensure SVG has the proper namespace.\n */\n if (\n !markup.includes(\n 'xmlns=\"http://www.w3.org/2000/svg\"',\n )\n ) {\n markup = markup.replace(\n '<svg',\n '<svg xmlns=\"http://www.w3.org/2000/svg\"',\n );\n }\n\n return markup;\n};\n\n/**\n * Creates a Blob URL from SVG markup.\n */\nexport const createSvgUrl = (\n svgMarkup: string,\n): string => {\n return URL.createObjectURL(\n new Blob(\n [svgMarkup],\n {\n type:\n 'image/svg+xml;charset=utf-8',\n },\n ),\n );\n};\n\n/**\n * Creates a clean SVG clone for export.\n *\n * sourceX/sourceY represent the origin of the exported\n * coordinate system.\n */\nexport const prepareSvgForExport = (\n sourceSvg: SVGSVGElement,\n width: number,\n height: number,\n background: 'white' | 'transparent',\n margin: number = 0,\n sourceX: number = 0,\n sourceY: number = 0,\n): SVGSVGElement => {\n const clone =\n sourceSvg.cloneNode(\n true,\n ) as SVGSVGElement;\n\n /*\n * Remove viewport/background elements.\n */\n const bgNodes =\n clone.querySelectorAll(\n '[data-export-bg=\"canvas\"]',\n );\n\n bgNodes.forEach(\n (node) => {\n node.remove();\n },\n );\n\n /*\n * Set final dimensions.\n */\n clone.setAttribute(\n 'width',\n String(width),\n );\n\n clone.setAttribute(\n 'height',\n String(height),\n );\n\n /*\n * The viewBox is what allows us to export content\n * that exists outside the original viewport.\n */\n clone.setAttribute(\n 'viewBox',\n `${sourceX} ${sourceY} ${width} ${height}`,\n );\n\n /*\n * Prevent unexpected aspect-ratio behavior.\n */\n clone.setAttribute(\n 'preserveAspectRatio',\n 'xMinYMin meet',\n );\n\n /*\n * Add explicit background.\n */\n if (\n background === 'white'\n ) {\n const whiteRect =\n document.createElementNS(\n 'http://www.w3.org/2000/svg',\n 'rect',\n );\n\n whiteRect.setAttribute(\n 'x',\n String(sourceX),\n );\n\n whiteRect.setAttribute(\n 'y',\n String(sourceY),\n );\n\n whiteRect.setAttribute(\n 'width',\n String(width),\n );\n\n whiteRect.setAttribute(\n 'height',\n String(height),\n );\n\n whiteRect.setAttribute(\n 'fill',\n '#ffffff',\n );\n\n /*\n * Keep background behind the diagram.\n */\n clone.insertBefore(\n whiteRect,\n clone.firstChild,\n );\n }\n\n /*\n * Backwards compatibility:\n *\n * Older consumers/tests may call this function without\n * sourceX/sourceY and expect the old margin behavior.\n *\n * In the real export flow sourceX/sourceY are provided,\n * so we do NOT modify existing transforms.\n */\n if (\n margin > 0 &&\n sourceX === 0 &&\n sourceY === 0\n ) {\n const scaledGroup =\n clone.querySelector(\n 'g[transform*=\"scale\"]',\n );\n\n if (scaledGroup) {\n const existingTransform =\n scaledGroup.getAttribute(\n 'transform',\n ) || '';\n\n /*\n * Preserve the original scale/transform.\n *\n * We prepend translate instead of replacing\n * the transform entirely.\n */\n scaledGroup.setAttribute(\n 'transform',\n `translate(${margin / 2}, ${margin / 2}) ${existingTransform}`,\n );\n }\n }\n\n return clone;\n};","/**\n * Downloads a Blob as a file.\n */\nexport const downloadBlob = (\n blob: Blob,\n fileName: string,\n): void => {\n const url =\n URL.createObjectURL(\n blob,\n );\n\n const link =\n document.createElement(\n 'a',\n );\n\n link.href = url;\n link.download = fileName;\n\n try {\n document.body?.appendChild(\n link,\n );\n\n link.click();\n\n document.body?.removeChild(\n link,\n );\n } catch {\n link.click();\n }\n\n /*\n * Give the browser a chance to start the download before\n * revoking the object URL.\n */\n setTimeout(() => {\n URL.revokeObjectURL(\n url,\n );\n }, 100);\n};\n\n/**\n * Generates a safe filename.\n */\nexport const generateFileName = (\n projectName:\n | string\n | undefined,\n format:\n | 'png'\n | 'svg',\n): string => {\n const safeName =\n (\n projectName ||\n 'diagram'\n )\n .replace(\n /\\s+/g,\n '_',\n )\n .toLowerCase()\n .replace(\n /[^a-z0-9_-]/g,\n '',\n );\n\n return `${safeName}.${format}`;\n};\n\n/**\n * Converts a canvas to a PNG Blob.\n */\nexport const canvasToBlob = (\n canvas: HTMLCanvasElement,\n): Promise<Blob> => {\n return new Promise(\n (\n resolve,\n reject,\n ) => {\n try {\n if (\n typeof canvas.toBlob !==\n 'function'\n ) {\n reject(\n new Error(\n 'Canvas.toBlob is not supported',\n ),\n );\n\n return;\n }\n\n canvas.toBlob(\n (\n blob,\n ) => {\n if (!blob) {\n reject(\n new Error(\n 'Failed to create PNG blob',\n ),\n );\n\n return;\n }\n\n resolve(blob);\n },\n 'image/png',\n );\n } catch (error) {\n reject(error);\n }\n },\n );\n};","import type {\n ExportOptions,\n DiagramData,\n GetTableHeight,\n} from './types';\n\nimport {\n calculateSvgDimensions,\n} from './calculateDimensions';\n\nimport {\n renderHighQuality,\n} from './renderHighQuality';\n\nimport {\n serializeSvg,\n createSvgUrl,\n prepareSvgForExport,\n} from './utils/serializer';\n\nimport {\n downloadBlob,\n generateFileName,\n canvasToBlob,\n} from './utils/download';\n\nexport interface ExportDiagramParams {\n svgRef: React.RefObject<SVGSVGElement>;\n diagramData: DiagramData;\n getTableHeight: GetTableHeight;\n projectName?: string;\n setExportAllTables?: (value: boolean) => void;\n showToast?: (\n message: string,\n type?: string,\n ) => void;\n}\n\n/**\n * Exports the complete SVG diagram regardless of the\n * visible viewport.\n *\n * The export process is:\n *\n * 1. Render all diagram content.\n * 2. Calculate the real SVG content bounds.\n * 3. Create an independent SVG clone.\n * 4. Set a viewBox covering the complete content.\n * 5. Convert the SVG to PNG when requested.\n */\nexport const exportDiagram = async (\n params: ExportDiagramParams,\n options: ExportOptions = {},\n): Promise<void> => {\n const {\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n setExportAllTables,\n showToast,\n } = params;\n\n const {\n format = 'png',\n background = 'white',\n scale = 2,\n margin = 100,\n fileName,\n onProgress,\n } = options;\n\n if (!svgRef.current) {\n onProgress?.({\n type: 'error',\n message: 'SVG reference not available',\n });\n\n return;\n }\n\n let objectUrl: string | null = null;\n\n try {\n onProgress?.({\n type: 'preparing',\n });\n\n showToast?.(\n 'Preparing export...',\n 'info',\n );\n\n /*\n * Tell the consuming application to render all\n * currently virtualized/cullable content.\n */\n setExportAllTables?.(true);\n\n /*\n * Give React time to render the complete diagram.\n */\n await new Promise<void>((resolve) => {\n setTimeout(resolve, 300);\n });\n\n onProgress?.({\n type: 'rendering',\n });\n\n const sourceSvg = svgRef.current;\n\n /*\n * IMPORTANT:\n *\n * Do NOT use calculateDimensions() here.\n *\n * calculateDimensions() exists for diagram-data based\n * compatibility.\n *\n * For a generic SVG export we need the real SVG bounds.\n */\n const dimensions = calculateSvgDimensions(\n sourceSvg,\n margin,\n );\n\n const {\n x,\n y,\n width,\n height,\n } = dimensions;\n\n /*\n * Prepare a completely independent SVG clone.\n */\n const exportSvg = prepareSvgForExport(\n sourceSvg,\n width,\n height,\n background,\n margin,\n x,\n y,\n );\n\n onProgress?.({\n type: 'serializing',\n });\n\n /*\n * Serialize the prepared SVG.\n */\n const svgMarkup = serializeSvg(\n exportSvg,\n );\n\n /*\n * SVG export does not require canvas.\n */\n if (format === 'svg') {\n const blob = new Blob(\n [svgMarkup],\n {\n type:\n 'image/svg+xml;charset=utf-8',\n },\n );\n\n const finalFileName =\n fileName ||\n generateFileName(\n projectName,\n 'svg',\n );\n\n downloadBlob(\n blob,\n finalFileName,\n );\n\n onProgress?.({\n type: 'complete',\n });\n\n showToast?.(\n 'Export completed successfully',\n 'success',\n );\n\n return;\n }\n\n /*\n * Create an object URL for the SVG.\n */\n const image = new Image();\n\n objectUrl = createSvgUrl(\n svgMarkup,\n );\n\n await new Promise<void>(\n (resolve, reject) => {\n image.onload = () => {\n resolve();\n };\n\n image.onerror = () => {\n reject(\n new Error(\n 'Failed to load SVG image',\n ),\n );\n };\n\n image.src = objectUrl!;\n },\n );\n\n /*\n * Render the complete SVG into a high-quality canvas.\n */\n const canvas =\n await renderHighQuality(\n image,\n width,\n height,\n scale,\n background,\n );\n\n onProgress?.({\n type: 'downloading',\n });\n\n /*\n * Convert the canvas to PNG.\n */\n const blob =\n await canvasToBlob(\n canvas,\n );\n\n const finalFileName =\n fileName ||\n generateFileName(\n projectName,\n 'png',\n );\n\n downloadBlob(\n blob,\n finalFileName,\n );\n\n onProgress?.({\n type: 'complete',\n });\n\n showToast?.(\n 'Export completed successfully',\n 'success',\n );\n } catch (error) {\n console.error(\n 'Export error:',\n error,\n );\n\n onProgress?.({\n type: 'error',\n message: 'Export failed',\n });\n\n showToast?.(\n 'Failed to export file',\n 'error',\n );\n } finally {\n /*\n * Always restore the normal rendering state.\n */\n setExportAllTables?.(\n false,\n );\n\n /*\n * Always release the object URL.\n */\n if (objectUrl) {\n URL.revokeObjectURL(\n objectUrl,\n );\n }\n }\n};"],"mappings":"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","names":["index_exports","__export","calculateDimensions","calculateSvgDimensions","canvasToBlob","createSvgUrl","useDiagramExport","downloadBlob","exportDiagram","generateFileName","getScrollDimensions","getSvgContentBounds","prepareSvgForExport","renderHighQuality","serializeSvg","__toCommonJS","import_react","calculateDimensions","diagramData","getTableHeight","margin","minX","table","minY","maxX","maxY","getSvgContentBounds","svg","elements","updateBounds","x","y","width","height","element","bbox","calculateSvgDimensions","bounds","viewBox","getScrollDimensions","scrollContainer","renderHighQuality","image","width","height","scale","background","safeWidth","safeHeight","safeScale","effectiveScale","canvas","context","serializeSvg","svg","serializer","styles","markup","_match","name","createSvgUrl","svgMarkup","prepareSvgForExport","sourceSvg","width","height","background","margin","sourceX","sourceY","clone","node","whiteRect","scaledGroup","existingTransform","downloadBlob","blob","fileName","url","link","generateFileName","projectName","format","canvasToBlob","canvas","resolve","reject","error","exportDiagram","params","options","svgRef","diagramData","getTableHeight","projectName","setExportAllTables","showToast","format","background","scale","margin","fileName","onProgress","objectUrl","resolve","sourceSvg","dimensions","calculateSvgDimensions","x","y","width","height","exportSvg","prepareSvgForExport","svgMarkup","serializeSvg","blob","finalFileName","generateFileName","downloadBlob","image","createSvgUrl","reject","canvas","renderHighQuality","canvasToBlob","error","useDiagramExport","svgRef","diagramData","getTableHeight","projectName","isExporting","setIsExporting","exportAllTables","setExportAllTables","handleExport","options","exportDiagram"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/useDiagramExport.ts","../src/calculateDimensions.ts","../src/renderHighQuality.ts","../src/utils/serializer.ts","../src/utils/download.ts","../src/exportDiagram.ts"],"sourcesContent":["export {\n useDiagramExport,\n} from './useDiagramExport';\n\nexport {\n exportDiagram,\n} from './exportDiagram';\n\nexport {\n calculateDimensions,\n calculateSvgDimensions,\n getSvgContentBounds,\n getScrollDimensions,\n} from './calculateDimensions';\n\nexport {\n renderHighQuality,\n} from './renderHighQuality';\n\nexport {\n serializeSvg,\n createSvgUrl,\n prepareSvgForExport,\n} from './utils/serializer';\n\nexport {\n downloadBlob,\n generateFileName,\n canvasToBlob,\n} from './utils/download';\n\nexport type {\n ExportOptions,\n ExportStatus,\n ExportContext,\n DiagramData,\n GetTableHeight,\n} from './types';\n\nexport {\n useDiagramExport as default,\n} from './useDiagramExport';","import {\n useState,\n useCallback,\n} from 'react';\n\nimport type {\n ExportOptions,\n ExportContext,\n DiagramData,\n GetTableHeight,\n} from './types';\n\nimport {\n exportDiagram,\n} from './exportDiagram';\n\nexport interface UseDiagramExportParams {\n svgRef:\n React.RefObject<SVGSVGElement>;\n\n diagramData?:\n | DiagramData\n | null;\n\n getTableHeight?:\n GetTableHeight;\n\n projectName?: string;\n}\n\n/**\n * Hook that provides SVG diagram export functionality.\n *\n * The exporter automatically calculates the real SVG content bounds,\n * so diagramData is optional for generic SVG usage.\n */\nexport const useDiagramExport = ({\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n}: UseDiagramExportParams):\n ExportContext & {\n exportAllTables: boolean;\n\n setExportAllTables: (\n value: boolean,\n ) => void;\n } => {\n const [\n isExporting,\n setIsExporting,\n ] = useState(false);\n\n const [\n exportAllTables,\n setExportAllTables,\n ] = useState(false);\n\n const handleExport =\n useCallback(\n async (\n options: ExportOptions = {},\n ) => {\n setIsExporting(\n true,\n );\n\n try {\n await exportDiagram(\n {\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n setExportAllTables,\n },\n options,\n );\n } finally {\n setIsExporting(\n false,\n );\n\n setExportAllTables(\n false,\n );\n }\n },\n [\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n ],\n );\n\n return {\n isExporting,\n\n exportDiagram:\n handleExport,\n\n exportAllTables,\n\n setExportAllTables,\n };\n};","import type {\n DiagramData,\n GetTableHeight,\n} from './types';\n\nconst TABLE_WIDTH = 280;\nconst TABLE_PADDING = 40;\n\nexport interface Dimensions {\n width: number;\n height: number;\n x: number;\n y: number;\n}\n\n/**\n * Calculates dimensions based on diagram data.\n *\n * This function is kept compatible with the original API and tests.\n *\n * It calculates the total diagram size from the table coordinates,\n * assuming the table width is 280px plus the configured padding.\n */\nexport const calculateDimensions = (\n diagramData:\n | DiagramData\n | undefined\n | null,\n getTableHeight:\n GetTableHeight,\n margin: number = 100,\n): Dimensions => {\n if (\n !diagramData?.tables?.length\n ) {\n return {\n width:\n 100 +\n margin * 2,\n\n height:\n 100 +\n margin * 2,\n\n x:\n -margin,\n\n y:\n -margin,\n };\n }\n\n const minX =\n Math.min(\n ...diagramData.tables.map(\n (table) =>\n table.x,\n ),\n );\n\n const minY =\n Math.min(\n ...diagramData.tables.map(\n (table) =>\n table.y,\n ),\n );\n\n const maxX =\n Math.max(\n ...diagramData.tables.map(\n (table) =>\n table.x +\n TABLE_WIDTH +\n TABLE_PADDING,\n ),\n );\n\n const maxY =\n Math.max(\n ...diagramData.tables.map(\n (table) =>\n table.y +\n getTableHeight(\n table.fields,\n ) +\n TABLE_PADDING,\n ),\n );\n\n return {\n x:\n minX -\n margin,\n\n y:\n minY -\n margin,\n\n width:\n Math.max(\n 100,\n maxX -\n minX +\n margin * 2,\n ),\n\n height:\n Math.max(\n 100,\n maxY -\n minY +\n margin * 2,\n ),\n };\n};\n\n/**\n * Calculates the visible bounds of an SVG's graphical content.\n *\n * This function is intended for generic SVG exports.\n *\n * It does NOT rely on:\n *\n * - width\n * - height\n * - viewport\n * - scroll container\n *\n * Instead, it attempts to determine the real graphical bounds.\n */\nexport const getSvgContentBounds = (\n svg: SVGSVGElement,\n): Dimensions | null => {\n const elements =\n Array.from(\n svg.querySelectorAll<SVGGraphicsElement>(\n 'g, path, rect, circle, ellipse, line, polyline, polygon, text, use, image',\n ),\n );\n\n let minX =\n Infinity;\n\n let minY =\n Infinity;\n\n let maxX =\n -Infinity;\n\n let maxY =\n -Infinity;\n\n const updateBounds =\n (\n x: number,\n y: number,\n width: number,\n height: number,\n ) => {\n if (\n !Number.isFinite(\n x,\n ) ||\n !Number.isFinite(\n y,\n ) ||\n !Number.isFinite(\n width,\n ) ||\n !Number.isFinite(\n height,\n )\n ) {\n return;\n }\n\n if (\n width < 0 ||\n height < 0\n ) {\n return;\n }\n\n minX =\n Math.min(\n minX,\n x,\n );\n\n minY =\n Math.min(\n minY,\n y,\n );\n\n maxX =\n Math.max(\n maxX,\n x + width,\n );\n\n maxY =\n Math.max(\n maxY,\n y + height,\n );\n };\n\n for (\n const element of elements\n ) {\n /*\n * Ignore the artificial canvas background.\n */\n if (\n element.matches(\n '[data-export-bg=\"canvas\"]',\n ) ||\n element.closest(\n '[data-export-bg=\"canvas\"]',\n )\n ) {\n continue;\n }\n\n try {\n const bbox =\n element.getBBox();\n\n updateBounds(\n bbox.x,\n bbox.y,\n bbox.width,\n bbox.height,\n );\n } catch {\n /*\n * getBBox() can fail in environments where\n * the SVG has not been rendered yet.\n */\n }\n }\n\n if (\n !Number.isFinite(\n minX,\n ) ||\n !Number.isFinite(\n minY,\n ) ||\n !Number.isFinite(\n maxX,\n ) ||\n !Number.isFinite(\n maxY,\n )\n ) {\n return null;\n }\n\n return {\n x:\n minX,\n\n y:\n minY,\n\n width:\n Math.max(\n 1,\n maxX - minX,\n ),\n\n height:\n Math.max(\n 1,\n maxY - minY,\n ),\n };\n};\n\n/**\n * Calculates the dimensions that should be used for a real SVG export.\n *\n * The content bounds are preferred.\n *\n * The returned x/y values are important because the content does not\n * necessarily start at 0,0.\n */\nexport const calculateSvgDimensions = (\n svg: SVGSVGElement,\n margin: number = 100,\n): Dimensions => {\n const bounds =\n getSvgContentBounds(\n svg,\n );\n\n if (bounds) {\n return {\n x:\n bounds.x -\n margin,\n\n y:\n bounds.y -\n margin,\n\n width:\n bounds.width +\n margin * 2,\n\n height:\n bounds.height +\n margin * 2,\n };\n }\n\n /*\n * Fallback to the SVG viewBox.\n */\n const viewBox =\n svg.viewBox?.baseVal;\n\n if (\n viewBox &&\n Number.isFinite(\n viewBox.width,\n ) &&\n Number.isFinite(\n viewBox.height,\n ) &&\n viewBox.width > 0 &&\n viewBox.height > 0\n ) {\n return {\n x:\n viewBox.x -\n margin,\n\n y:\n viewBox.y -\n margin,\n\n width:\n viewBox.width +\n margin * 2,\n\n height:\n viewBox.height +\n margin * 2,\n };\n }\n\n /*\n * Final fallback to width/height attributes.\n */\n const width =\n Number(\n svg.getAttribute(\n 'width',\n ),\n ) ||\n svg.clientWidth ||\n 1;\n\n const height =\n Number(\n svg.getAttribute(\n 'height',\n ),\n ) ||\n svg.clientHeight ||\n 1;\n\n return {\n x:\n -margin,\n\n y:\n -margin,\n\n width:\n width +\n margin * 2,\n\n height:\n height +\n margin * 2,\n };\n};\n\n/**\n * Calculates dimensions based on an SVG element and its scroll container.\n *\n * Kept for backwards compatibility.\n */\nexport const getScrollDimensions = (\n svg: SVGSVGElement,\n): Dimensions => {\n const scrollContainer =\n svg.parentElement ||\n svg.closest(\n '.overflow-auto',\n ) ||\n svg.closest(\n '.overflow-scroll',\n );\n\n let width =\n Number(\n svg.getAttribute(\n 'width',\n ),\n ) ||\n svg.clientWidth ||\n 1;\n\n let height =\n Number(\n svg.getAttribute(\n 'height',\n ),\n ) ||\n svg.clientHeight ||\n 1;\n\n if (\n scrollContainer\n ) {\n width =\n Math.max(\n width,\n scrollContainer.scrollWidth,\n );\n\n height =\n Math.max(\n height,\n scrollContainer.scrollHeight,\n );\n }\n\n return {\n x: 0,\n y: 0,\n width,\n height,\n };\n};","/**\n * Renders an SVG image to a high-quality canvas.\n *\n * The background can be:\n *\n * - 'transparent'\n * - any valid CSS color\n *\n * Examples:\n *\n * - '#C300FF'\n * - '#ffffff'\n * - 'rgb(195, 0, 255)'\n * - 'rgba(195, 0, 255, 0.5)'\n * - 'hsl(285, 100%, 50%)'\n */\nexport const renderHighQuality = async (\n image: HTMLImageElement,\n width: number,\n height: number,\n scale: number = 2,\n background: string = 'transparent',\n): Promise<HTMLCanvasElement> => {\n const MAX_CANVAS_SIZE = 16384;\n\n const safeWidth =\n Math.max(1, width);\n\n const safeHeight =\n Math.max(1, height);\n\n const safeScale =\n Math.max(0.1, scale);\n\n const effectiveScale = Math.min(\n safeScale,\n MAX_CANVAS_SIZE /\n Math.max(\n safeWidth,\n safeHeight,\n 1,\n ),\n );\n\n const canvas =\n document.createElement(\n 'canvas',\n );\n\n canvas.width = Math.max(\n 1,\n Math.floor(\n safeWidth *\n effectiveScale,\n ),\n );\n\n canvas.height = Math.max(\n 1,\n Math.floor(\n safeHeight *\n effectiveScale,\n ),\n );\n\n /*\n * Always keep alpha enabled.\n *\n * This is important because the user may provide:\n *\n * rgba(...)\n *\n * or transparent.\n */\n const context =\n canvas.getContext(\n '2d',\n {\n alpha: true,\n },\n );\n\n if (!context) {\n throw new Error(\n 'Unable to create 2D canvas context',\n );\n }\n\n context.imageSmoothingEnabled =\n true;\n\n context.imageSmoothingQuality =\n 'high';\n\n /*\n * Background handling.\n *\n * transparent:\n * Keep the canvas transparent.\n *\n * any other value:\n * Use it as a CSS color.\n */\n if (\n background !== 'transparent'\n ) {\n context.fillStyle =\n background;\n\n context.fillRect(\n 0,\n 0,\n canvas.width,\n canvas.height,\n );\n } else {\n context.clearRect(\n 0,\n 0,\n canvas.width,\n canvas.height,\n );\n }\n\n /*\n * The canvas already contains the\n * final scaled dimensions.\n */\n context.drawImage(\n image,\n 0,\n 0,\n safeWidth *\n effectiveScale,\n safeHeight *\n effectiveScale,\n );\n\n return canvas;\n};","/**\n * Downloads are handled elsewhere.\n *\n * This file contains SVG serialization and preparation utilities.\n */\n\n/**\n * Serializes an SVG element to a string.\n *\n * CSS custom properties are resolved using the current document styles.\n */\nexport const serializeSvg = (\n svg: SVGSVGElement,\n): string => {\n const serializer =\n new XMLSerializer();\n\n const styles =\n typeof document !== 'undefined'\n ? getComputedStyle(\n document.documentElement,\n )\n : null;\n\n let markup =\n serializer.serializeToString(\n svg,\n );\n\n if (styles) {\n markup = markup.replace(\n /var\\(--([^)]+)\\)/g,\n (\n _match,\n name: string,\n ) => {\n const value =\n styles\n .getPropertyValue(\n `--${name}`,\n )\n ?.trim();\n\n return value ||\n `var(--${name})`;\n },\n );\n }\n\n /*\n * Ensure SVG has the proper namespace.\n */\n if (\n !markup.includes(\n 'xmlns=\"http://www.w3.org/2000/svg\"',\n )\n ) {\n markup = markup.replace(\n '<svg',\n '<svg xmlns=\"http://www.w3.org/2000/svg\"',\n );\n }\n\n return markup;\n};\n\n/**\n * Creates a Blob URL from SVG markup.\n */\nexport const createSvgUrl = (\n svgMarkup: string,\n): string => {\n return URL.createObjectURL(\n new Blob(\n [svgMarkup],\n {\n type:\n 'image/svg+xml;charset=utf-8',\n },\n ),\n );\n};\n\n/**\n * Creates a clean SVG clone for export.\n *\n * The clone receives an explicit viewBox based on the REAL\n * content bounds instead of assuming the content starts at 0,0.\n *\n * The background can be:\n *\n * - 'transparent'\n * - any valid CSS color\n */\nexport const prepareSvgForExport = (\n sourceSvg: SVGSVGElement,\n width: number,\n height: number,\n background: string = 'transparent',\n margin: number = 0,\n sourceX: number = 0,\n sourceY: number = 0,\n): SVGSVGElement => {\n const clone =\n sourceSvg.cloneNode(\n true,\n ) as SVGSVGElement;\n\n /*\n * Remove elements explicitly marked as canvas backgrounds.\n *\n * These backgrounds are usually viewport-sized and would otherwise\n * make getBBox() consider the entire viewport instead of the\n * actual diagram.\n */\n const bgNodes =\n clone.querySelectorAll(\n '[data-export-bg=\"canvas\"]',\n );\n\n bgNodes.forEach(\n (node) => {\n node.remove();\n },\n );\n\n /*\n * The dimensions passed here already include the requested margin.\n */\n clone.setAttribute(\n 'width',\n String(width),\n );\n\n clone.setAttribute(\n 'height',\n String(height),\n );\n\n clone.setAttribute(\n 'viewBox',\n `${sourceX} ${sourceY} ${width} ${height}`,\n );\n\n /*\n * Prevent unexpected cropping or distortion.\n */\n clone.setAttribute(\n 'preserveAspectRatio',\n 'xMinYMin meet',\n );\n\n /*\n * Add an explicit background when requested.\n *\n * transparent:\n * No background is added.\n *\n * Any other CSS color:\n * The color is used as the SVG background.\n */\n if (\n background !== 'transparent'\n ) {\n const backgroundRect =\n document.createElementNS(\n 'http://www.w3.org/2000/svg',\n 'rect',\n );\n\n backgroundRect.setAttribute(\n 'x',\n String(sourceX),\n );\n\n backgroundRect.setAttribute(\n 'y',\n String(sourceY),\n );\n\n backgroundRect.setAttribute(\n 'width',\n String(width),\n );\n\n backgroundRect.setAttribute(\n 'height',\n String(height),\n );\n\n backgroundRect.setAttribute(\n 'fill',\n background,\n );\n\n /*\n * Keep the background behind the diagram.\n */\n clone.insertBefore(\n backgroundRect,\n clone.firstChild,\n );\n }\n\n /*\n * We intentionally do NOT manipulate arbitrary\n * transforms here.\n *\n * The viewBox handles positioning and margin.\n */\n return clone;\n};","/**\n * Downloads a Blob as a file.\n */\nexport const downloadBlob = (\n blob: Blob,\n fileName: string,\n): void => {\n const url =\n URL.createObjectURL(\n blob,\n );\n\n const link =\n document.createElement(\n 'a',\n );\n\n link.href = url;\n link.download = fileName;\n\n try {\n document.body?.appendChild(\n link,\n );\n\n link.click();\n\n document.body?.removeChild(\n link,\n );\n } catch {\n link.click();\n }\n\n /*\n * Give the browser a chance to start the download before\n * revoking the object URL.\n */\n setTimeout(() => {\n URL.revokeObjectURL(\n url,\n );\n }, 100);\n};\n\n/**\n * Generates a safe filename.\n */\nexport const generateFileName = (\n projectName:\n | string\n | undefined,\n format:\n | 'png'\n | 'svg',\n): string => {\n const safeName =\n (\n projectName ||\n 'diagram'\n )\n .replace(\n /\\s+/g,\n '_',\n )\n .toLowerCase()\n .replace(\n /[^a-z0-9_-]/g,\n '',\n );\n\n return `${safeName}.${format}`;\n};\n\n/**\n * Converts a canvas to a PNG Blob.\n */\nexport const canvasToBlob = (\n canvas: HTMLCanvasElement,\n): Promise<Blob> => {\n return new Promise(\n (\n resolve,\n reject,\n ) => {\n try {\n if (\n typeof canvas.toBlob !==\n 'function'\n ) {\n reject(\n new Error(\n 'Canvas.toBlob is not supported',\n ),\n );\n\n return;\n }\n\n canvas.toBlob(\n (\n blob,\n ) => {\n if (!blob) {\n reject(\n new Error(\n 'Failed to create PNG blob',\n ),\n );\n\n return;\n }\n\n resolve(blob);\n },\n 'image/png',\n );\n } catch (error) {\n reject(error);\n }\n },\n );\n};","import type {\n ExportOptions,\n DiagramData,\n GetTableHeight,\n} from './types';\n\nimport {\n calculateSvgDimensions,\n} from './calculateDimensions';\n\nimport {\n renderHighQuality,\n} from './renderHighQuality';\n\nimport {\n serializeSvg,\n createSvgUrl,\n prepareSvgForExport,\n} from './utils/serializer';\n\nimport {\n downloadBlob,\n generateFileName,\n canvasToBlob,\n} from './utils/download';\n\nexport interface ExportDiagramParams {\n svgRef: React.RefObject<SVGSVGElement>;\n\n diagramData?:\n | DiagramData\n | null;\n\n getTableHeight?:\n | GetTableHeight;\n\n projectName?: string;\n\n setExportAllTables?: (\n value: boolean,\n ) => void;\n\n showToast?: (\n message: string,\n type?: string,\n ) => void;\n}\n\n/**\n * Exports the complete SVG diagram regardless of the\n * visible viewport.\n *\n * The export process is:\n *\n * 1. Render all diagram content.\n * 2. Calculate the real SVG content bounds.\n * 3. Create an independent SVG clone.\n * 4. Set a viewBox covering the complete content.\n * 5. Apply the requested background.\n * 6. Convert the SVG to PNG when requested.\n *\n * Background examples:\n *\n * - 'white'\n * - 'transparent'\n * - '#C300FF'\n * - 'rgb(195, 0, 255)'\n * - 'rgba(195, 0, 255, 0.5)'\n */\nexport const exportDiagram = async (\n params: ExportDiagramParams,\n options: ExportOptions = {},\n): Promise<void> => {\n const {\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n setExportAllTables,\n showToast,\n } = params;\n\n const {\n format = 'png',\n background = 'white',\n scale = 2,\n margin = 100,\n fileName,\n onProgress,\n } = options;\n\n if (!svgRef.current) {\n onProgress?.({\n type: 'error',\n message:\n 'SVG reference not available',\n });\n\n return;\n }\n\n let objectUrl:\n string | null = null;\n\n try {\n onProgress?.({\n type: 'preparing',\n });\n\n showToast?.(\n 'Preparing export...',\n 'info',\n );\n\n /*\n * Tell the consuming application to render\n * all currently virtualized/cullable content.\n */\n setExportAllTables?.(\n true,\n );\n\n /*\n * Give React time to render the complete diagram.\n */\n await new Promise<void>(\n (resolve) => {\n setTimeout(\n resolve,\n 300,\n );\n },\n );\n\n onProgress?.({\n type: 'rendering',\n });\n\n const sourceSvg =\n svgRef.current;\n\n /*\n * Calculate the real SVG bounds.\n *\n * We intentionally do not use calculateDimensions()\n * because SVG content may extend beyond the viewport.\n */\n const dimensions =\n calculateSvgDimensions(\n sourceSvg,\n margin,\n );\n\n const {\n x,\n y,\n width,\n height,\n } = dimensions;\n\n /*\n * Prepare a completely independent SVG clone.\n *\n * The background is applied here for SVG exports\n * and is also preserved when converting to PNG.\n */\n const exportSvg =\n prepareSvgForExport(\n sourceSvg,\n width,\n height,\n background,\n margin,\n x,\n y,\n );\n\n onProgress?.({\n type: 'serializing',\n });\n\n /*\n * Serialize the prepared SVG.\n */\n const svgMarkup =\n serializeSvg(\n exportSvg,\n );\n\n /*\n * SVG export does not require canvas.\n */\n if (\n format === 'svg'\n ) {\n const blob =\n new Blob(\n [svgMarkup],\n {\n type:\n 'image/svg+xml;charset=utf-8',\n },\n );\n\n const finalFileName =\n fileName ||\n generateFileName(\n projectName,\n 'svg',\n );\n\n downloadBlob(\n blob,\n finalFileName,\n );\n\n onProgress?.({\n type: 'complete',\n });\n\n showToast?.(\n 'Export completed successfully',\n 'success',\n );\n\n return;\n }\n\n /*\n * Create an object URL for the SVG.\n */\n const image =\n new Image();\n\n objectUrl =\n createSvgUrl(\n svgMarkup,\n );\n\n await new Promise<void>(\n (\n resolve,\n reject,\n ) => {\n image.onload =\n () => {\n resolve();\n };\n\n image.onerror =\n () => {\n reject(\n new Error(\n 'Failed to load SVG image',\n ),\n );\n };\n\n image.src =\n objectUrl!;\n },\n );\n\n /*\n * Render the complete SVG into a high-quality canvas.\n *\n * The same background is passed to the canvas renderer,\n * allowing custom CSS colors for PNG exports.\n */\n const canvas =\n await renderHighQuality(\n image,\n width,\n height,\n scale,\n background,\n );\n\n onProgress?.({\n type: 'downloading',\n });\n\n /*\n * Convert the canvas to PNG.\n */\n const blob =\n await canvasToBlob(\n canvas,\n );\n\n const finalFileName =\n fileName ||\n generateFileName(\n projectName,\n 'png',\n );\n\n downloadBlob(\n blob,\n finalFileName,\n );\n\n onProgress?.({\n type: 'complete',\n });\n\n showToast?.(\n 'Export completed successfully',\n 'success',\n );\n } catch (error) {\n console.error(\n 'Export error:',\n error,\n );\n\n onProgress?.({\n type: 'error',\n message:\n 'Export failed',\n });\n\n showToast?.(\n 'Failed to export file',\n 'error',\n );\n } finally {\n /*\n * Always restore the normal rendering state.\n */\n setExportAllTables?.(\n false,\n );\n\n /*\n * Always release the object URL.\n */\n if (objectUrl) {\n URL.revokeObjectURL(\n objectUrl,\n );\n }\n }\n};"],"mappings":"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CAAM,GAMjBC,EACXC,GAEO,IAAI,QACT,CACEC,EACAC,IACG,CACH,GAAI,CACF,GACE,OAAOF,EAAO,QACd,WACA,CACAE,EACE,IAAI,MACF,gCACF,CACF,EAEA,MACF,CAEAF,EAAO,OAEHR,GACG,CACH,GAAI,CAACA,EAAM,CACTU,EACE,IAAI,MACF,2BACF,CACF,EAEA,MACF,CAEAD,EAAQT,CAAI,CACd,EACA,WACF,CACF,OAASW,EAAO,CACdD,EAAOC,CAAK,CACd,CACF,CACF,ECpDK,IAAMC,EAAgB,MAC3BC,EACAC,EAAyB,CAAC,IACR,CAClB,GAAM,CACJ,OAAAC,EACA,YAAAC,EACA,eAAAC,EACA,YAAAC,EACA,mBAAAC,EACA,UAAAC,CACF,EAAIP,EAEE,CACJ,OAAAQ,EAAS,MACT,WAAAC,EAAa,QACb,MAAAC,EAAQ,EACR,OAAAC,EAAS,IACT,SAAAC,EACA,WAAAC,CACF,EAAIZ,EAEJ,GAAI,CAACC,EAAO,QAAS,CACnBW,IAAa,CACX,KAAM,QACN,QACE,6BACJ,CAAC,EAED,MACF,CAEA,IAAIC,EACc,KAElB,GAAI,CACFD,IAAa,CACX,KAAM,WACR,CAAC,EAEDN,IACE,sBACA,MACF,EAMAD,IACE,EACF,EAKA,MAAM,IAAI,QACPS,GAAY,CACX,WACEA,EACA,GACF,CACF,CACF,EAEAF,IAAa,CACX,KAAM,WACR,CAAC,EAED,IAAMG,EACJd,EAAO,QAQHe,EACJC,EACEF,EACAL,CACF,EAEI,CACJ,EAAAQ,EACA,EAAAC,EACA,MAAAC,EACA,OAAAC,CACF,EAAIL,EAQEM,EACJC,EACER,EACAK,EACAC,EACAb,EACAE,EACAQ,EACAC,CACF,EAEFP,IAAa,CACX,KAAM,aACR,CAAC,EAKD,IAAMY,EACJC,EACEH,CACF,EAKF,GACEf,IAAW,MACX,CACA,IAAMmB,EACJ,IAAI,KACF,CAACF,CAAS,EACV,CACE,KACE,6BACJ,CACF,EAEIG,EACJhB,GACAiB,EACExB,EACA,KACF,EAEFyB,EACEH,EACAC,CACF,EAEAf,IAAa,CACX,KAAM,UACR,CAAC,EAEDN,IACE,gCACA,SACF,EAEA,MACF,CAKA,IAAMwB,EACJ,IAAI,MAENjB,EACEkB,EACEP,CACF,EAEF,MAAM,IAAI,QACR,CACEV,EACAkB,IACG,CACHF,EAAM,OACJ,IAAM,CACJhB,EAAQ,CACV,EAEFgB,EAAM,QACJ,IAAM,CACJE,EACE,IAAI,MACF,0BACF,CACF,CACF,EAEFF,EAAM,IACJjB,CACJ,CACF,EAQA,IAAMoB,EACJ,MAAMC,EACJJ,EACAV,EACAC,EACAZ,EACAD,CACF,EAEFI,IAAa,CACX,KAAM,aACR,CAAC,EAKD,IAAMc,EACJ,MAAMS,EACJF,CACF,EAEIN,EACJhB,GACAiB,EACExB,EACA,KACF,EAEFyB,EACEH,EACAC,CACF,EAEAf,IAAa,CACX,KAAM,UACR,CAAC,EAEDN,IACE,gCACA,SACF,CACF,OAAS8B,EAAO,CACd,QAAQ,MACN,gBACAA,CACF,EAEAxB,IAAa,CACX,KAAM,QACN,QACE,eACJ,CAAC,EAEDN,IACE,wBACA,OACF,CACF,QAAE,CAIAD,IACE,EACF,EAKIQ,GACF,IAAI,gBACFA,CACF,CAEJ,CACF,ELnTO,IAAMwB,EAAmB,CAAC,CAC/B,OAAAC,EACA,YAAAC,EACA,eAAAC,EACA,YAAAC,CACF,IAOO,CACL,GAAM,CACJC,EACAC,CACF,KAAI,YAAS,EAAK,EAEZ,CACJC,EACAC,CACF,KAAI,YAAS,EAAK,EAEZC,KACJ,eACE,MACEC,EAAyB,CAAC,IACvB,CACHJ,EACE,EACF,EAEA,GAAI,CACF,MAAMK,EACJ,CACE,OAAAV,EACA,YAAAC,EACA,eAAAC,EACA,YAAAC,EACA,mBAAAI,CACF,EACAE,CACF,CACF,QAAE,CACAJ,EACE,EACF,EAEAE,EACE,EACF,CACF,CACF,EACA,CACEP,EACAC,EACAC,EACAC,CACF,CACF,EAEF,MAAO,CACL,YAAAC,EAEA,cACEI,EAEF,gBAAAF,EAEA,mBAAAC,CACF,CACF","names":["index_exports","__export","calculateDimensions","calculateSvgDimensions","canvasToBlob","createSvgUrl","useDiagramExport","downloadBlob","exportDiagram","generateFileName","getScrollDimensions","getSvgContentBounds","prepareSvgForExport","renderHighQuality","serializeSvg","__toCommonJS","import_react","calculateDimensions","diagramData","getTableHeight","margin","minX","table","minY","maxX","maxY","getSvgContentBounds","svg","elements","updateBounds","x","y","width","height","element","bbox","calculateSvgDimensions","bounds","viewBox","getScrollDimensions","scrollContainer","renderHighQuality","image","width","height","scale","background","safeWidth","safeHeight","safeScale","effectiveScale","canvas","context","serializeSvg","svg","serializer","styles","markup","_match","name","createSvgUrl","svgMarkup","prepareSvgForExport","sourceSvg","width","height","background","margin","sourceX","sourceY","clone","node","backgroundRect","downloadBlob","blob","fileName","url","link","generateFileName","projectName","format","canvasToBlob","canvas","resolve","reject","error","exportDiagram","params","options","svgRef","diagramData","getTableHeight","projectName","setExportAllTables","showToast","format","background","scale","margin","fileName","onProgress","objectUrl","resolve","sourceSvg","dimensions","calculateSvgDimensions","x","y","width","height","exportSvg","prepareSvgForExport","svgMarkup","serializeSvg","blob","finalFileName","generateFileName","downloadBlob","image","createSvgUrl","reject","canvas","renderHighQuality","canvasToBlob","error","useDiagramExport","svgRef","diagramData","getTableHeight","projectName","isExporting","setIsExporting","exportAllTables","setExportAllTables","handleExport","options","exportDiagram"]}
package/dist/index.d.cts CHANGED
@@ -1,9 +1,40 @@
1
+ type ExportBackground = string;
1
2
  interface ExportOptions {
2
3
  format?: 'png' | 'svg';
3
- background?: 'white' | 'transparent';
4
+ /**
5
+ * Background color for the exported diagram.
6
+ *
7
+ * Supported examples:
8
+ *
9
+ * - 'white'
10
+ * - 'transparent'
11
+ * - '#C300FF'
12
+ * - '#ffffff'
13
+ * - 'rgb(195, 0, 255)'
14
+ * - 'rgba(195, 0, 255, 0.5)'
15
+ * - 'hsl(285, 100%, 50%)'
16
+ *
17
+ * Any valid CSS color supported by the browser can be used.
18
+ */
19
+ background?: ExportBackground;
20
+ /**
21
+ * Export scale.
22
+ *
23
+ * Example:
24
+ * scale: 2
25
+ */
4
26
  scale?: number;
27
+ /**
28
+ * Additional space around the exported diagram.
29
+ */
5
30
  margin?: number;
31
+ /**
32
+ * Custom output filename.
33
+ */
6
34
  fileName?: string;
35
+ /**
36
+ * Export progress callback.
37
+ */
7
38
  onProgress?: (status: ExportStatus) => void;
8
39
  }
9
40
  interface DiagramData {
@@ -56,8 +87,8 @@ declare const useDiagramExport: ({ svgRef, diagramData, getTableHeight, projectN
56
87
 
57
88
  interface ExportDiagramParams {
58
89
  svgRef: React.RefObject<SVGSVGElement>;
59
- diagramData: DiagramData;
60
- getTableHeight: GetTableHeight;
90
+ diagramData?: DiagramData | null;
91
+ getTableHeight?: GetTableHeight;
61
92
  projectName?: string;
62
93
  setExportAllTables?: (value: boolean) => void;
63
94
  showToast?: (message: string, type?: string) => void;
@@ -72,7 +103,16 @@ interface ExportDiagramParams {
72
103
  * 2. Calculate the real SVG content bounds.
73
104
  * 3. Create an independent SVG clone.
74
105
  * 4. Set a viewBox covering the complete content.
75
- * 5. Convert the SVG to PNG when requested.
106
+ * 5. Apply the requested background.
107
+ * 6. Convert the SVG to PNG when requested.
108
+ *
109
+ * Background examples:
110
+ *
111
+ * - 'white'
112
+ * - 'transparent'
113
+ * - '#C300FF'
114
+ * - 'rgb(195, 0, 255)'
115
+ * - 'rgba(195, 0, 255, 0.5)'
76
116
  */
77
117
  declare const exportDiagram: (params: ExportDiagramParams, options?: ExportOptions) => Promise<void>;
78
118
 
@@ -124,8 +164,21 @@ declare const getScrollDimensions: (svg: SVGSVGElement) => Dimensions;
124
164
 
125
165
  /**
126
166
  * Renders an SVG image to a high-quality canvas.
167
+ *
168
+ * The background can be:
169
+ *
170
+ * - 'transparent'
171
+ * - any valid CSS color
172
+ *
173
+ * Examples:
174
+ *
175
+ * - '#C300FF'
176
+ * - '#ffffff'
177
+ * - 'rgb(195, 0, 255)'
178
+ * - 'rgba(195, 0, 255, 0.5)'
179
+ * - 'hsl(285, 100%, 50%)'
127
180
  */
128
- declare const renderHighQuality: (image: HTMLImageElement, width: number, height: number, scale?: number, background?: "white" | "transparent") => Promise<HTMLCanvasElement>;
181
+ declare const renderHighQuality: (image: HTMLImageElement, width: number, height: number, scale?: number, background?: string) => Promise<HTMLCanvasElement>;
129
182
 
130
183
  /**
131
184
  * Downloads are handled elsewhere.
@@ -145,10 +198,15 @@ declare const createSvgUrl: (svgMarkup: string) => string;
145
198
  /**
146
199
  * Creates a clean SVG clone for export.
147
200
  *
148
- * sourceX/sourceY represent the origin of the exported
149
- * coordinate system.
201
+ * The clone receives an explicit viewBox based on the REAL
202
+ * content bounds instead of assuming the content starts at 0,0.
203
+ *
204
+ * The background can be:
205
+ *
206
+ * - 'transparent'
207
+ * - any valid CSS color
150
208
  */
151
- declare const prepareSvgForExport: (sourceSvg: SVGSVGElement, width: number, height: number, background: "white" | "transparent", margin?: number, sourceX?: number, sourceY?: number) => SVGSVGElement;
209
+ declare const prepareSvgForExport: (sourceSvg: SVGSVGElement, width: number, height: number, background?: string, margin?: number, sourceX?: number, sourceY?: number) => SVGSVGElement;
152
210
 
153
211
  /**
154
212
  * Downloads a Blob as a file.
package/dist/index.d.ts CHANGED
@@ -1,9 +1,40 @@
1
+ type ExportBackground = string;
1
2
  interface ExportOptions {
2
3
  format?: 'png' | 'svg';
3
- background?: 'white' | 'transparent';
4
+ /**
5
+ * Background color for the exported diagram.
6
+ *
7
+ * Supported examples:
8
+ *
9
+ * - 'white'
10
+ * - 'transparent'
11
+ * - '#C300FF'
12
+ * - '#ffffff'
13
+ * - 'rgb(195, 0, 255)'
14
+ * - 'rgba(195, 0, 255, 0.5)'
15
+ * - 'hsl(285, 100%, 50%)'
16
+ *
17
+ * Any valid CSS color supported by the browser can be used.
18
+ */
19
+ background?: ExportBackground;
20
+ /**
21
+ * Export scale.
22
+ *
23
+ * Example:
24
+ * scale: 2
25
+ */
4
26
  scale?: number;
27
+ /**
28
+ * Additional space around the exported diagram.
29
+ */
5
30
  margin?: number;
31
+ /**
32
+ * Custom output filename.
33
+ */
6
34
  fileName?: string;
35
+ /**
36
+ * Export progress callback.
37
+ */
7
38
  onProgress?: (status: ExportStatus) => void;
8
39
  }
9
40
  interface DiagramData {
@@ -56,8 +87,8 @@ declare const useDiagramExport: ({ svgRef, diagramData, getTableHeight, projectN
56
87
 
57
88
  interface ExportDiagramParams {
58
89
  svgRef: React.RefObject<SVGSVGElement>;
59
- diagramData: DiagramData;
60
- getTableHeight: GetTableHeight;
90
+ diagramData?: DiagramData | null;
91
+ getTableHeight?: GetTableHeight;
61
92
  projectName?: string;
62
93
  setExportAllTables?: (value: boolean) => void;
63
94
  showToast?: (message: string, type?: string) => void;
@@ -72,7 +103,16 @@ interface ExportDiagramParams {
72
103
  * 2. Calculate the real SVG content bounds.
73
104
  * 3. Create an independent SVG clone.
74
105
  * 4. Set a viewBox covering the complete content.
75
- * 5. Convert the SVG to PNG when requested.
106
+ * 5. Apply the requested background.
107
+ * 6. Convert the SVG to PNG when requested.
108
+ *
109
+ * Background examples:
110
+ *
111
+ * - 'white'
112
+ * - 'transparent'
113
+ * - '#C300FF'
114
+ * - 'rgb(195, 0, 255)'
115
+ * - 'rgba(195, 0, 255, 0.5)'
76
116
  */
77
117
  declare const exportDiagram: (params: ExportDiagramParams, options?: ExportOptions) => Promise<void>;
78
118
 
@@ -124,8 +164,21 @@ declare const getScrollDimensions: (svg: SVGSVGElement) => Dimensions;
124
164
 
125
165
  /**
126
166
  * Renders an SVG image to a high-quality canvas.
167
+ *
168
+ * The background can be:
169
+ *
170
+ * - 'transparent'
171
+ * - any valid CSS color
172
+ *
173
+ * Examples:
174
+ *
175
+ * - '#C300FF'
176
+ * - '#ffffff'
177
+ * - 'rgb(195, 0, 255)'
178
+ * - 'rgba(195, 0, 255, 0.5)'
179
+ * - 'hsl(285, 100%, 50%)'
127
180
  */
128
- declare const renderHighQuality: (image: HTMLImageElement, width: number, height: number, scale?: number, background?: "white" | "transparent") => Promise<HTMLCanvasElement>;
181
+ declare const renderHighQuality: (image: HTMLImageElement, width: number, height: number, scale?: number, background?: string) => Promise<HTMLCanvasElement>;
129
182
 
130
183
  /**
131
184
  * Downloads are handled elsewhere.
@@ -145,10 +198,15 @@ declare const createSvgUrl: (svgMarkup: string) => string;
145
198
  /**
146
199
  * Creates a clean SVG clone for export.
147
200
  *
148
- * sourceX/sourceY represent the origin of the exported
149
- * coordinate system.
201
+ * The clone receives an explicit viewBox based on the REAL
202
+ * content bounds instead of assuming the content starts at 0,0.
203
+ *
204
+ * The background can be:
205
+ *
206
+ * - 'transparent'
207
+ * - any valid CSS color
150
208
  */
151
- declare const prepareSvgForExport: (sourceSvg: SVGSVGElement, width: number, height: number, background: "white" | "transparent", margin?: number, sourceX?: number, sourceY?: number) => SVGSVGElement;
209
+ declare const prepareSvgForExport: (sourceSvg: SVGSVGElement, width: number, height: number, background?: string, margin?: number, sourceX?: number, sourceY?: number) => SVGSVGElement;
152
210
 
153
211
  /**
154
212
  * Downloads a Blob as a file.
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{useState as F,useCallback as k}from"react";var O=(r,o,t=100)=>{if(!r?.tables?.length)return{width:100+t*2,height:100+t*2,x:-t,y:-t};let e=Math.min(...r.tables.map(n=>n.x)),s=Math.min(...r.tables.map(n=>n.y)),a=Math.max(...r.tables.map(n=>n.x+280+40)),l=Math.max(...r.tables.map(n=>n.y+o(n.fields)+40));return{x:e-t,y:s-t,width:Math.max(100,a-e+t*2),height:Math.max(100,l-s+t*2)}},V=r=>{let o=Array.from(r.querySelectorAll("g, path, rect, circle, ellipse, line, polyline, polygon, text, use, image")),t=1/0,e=1/0,s=-1/0,a=-1/0,l=(n,m,i,c)=>{!Number.isFinite(n)||!Number.isFinite(m)||!Number.isFinite(i)||!Number.isFinite(c)||i<0||c<0||(t=Math.min(t,n),e=Math.min(e,m),s=Math.max(s,n+i),a=Math.max(a,m+c))};for(let n of o)if(!(n.matches('[data-export-bg="canvas"]')||n.closest('[data-export-bg="canvas"]')))try{let m=n.getBBox();l(m.x,m.y,m.width,m.height)}catch{}return!Number.isFinite(t)||!Number.isFinite(e)||!Number.isFinite(s)||!Number.isFinite(a)?null:{x:t,y:e,width:Math.max(1,s-t),height:Math.max(1,a-e)}},y=(r,o=100)=>{let t=V(r);if(t)return{x:t.x-o,y:t.y-o,width:t.width+o*2,height:t.height+o*2};let e=r.viewBox?.baseVal;if(e&&Number.isFinite(e.width)&&Number.isFinite(e.height)&&e.width>0&&e.height>0)return{x:e.x-o,y:e.y-o,width:e.width+o*2,height:e.height+o*2};let s=Number(r.getAttribute("width"))||r.clientWidth||1,a=Number(r.getAttribute("height"))||r.clientHeight||1;return{x:-o,y:-o,width:s+o*2,height:a+o*2}},$=r=>{let o=r.parentElement||r.closest(".overflow-auto")||r.closest(".overflow-scroll"),t=Number(r.getAttribute("width"))||r.clientWidth||1,e=Number(r.getAttribute("height"))||r.clientHeight||1;return o&&(t=Math.max(t,o.scrollWidth),e=Math.max(e,o.scrollHeight)),{x:0,y:0,width:t,height:e}};var S=async(r,o,t,e=2,s="transparent")=>{let l=Math.max(1,o),n=Math.max(1,t),m=Math.max(.1,e),i=Math.min(m,16384/Math.max(l,n,1)),c=document.createElement("canvas");c.width=Math.max(1,Math.floor(l*i)),c.height=Math.max(1,Math.floor(n*i));let p=c.getContext("2d",{alpha:s!=="white"});if(!p)throw new Error("Unable to create 2D canvas context");return p.imageSmoothingEnabled=!0,p.imageSmoothingQuality="high",s==="white"?(p.fillStyle="#ffffff",p.fillRect(0,0,c.width,c.height)):p.clearRect(0,0,c.width,c.height),p.drawImage(r,0,0,l*i,n*i),c};var E=r=>{let o=new XMLSerializer,t=typeof document<"u"?getComputedStyle(document.documentElement):null,e=o.serializeToString(r);return t&&(e=e.replace(/var\(--([^)]+)\)/g,(s,a)=>t.getPropertyValue(`--${a}`)?.trim()||`var(--${a})`)),e.includes('xmlns="http://www.w3.org/2000/svg"')||(e=e.replace("<svg",'<svg xmlns="http://www.w3.org/2000/svg"')),e},v=r=>URL.createObjectURL(new Blob([r],{type:"image/svg+xml;charset=utf-8"})),D=(r,o,t,e,s=0,a=0,l=0)=>{let n=r.cloneNode(!0);if(n.querySelectorAll('[data-export-bg="canvas"]').forEach(i=>{i.remove()}),n.setAttribute("width",String(o)),n.setAttribute("height",String(t)),n.setAttribute("viewBox",`${a} ${l} ${o} ${t}`),n.setAttribute("preserveAspectRatio","xMinYMin meet"),e==="white"){let i=document.createElementNS("http://www.w3.org/2000/svg","rect");i.setAttribute("x",String(a)),i.setAttribute("y",String(l)),i.setAttribute("width",String(o)),i.setAttribute("height",String(t)),i.setAttribute("fill","#ffffff"),n.insertBefore(i,n.firstChild)}if(s>0&&a===0&&l===0){let i=n.querySelector('g[transform*="scale"]');if(i){let c=i.getAttribute("transform")||"";i.setAttribute("transform",`translate(${s/2}, ${s/2}) ${c}`)}}return n};var b=(r,o)=>{let t=URL.createObjectURL(r),e=document.createElement("a");e.href=t,e.download=o;try{document.body?.appendChild(e),e.click(),document.body?.removeChild(e)}catch{e.click()}setTimeout(()=>{URL.revokeObjectURL(t)},100)},d=(r,o)=>`${(r||"diagram").replace(/\s+/g,"_").toLowerCase().replace(/[^a-z0-9_-]/g,"")}.${o}`,A=r=>new Promise((o,t)=>{try{if(typeof r.toBlob!="function"){t(new Error("Canvas.toBlob is not supported"));return}r.toBlob(e=>{if(!e){t(new Error("Failed to create PNG blob"));return}o(e)},"image/png")}catch(e){t(e)}});var G=async(r,o={})=>{let{svgRef:t,diagramData:e,getTableHeight:s,projectName:a,setExportAllTables:l,showToast:n}=r,{format:m="png",background:i="white",scale:c=2,margin:p=100,fileName:N,onProgress:u}=o;if(!t.current){u?.({type:"error",message:"SVG reference not available"});return}let h=null;try{u?.({type:"preparing"}),n?.("Preparing export...","info"),l?.(!0),await new Promise(g=>{setTimeout(g,300)}),u?.({type:"rendering"});let x=t.current,C=y(x,p),{x:L,y:R,width:T,height:M}=C,P=D(x,T,M,i,p,L,R);u?.({type:"serializing"});let B=E(P);if(m==="svg"){let g=new Blob([B],{type:"image/svg+xml;charset=utf-8"}),w=N||d(a,"svg");b(g,w),u?.({type:"complete"}),n?.("Export completed successfully","success");return}let f=new Image;h=v(B),await new Promise((g,w)=>{f.onload=()=>{g()},f.onerror=()=>{w(new Error("Failed to load SVG image"))},f.src=h});let I=await S(f,T,M,c,i);u?.({type:"downloading"});let U=await A(I),_=N||d(a,"png");b(U,_),u?.({type:"complete"}),n?.("Export completed successfully","success")}catch(x){console.error("Export error:",x),u?.({type:"error",message:"Export failed"}),n?.("Failed to export file","error")}finally{l?.(!1),h&&URL.revokeObjectURL(h)}};var H=({svgRef:r,diagramData:o,getTableHeight:t,projectName:e})=>{let[s,a]=F(!1),[l,n]=F(!1),m=k(async(i={})=>{a(!0);try{await G({svgRef:r,diagramData:o,getTableHeight:t,projectName:e,setExportAllTables:n},i)}finally{a(!1),n(!1)}},[r,o,t,e]);return{isExporting:s,exportDiagram:m,exportAllTables:l,setExportAllTables:n}};export{O as calculateDimensions,y as calculateSvgDimensions,A as canvasToBlob,v as createSvgUrl,H as default,b as downloadBlob,G as exportDiagram,d as generateFileName,$ as getScrollDimensions,V as getSvgContentBounds,D as prepareSvgForExport,S as renderHighQuality,E as serializeSvg,H as useDiagramExport};
1
+ import{useState as F,useCallback as z}from"react";var O=(r,o,t=100)=>{if(!r?.tables?.length)return{width:100+t*2,height:100+t*2,x:-t,y:-t};let e=Math.min(...r.tables.map(n=>n.x)),s=Math.min(...r.tables.map(n=>n.y)),a=Math.max(...r.tables.map(n=>n.x+280+40)),l=Math.max(...r.tables.map(n=>n.y+o(n.fields)+40));return{x:e-t,y:s-t,width:Math.max(100,a-e+t*2),height:Math.max(100,l-s+t*2)}},V=r=>{let o=Array.from(r.querySelectorAll("g, path, rect, circle, ellipse, line, polyline, polygon, text, use, image")),t=1/0,e=1/0,s=-1/0,a=-1/0,l=(n,m,i,c)=>{!Number.isFinite(n)||!Number.isFinite(m)||!Number.isFinite(i)||!Number.isFinite(c)||i<0||c<0||(t=Math.min(t,n),e=Math.min(e,m),s=Math.max(s,n+i),a=Math.max(a,m+c))};for(let n of o)if(!(n.matches('[data-export-bg="canvas"]')||n.closest('[data-export-bg="canvas"]')))try{let m=n.getBBox();l(m.x,m.y,m.width,m.height)}catch{}return!Number.isFinite(t)||!Number.isFinite(e)||!Number.isFinite(s)||!Number.isFinite(a)?null:{x:t,y:e,width:Math.max(1,s-t),height:Math.max(1,a-e)}},w=(r,o=100)=>{let t=V(r);if(t)return{x:t.x-o,y:t.y-o,width:t.width+o*2,height:t.height+o*2};let e=r.viewBox?.baseVal;if(e&&Number.isFinite(e.width)&&Number.isFinite(e.height)&&e.width>0&&e.height>0)return{x:e.x-o,y:e.y-o,width:e.width+o*2,height:e.height+o*2};let s=Number(r.getAttribute("width"))||r.clientWidth||1,a=Number(r.getAttribute("height"))||r.clientHeight||1;return{x:-o,y:-o,width:s+o*2,height:a+o*2}},k=r=>{let o=r.parentElement||r.closest(".overflow-auto")||r.closest(".overflow-scroll"),t=Number(r.getAttribute("width"))||r.clientWidth||1,e=Number(r.getAttribute("height"))||r.clientHeight||1;return o&&(t=Math.max(t,o.scrollWidth),e=Math.max(e,o.scrollHeight)),{x:0,y:0,width:t,height:e}};var S=async(r,o,t,e=2,s="transparent")=>{let l=Math.max(1,o),n=Math.max(1,t),m=Math.max(.1,e),i=Math.min(m,16384/Math.max(l,n,1)),c=document.createElement("canvas");c.width=Math.max(1,Math.floor(l*i)),c.height=Math.max(1,Math.floor(n*i));let p=c.getContext("2d",{alpha:!0});if(!p)throw new Error("Unable to create 2D canvas context");return p.imageSmoothingEnabled=!0,p.imageSmoothingQuality="high",s!=="transparent"?(p.fillStyle=s,p.fillRect(0,0,c.width,c.height)):p.clearRect(0,0,c.width,c.height),p.drawImage(r,0,0,l*i,n*i),c};var E=r=>{let o=new XMLSerializer,t=typeof document<"u"?getComputedStyle(document.documentElement):null,e=o.serializeToString(r);return t&&(e=e.replace(/var\(--([^)]+)\)/g,(s,a)=>t.getPropertyValue(`--${a}`)?.trim()||`var(--${a})`)),e.includes('xmlns="http://www.w3.org/2000/svg"')||(e=e.replace("<svg",'<svg xmlns="http://www.w3.org/2000/svg"')),e},v=r=>URL.createObjectURL(new Blob([r],{type:"image/svg+xml;charset=utf-8"})),D=(r,o,t,e="transparent",s=0,a=0,l=0)=>{let n=r.cloneNode(!0);if(n.querySelectorAll('[data-export-bg="canvas"]').forEach(i=>{i.remove()}),n.setAttribute("width",String(o)),n.setAttribute("height",String(t)),n.setAttribute("viewBox",`${a} ${l} ${o} ${t}`),n.setAttribute("preserveAspectRatio","xMinYMin meet"),e!=="transparent"){let i=document.createElementNS("http://www.w3.org/2000/svg","rect");i.setAttribute("x",String(a)),i.setAttribute("y",String(l)),i.setAttribute("width",String(o)),i.setAttribute("height",String(t)),i.setAttribute("fill",e),n.insertBefore(i,n.firstChild)}return n};var f=(r,o)=>{let t=URL.createObjectURL(r),e=document.createElement("a");e.href=t,e.download=o;try{document.body?.appendChild(e),e.click(),document.body?.removeChild(e)}catch{e.click()}setTimeout(()=>{URL.revokeObjectURL(t)},100)},d=(r,o)=>`${(r||"diagram").replace(/\s+/g,"_").toLowerCase().replace(/[^a-z0-9_-]/g,"")}.${o}`,A=r=>new Promise((o,t)=>{try{if(typeof r.toBlob!="function"){t(new Error("Canvas.toBlob is not supported"));return}r.toBlob(e=>{if(!e){t(new Error("Failed to create PNG blob"));return}o(e)},"image/png")}catch(e){t(e)}});var N=async(r,o={})=>{let{svgRef:t,diagramData:e,getTableHeight:s,projectName:a,setExportAllTables:l,showToast:n}=r,{format:m="png",background:i="white",scale:c=2,margin:p=100,fileName:G,onProgress:u}=o;if(!t.current){u?.({type:"error",message:"SVG reference not available"});return}let h=null;try{u?.({type:"preparing"}),n?.("Preparing export...","info"),l?.(!0),await new Promise(g=>{setTimeout(g,300)}),u?.({type:"rendering"});let x=t.current,C=w(x,p),{x:L,y:R,width:T,height:M}=C,P=D(x,T,M,i,p,L,R);u?.({type:"serializing"});let B=E(P);if(m==="svg"){let g=new Blob([B],{type:"image/svg+xml;charset=utf-8"}),y=G||d(a,"svg");f(g,y),u?.({type:"complete"}),n?.("Export completed successfully","success");return}let b=new Image;h=v(B),await new Promise((g,y)=>{b.onload=()=>{g()},b.onerror=()=>{y(new Error("Failed to load SVG image"))},b.src=h});let I=await S(b,T,M,c,i);u?.({type:"downloading"});let U=await A(I),_=G||d(a,"png");f(U,_),u?.({type:"complete"}),n?.("Export completed successfully","success")}catch(x){console.error("Export error:",x),u?.({type:"error",message:"Export failed"}),n?.("Failed to export file","error")}finally{l?.(!1),h&&URL.revokeObjectURL(h)}};var H=({svgRef:r,diagramData:o,getTableHeight:t,projectName:e})=>{let[s,a]=F(!1),[l,n]=F(!1),m=z(async(i={})=>{a(!0);try{await N({svgRef:r,diagramData:o,getTableHeight:t,projectName:e,setExportAllTables:n},i)}finally{a(!1),n(!1)}},[r,o,t,e]);return{isExporting:s,exportDiagram:m,exportAllTables:l,setExportAllTables:n}};export{O as calculateDimensions,w as calculateSvgDimensions,A as canvasToBlob,v as createSvgUrl,H as default,f as downloadBlob,N as exportDiagram,d as generateFileName,k as getScrollDimensions,V as getSvgContentBounds,D as prepareSvgForExport,S as renderHighQuality,E as serializeSvg,H as useDiagramExport};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useDiagramExport.ts","../src/calculateDimensions.ts","../src/renderHighQuality.ts","../src/utils/serializer.ts","../src/utils/download.ts","../src/exportDiagram.ts"],"sourcesContent":["import {\n useState,\n useCallback,\n} from 'react';\n\nimport type {\n ExportOptions,\n ExportContext,\n DiagramData,\n GetTableHeight,\n} from './types';\n\nimport {\n exportDiagram,\n} from './exportDiagram';\n\nexport interface UseDiagramExportParams {\n svgRef: React.RefObject<SVGSVGElement>;\n\n diagramData?:\n | DiagramData\n | null;\n\n getTableHeight?:\n GetTableHeight;\n\n projectName?: string;\n}\n\n/**\n * Hook that provides SVG diagram export functionality.\n *\n * The exporter automatically calculates the real SVG content bounds,\n * so diagramData is optional for generic SVG usage.\n */\nexport const useDiagramExport = ({\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n}: UseDiagramExportParams): ExportContext & {\n exportAllTables: boolean;\n setExportAllTables: (\n value: boolean,\n ) => void;\n} => {\n const [\n isExporting,\n setIsExporting,\n ] = useState(false);\n\n const [\n exportAllTables,\n setExportAllTables,\n ] = useState(false);\n\n const handleExport =\n useCallback(\n async (\n options: ExportOptions = {},\n ) => {\n setIsExporting(true);\n\n try {\n await exportDiagram(\n {\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n setExportAllTables,\n },\n options,\n );\n } finally {\n setIsExporting(false);\n\n setExportAllTables(\n false,\n );\n }\n },\n [\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n ],\n );\n\n return {\n isExporting,\n exportDiagram:\n handleExport,\n exportAllTables,\n setExportAllTables,\n };\n};","import type { DiagramData, GetTableHeight } from './types';\n\nconst TABLE_WIDTH = 280;\nconst TABLE_PADDING = 40;\n\nexport interface Dimensions {\n width: number;\n height: number;\n x: number;\n y: number;\n}\n\n/**\n * Calculates dimensions based on diagram data.\n *\n * This function is kept compatible with the original API and tests.\n *\n * It calculates the total diagram size from the table coordinates,\n * assuming the table width is 280px plus the configured padding.\n */\nexport const calculateDimensions = (\n diagramData: DiagramData | undefined | null,\n getTableHeight: GetTableHeight,\n margin: number = 100,\n): Dimensions => {\n if (!diagramData?.tables?.length) {\n return {\n width: 100 + margin * 2,\n height: 100 + margin * 2,\n x: -margin,\n y: -margin,\n };\n }\n\n const minX = Math.min(\n ...diagramData.tables.map((table) => table.x),\n );\n\n const minY = Math.min(\n ...diagramData.tables.map((table) => table.y),\n );\n\n const maxX = Math.max(\n ...diagramData.tables.map(\n (table) => table.x + TABLE_WIDTH + TABLE_PADDING,\n ),\n );\n\n const maxY = Math.max(\n ...diagramData.tables.map(\n (table) =>\n table.y +\n getTableHeight(table.fields) +\n TABLE_PADDING,\n ),\n );\n\n /*\n * Keep the original expected behavior:\n *\n * width = maxX - minX\n * height = maxY - minY\n *\n * with margin added on both sides.\n */\n return {\n x: minX - margin,\n y: minY - margin,\n width: Math.max(\n 100,\n maxX - minX + margin * 2,\n ),\n height: Math.max(\n 100,\n maxY - minY + margin * 2,\n ),\n };\n};\n\n/**\n * Calculates the visible bounds of an SVG's graphical content.\n *\n * This function is intended for generic SVG exports.\n *\n * It does NOT rely on:\n *\n * - width\n * - height\n * - viewport\n * - scroll container\n *\n * Instead, it attempts to determine the real graphical bounds.\n */\nexport const getSvgContentBounds = (\n svg: SVGSVGElement,\n): Dimensions | null => {\n const elements = Array.from(\n svg.querySelectorAll<SVGGraphicsElement>(\n 'g, path, rect, circle, ellipse, line, polyline, polygon, text, use, image',\n ),\n );\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n const updateBounds = (\n x: number,\n y: number,\n width: number,\n height: number,\n ) => {\n if (\n !Number.isFinite(x) ||\n !Number.isFinite(y) ||\n !Number.isFinite(width) ||\n !Number.isFinite(height)\n ) {\n return;\n }\n\n if (width < 0 || height < 0) {\n return;\n }\n\n minX = Math.min(minX, x);\n minY = Math.min(minY, y);\n maxX = Math.max(maxX, x + width);\n maxY = Math.max(maxY, y + height);\n };\n\n for (const element of elements) {\n /*\n * Ignore the artificial canvas background.\n */\n if (\n element.matches('[data-export-bg=\"canvas\"]') ||\n element.closest('[data-export-bg=\"canvas\"]')\n ) {\n continue;\n }\n\n try {\n const bbox = element.getBBox();\n\n updateBounds(\n bbox.x,\n bbox.y,\n bbox.width,\n bbox.height,\n );\n } catch {\n /*\n * getBBox() can fail in environments where the SVG\n * has not been rendered yet.\n */\n }\n }\n\n if (\n !Number.isFinite(minX) ||\n !Number.isFinite(minY) ||\n !Number.isFinite(maxX) ||\n !Number.isFinite(maxY)\n ) {\n return null;\n }\n\n return {\n x: minX,\n y: minY,\n width: Math.max(1, maxX - minX),\n height: Math.max(1, maxY - minY),\n };\n};\n\n/**\n * Calculates the dimensions that should be used for a real SVG export.\n *\n * The content bounds are preferred.\n *\n * The returned x/y values are important because the content does not\n * necessarily start at 0,0.\n */\nexport const calculateSvgDimensions = (\n svg: SVGSVGElement,\n margin: number = 100,\n): Dimensions => {\n const bounds = getSvgContentBounds(svg);\n\n if (bounds) {\n return {\n x: bounds.x - margin,\n y: bounds.y - margin,\n width: bounds.width + margin * 2,\n height: bounds.height + margin * 2,\n };\n }\n\n /*\n * Fallback to the SVG viewBox.\n */\n const viewBox = svg.viewBox?.baseVal;\n\n if (\n viewBox &&\n Number.isFinite(viewBox.width) &&\n Number.isFinite(viewBox.height) &&\n viewBox.width > 0 &&\n viewBox.height > 0\n ) {\n return {\n x: viewBox.x - margin,\n y: viewBox.y - margin,\n width: viewBox.width + margin * 2,\n height: viewBox.height + margin * 2,\n };\n }\n\n /*\n * Final fallback to width/height attributes.\n */\n const width =\n Number(svg.getAttribute('width')) ||\n svg.clientWidth ||\n 1;\n\n const height =\n Number(svg.getAttribute('height')) ||\n svg.clientHeight ||\n 1;\n\n return {\n x: -margin,\n y: -margin,\n width: width + margin * 2,\n height: height + margin * 2,\n };\n};\n\n/**\n * Calculates dimensions based on an SVG element and its scroll container.\n *\n * Kept for backwards compatibility.\n */\nexport const getScrollDimensions = (\n svg: SVGSVGElement,\n): Dimensions => {\n const scrollContainer =\n svg.parentElement ||\n svg.closest('.overflow-auto') ||\n svg.closest('.overflow-scroll');\n\n let width =\n Number(svg.getAttribute('width')) ||\n svg.clientWidth ||\n 1;\n\n let height =\n Number(svg.getAttribute('height')) ||\n svg.clientHeight ||\n 1;\n\n if (scrollContainer) {\n width = Math.max(\n width,\n scrollContainer.scrollWidth,\n );\n\n height = Math.max(\n height,\n scrollContainer.scrollHeight,\n );\n }\n\n return {\n x: 0,\n y: 0,\n width,\n height,\n };\n};","/**\n * Renders an SVG image to a high-quality canvas.\n */\nexport const renderHighQuality = async (\n image: HTMLImageElement,\n width: number,\n height: number,\n scale: number = 2,\n background: 'white' | 'transparent' = 'transparent',\n): Promise<HTMLCanvasElement> => {\n const MAX_CANVAS_SIZE = 16384;\n\n const safeWidth =\n Math.max(1, width);\n\n const safeHeight =\n Math.max(1, height);\n\n const safeScale =\n Math.max(0.1, scale);\n\n const effectiveScale = Math.min(\n safeScale,\n MAX_CANVAS_SIZE /\n Math.max(\n safeWidth,\n safeHeight,\n 1,\n ),\n );\n\n const canvas =\n document.createElement(\n 'canvas',\n );\n\n canvas.width = Math.max(\n 1,\n Math.floor(\n safeWidth *\n effectiveScale,\n ),\n );\n\n canvas.height = Math.max(\n 1,\n Math.floor(\n safeHeight *\n effectiveScale,\n ),\n );\n\n const context =\n canvas.getContext(\n '2d',\n {\n alpha:\n background !== 'white',\n },\n );\n\n if (!context) {\n throw new Error(\n 'Unable to create 2D canvas context',\n );\n }\n\n context.imageSmoothingEnabled =\n true;\n\n context.imageSmoothingQuality =\n 'high';\n\n /*\n * Background.\n */\n if (\n background === 'white'\n ) {\n context.fillStyle =\n '#ffffff';\n\n context.fillRect(\n 0,\n 0,\n canvas.width,\n canvas.height,\n );\n } else {\n context.clearRect(\n 0,\n 0,\n canvas.width,\n canvas.height,\n );\n }\n\n /*\n * The canvas already contains the scaled dimensions,\n * so we draw using the original SVG dimensions.\n */\n context.drawImage(\n image,\n 0,\n 0,\n safeWidth *\n effectiveScale,\n safeHeight *\n effectiveScale,\n );\n\n return canvas;\n};","/**\n * Downloads are handled elsewhere.\n *\n * This file contains SVG serialization and preparation utilities.\n */\n\n/**\n * Serializes an SVG element to a string.\n *\n * CSS custom properties are resolved using the current document styles.\n */\nexport const serializeSvg = (\n svg: SVGSVGElement,\n): string => {\n const serializer =\n new XMLSerializer();\n\n const styles =\n typeof document !== 'undefined'\n ? getComputedStyle(\n document.documentElement,\n )\n : null;\n\n let markup =\n serializer.serializeToString(\n svg,\n );\n\n /*\n * Resolve CSS custom properties.\n */\n if (styles) {\n markup = markup.replace(\n /var\\(--([^)]+)\\)/g,\n (\n _match,\n name: string,\n ) => {\n const value =\n styles\n .getPropertyValue(\n `--${name}`,\n )\n ?.trim();\n\n return (\n value ||\n `var(--${name})`\n );\n },\n );\n }\n\n /*\n * Ensure SVG has the proper namespace.\n */\n if (\n !markup.includes(\n 'xmlns=\"http://www.w3.org/2000/svg\"',\n )\n ) {\n markup = markup.replace(\n '<svg',\n '<svg xmlns=\"http://www.w3.org/2000/svg\"',\n );\n }\n\n return markup;\n};\n\n/**\n * Creates a Blob URL from SVG markup.\n */\nexport const createSvgUrl = (\n svgMarkup: string,\n): string => {\n return URL.createObjectURL(\n new Blob(\n [svgMarkup],\n {\n type:\n 'image/svg+xml;charset=utf-8',\n },\n ),\n );\n};\n\n/**\n * Creates a clean SVG clone for export.\n *\n * sourceX/sourceY represent the origin of the exported\n * coordinate system.\n */\nexport const prepareSvgForExport = (\n sourceSvg: SVGSVGElement,\n width: number,\n height: number,\n background: 'white' | 'transparent',\n margin: number = 0,\n sourceX: number = 0,\n sourceY: number = 0,\n): SVGSVGElement => {\n const clone =\n sourceSvg.cloneNode(\n true,\n ) as SVGSVGElement;\n\n /*\n * Remove viewport/background elements.\n */\n const bgNodes =\n clone.querySelectorAll(\n '[data-export-bg=\"canvas\"]',\n );\n\n bgNodes.forEach(\n (node) => {\n node.remove();\n },\n );\n\n /*\n * Set final dimensions.\n */\n clone.setAttribute(\n 'width',\n String(width),\n );\n\n clone.setAttribute(\n 'height',\n String(height),\n );\n\n /*\n * The viewBox is what allows us to export content\n * that exists outside the original viewport.\n */\n clone.setAttribute(\n 'viewBox',\n `${sourceX} ${sourceY} ${width} ${height}`,\n );\n\n /*\n * Prevent unexpected aspect-ratio behavior.\n */\n clone.setAttribute(\n 'preserveAspectRatio',\n 'xMinYMin meet',\n );\n\n /*\n * Add explicit background.\n */\n if (\n background === 'white'\n ) {\n const whiteRect =\n document.createElementNS(\n 'http://www.w3.org/2000/svg',\n 'rect',\n );\n\n whiteRect.setAttribute(\n 'x',\n String(sourceX),\n );\n\n whiteRect.setAttribute(\n 'y',\n String(sourceY),\n );\n\n whiteRect.setAttribute(\n 'width',\n String(width),\n );\n\n whiteRect.setAttribute(\n 'height',\n String(height),\n );\n\n whiteRect.setAttribute(\n 'fill',\n '#ffffff',\n );\n\n /*\n * Keep background behind the diagram.\n */\n clone.insertBefore(\n whiteRect,\n clone.firstChild,\n );\n }\n\n /*\n * Backwards compatibility:\n *\n * Older consumers/tests may call this function without\n * sourceX/sourceY and expect the old margin behavior.\n *\n * In the real export flow sourceX/sourceY are provided,\n * so we do NOT modify existing transforms.\n */\n if (\n margin > 0 &&\n sourceX === 0 &&\n sourceY === 0\n ) {\n const scaledGroup =\n clone.querySelector(\n 'g[transform*=\"scale\"]',\n );\n\n if (scaledGroup) {\n const existingTransform =\n scaledGroup.getAttribute(\n 'transform',\n ) || '';\n\n /*\n * Preserve the original scale/transform.\n *\n * We prepend translate instead of replacing\n * the transform entirely.\n */\n scaledGroup.setAttribute(\n 'transform',\n `translate(${margin / 2}, ${margin / 2}) ${existingTransform}`,\n );\n }\n }\n\n return clone;\n};","/**\n * Downloads a Blob as a file.\n */\nexport const downloadBlob = (\n blob: Blob,\n fileName: string,\n): void => {\n const url =\n URL.createObjectURL(\n blob,\n );\n\n const link =\n document.createElement(\n 'a',\n );\n\n link.href = url;\n link.download = fileName;\n\n try {\n document.body?.appendChild(\n link,\n );\n\n link.click();\n\n document.body?.removeChild(\n link,\n );\n } catch {\n link.click();\n }\n\n /*\n * Give the browser a chance to start the download before\n * revoking the object URL.\n */\n setTimeout(() => {\n URL.revokeObjectURL(\n url,\n );\n }, 100);\n};\n\n/**\n * Generates a safe filename.\n */\nexport const generateFileName = (\n projectName:\n | string\n | undefined,\n format:\n | 'png'\n | 'svg',\n): string => {\n const safeName =\n (\n projectName ||\n 'diagram'\n )\n .replace(\n /\\s+/g,\n '_',\n )\n .toLowerCase()\n .replace(\n /[^a-z0-9_-]/g,\n '',\n );\n\n return `${safeName}.${format}`;\n};\n\n/**\n * Converts a canvas to a PNG Blob.\n */\nexport const canvasToBlob = (\n canvas: HTMLCanvasElement,\n): Promise<Blob> => {\n return new Promise(\n (\n resolve,\n reject,\n ) => {\n try {\n if (\n typeof canvas.toBlob !==\n 'function'\n ) {\n reject(\n new Error(\n 'Canvas.toBlob is not supported',\n ),\n );\n\n return;\n }\n\n canvas.toBlob(\n (\n blob,\n ) => {\n if (!blob) {\n reject(\n new Error(\n 'Failed to create PNG blob',\n ),\n );\n\n return;\n }\n\n resolve(blob);\n },\n 'image/png',\n );\n } catch (error) {\n reject(error);\n }\n },\n );\n};","import type {\n ExportOptions,\n DiagramData,\n GetTableHeight,\n} from './types';\n\nimport {\n calculateSvgDimensions,\n} from './calculateDimensions';\n\nimport {\n renderHighQuality,\n} from './renderHighQuality';\n\nimport {\n serializeSvg,\n createSvgUrl,\n prepareSvgForExport,\n} from './utils/serializer';\n\nimport {\n downloadBlob,\n generateFileName,\n canvasToBlob,\n} from './utils/download';\n\nexport interface ExportDiagramParams {\n svgRef: React.RefObject<SVGSVGElement>;\n diagramData: DiagramData;\n getTableHeight: GetTableHeight;\n projectName?: string;\n setExportAllTables?: (value: boolean) => void;\n showToast?: (\n message: string,\n type?: string,\n ) => void;\n}\n\n/**\n * Exports the complete SVG diagram regardless of the\n * visible viewport.\n *\n * The export process is:\n *\n * 1. Render all diagram content.\n * 2. Calculate the real SVG content bounds.\n * 3. Create an independent SVG clone.\n * 4. Set a viewBox covering the complete content.\n * 5. Convert the SVG to PNG when requested.\n */\nexport const exportDiagram = async (\n params: ExportDiagramParams,\n options: ExportOptions = {},\n): Promise<void> => {\n const {\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n setExportAllTables,\n showToast,\n } = params;\n\n const {\n format = 'png',\n background = 'white',\n scale = 2,\n margin = 100,\n fileName,\n onProgress,\n } = options;\n\n if (!svgRef.current) {\n onProgress?.({\n type: 'error',\n message: 'SVG reference not available',\n });\n\n return;\n }\n\n let objectUrl: string | null = null;\n\n try {\n onProgress?.({\n type: 'preparing',\n });\n\n showToast?.(\n 'Preparing export...',\n 'info',\n );\n\n /*\n * Tell the consuming application to render all\n * currently virtualized/cullable content.\n */\n setExportAllTables?.(true);\n\n /*\n * Give React time to render the complete diagram.\n */\n await new Promise<void>((resolve) => {\n setTimeout(resolve, 300);\n });\n\n onProgress?.({\n type: 'rendering',\n });\n\n const sourceSvg = svgRef.current;\n\n /*\n * IMPORTANT:\n *\n * Do NOT use calculateDimensions() here.\n *\n * calculateDimensions() exists for diagram-data based\n * compatibility.\n *\n * For a generic SVG export we need the real SVG bounds.\n */\n const dimensions = calculateSvgDimensions(\n sourceSvg,\n margin,\n );\n\n const {\n x,\n y,\n width,\n height,\n } = dimensions;\n\n /*\n * Prepare a completely independent SVG clone.\n */\n const exportSvg = prepareSvgForExport(\n sourceSvg,\n width,\n height,\n background,\n margin,\n x,\n y,\n );\n\n onProgress?.({\n type: 'serializing',\n });\n\n /*\n * Serialize the prepared SVG.\n */\n const svgMarkup = serializeSvg(\n exportSvg,\n );\n\n /*\n * SVG export does not require canvas.\n */\n if (format === 'svg') {\n const blob = new Blob(\n [svgMarkup],\n {\n type:\n 'image/svg+xml;charset=utf-8',\n },\n );\n\n const finalFileName =\n fileName ||\n generateFileName(\n projectName,\n 'svg',\n );\n\n downloadBlob(\n blob,\n finalFileName,\n );\n\n onProgress?.({\n type: 'complete',\n });\n\n showToast?.(\n 'Export completed successfully',\n 'success',\n );\n\n return;\n }\n\n /*\n * Create an object URL for the SVG.\n */\n const image = new Image();\n\n objectUrl = createSvgUrl(\n svgMarkup,\n );\n\n await new Promise<void>(\n (resolve, reject) => {\n image.onload = () => {\n resolve();\n };\n\n image.onerror = () => {\n reject(\n new Error(\n 'Failed to load SVG image',\n ),\n );\n };\n\n image.src = objectUrl!;\n },\n );\n\n /*\n * Render the complete SVG into a high-quality canvas.\n */\n const canvas =\n await renderHighQuality(\n image,\n width,\n height,\n scale,\n background,\n );\n\n onProgress?.({\n type: 'downloading',\n });\n\n /*\n * Convert the canvas to PNG.\n */\n const blob =\n await canvasToBlob(\n canvas,\n );\n\n const finalFileName =\n fileName ||\n generateFileName(\n projectName,\n 'png',\n );\n\n downloadBlob(\n blob,\n finalFileName,\n );\n\n onProgress?.({\n type: 'complete',\n });\n\n showToast?.(\n 'Export completed successfully',\n 'success',\n );\n } catch (error) {\n console.error(\n 'Export error:',\n error,\n );\n\n onProgress?.({\n type: 'error',\n message: 'Export failed',\n });\n\n showToast?.(\n 'Failed to export file',\n 'error',\n );\n } finally {\n /*\n * Always restore the normal rendering state.\n */\n setExportAllTables?.(\n false,\n );\n\n /*\n * Always release the object URL.\n */\n if (objectUrl) {\n URL.revokeObjectURL(\n objectUrl,\n );\n }\n }\n};"],"mappings":"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,EACExB,EACA,KACF,EAEFyB,EACEH,EACAC,CACF,EAEAf,IAAa,CACX,KAAM,UACR,CAAC,EAEDN,IACE,gCACA,SACF,EAEA,MACF,CAKA,IAAMwB,EAAQ,IAAI,MAElBjB,EAAYkB,EACVP,CACF,EAEA,MAAM,IAAI,QACR,CAACV,EAASkB,IAAW,CACnBF,EAAM,OAAS,IAAM,CACnBhB,EAAQ,CACV,EAEAgB,EAAM,QAAU,IAAM,CACpBE,EACE,IAAI,MACF,0BACF,CACF,CACF,EAEAF,EAAM,IAAMjB,CACd,CACF,EAKA,IAAMoB,EACJ,MAAMC,EACJJ,EACAV,EACAC,EACAZ,EACAD,CACF,EAEFI,IAAa,CACX,KAAM,aACR,CAAC,EAKD,IAAMc,EACJ,MAAMS,EACJF,CACF,EAEIN,EACJhB,GACAiB,EACExB,EACA,KACF,EAEFyB,EACEH,EACAC,CACF,EAEAf,IAAa,CACX,KAAM,UACR,CAAC,EAEDN,IACE,gCACA,SACF,CACF,OAAS8B,EAAO,CACd,QAAQ,MACN,gBACAA,CACF,EAEAxB,IAAa,CACX,KAAM,QACN,QAAS,eACX,CAAC,EAEDN,IACE,wBACA,OACF,CACF,QAAE,CAIAD,IACE,EACF,EAKIQ,GACF,IAAI,gBACFA,CACF,CAEJ,CACF,ELtQO,IAAMwB,EAAmB,CAAC,CAC/B,OAAAC,EACA,YAAAC,EACA,eAAAC,EACA,YAAAC,CACF,IAKK,CACH,GAAM,CACJC,EACAC,CACF,EAAIC,EAAS,EAAK,EAEZ,CACJC,EACAC,CACF,EAAIF,EAAS,EAAK,EAEZG,EACJC,EACE,MACEC,EAAyB,CAAC,IACvB,CACHN,EAAe,EAAI,EAEnB,GAAI,CACF,MAAMO,EACJ,CACE,OAAAZ,EACA,YAAAC,EACA,eAAAC,EACA,YAAAC,EACA,mBAAAK,CACF,EACAG,CACF,CACF,QAAE,CACAN,EAAe,EAAK,EAEpBG,EACE,EACF,CACF,CACF,EACA,CACER,EACAC,EACAC,EACAC,CACF,CACF,EAEF,MAAO,CACL,YAAAC,EACA,cACEK,EACF,gBAAAF,EACA,mBAAAC,CACF,CACF","names":["useState","useCallback","calculateDimensions","diagramData","getTableHeight","margin","minX","table","minY","maxX","maxY","getSvgContentBounds","svg","elements","updateBounds","x","y","width","height","element","bbox","calculateSvgDimensions","bounds","viewBox","getScrollDimensions","scrollContainer","renderHighQuality","image","width","height","scale","background","safeWidth","safeHeight","safeScale","effectiveScale","canvas","context","serializeSvg","svg","serializer","styles","markup","_match","name","createSvgUrl","svgMarkup","prepareSvgForExport","sourceSvg","width","height","background","margin","sourceX","sourceY","clone","node","whiteRect","scaledGroup","existingTransform","downloadBlob","blob","fileName","url","link","generateFileName","projectName","format","canvasToBlob","canvas","resolve","reject","error","exportDiagram","params","options","svgRef","diagramData","getTableHeight","projectName","setExportAllTables","showToast","format","background","scale","margin","fileName","onProgress","objectUrl","resolve","sourceSvg","dimensions","calculateSvgDimensions","x","y","width","height","exportSvg","prepareSvgForExport","svgMarkup","serializeSvg","blob","finalFileName","generateFileName","downloadBlob","image","createSvgUrl","reject","canvas","renderHighQuality","canvasToBlob","error","useDiagramExport","svgRef","diagramData","getTableHeight","projectName","isExporting","setIsExporting","useState","exportAllTables","setExportAllTables","handleExport","useCallback","options","exportDiagram"]}
1
+ {"version":3,"sources":["../src/useDiagramExport.ts","../src/calculateDimensions.ts","../src/renderHighQuality.ts","../src/utils/serializer.ts","../src/utils/download.ts","../src/exportDiagram.ts"],"sourcesContent":["import {\n useState,\n useCallback,\n} from 'react';\n\nimport type {\n ExportOptions,\n ExportContext,\n DiagramData,\n GetTableHeight,\n} from './types';\n\nimport {\n exportDiagram,\n} from './exportDiagram';\n\nexport interface UseDiagramExportParams {\n svgRef:\n React.RefObject<SVGSVGElement>;\n\n diagramData?:\n | DiagramData\n | null;\n\n getTableHeight?:\n GetTableHeight;\n\n projectName?: string;\n}\n\n/**\n * Hook that provides SVG diagram export functionality.\n *\n * The exporter automatically calculates the real SVG content bounds,\n * so diagramData is optional for generic SVG usage.\n */\nexport const useDiagramExport = ({\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n}: UseDiagramExportParams):\n ExportContext & {\n exportAllTables: boolean;\n\n setExportAllTables: (\n value: boolean,\n ) => void;\n } => {\n const [\n isExporting,\n setIsExporting,\n ] = useState(false);\n\n const [\n exportAllTables,\n setExportAllTables,\n ] = useState(false);\n\n const handleExport =\n useCallback(\n async (\n options: ExportOptions = {},\n ) => {\n setIsExporting(\n true,\n );\n\n try {\n await exportDiagram(\n {\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n setExportAllTables,\n },\n options,\n );\n } finally {\n setIsExporting(\n false,\n );\n\n setExportAllTables(\n false,\n );\n }\n },\n [\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n ],\n );\n\n return {\n isExporting,\n\n exportDiagram:\n handleExport,\n\n exportAllTables,\n\n setExportAllTables,\n };\n};","import type {\n DiagramData,\n GetTableHeight,\n} from './types';\n\nconst TABLE_WIDTH = 280;\nconst TABLE_PADDING = 40;\n\nexport interface Dimensions {\n width: number;\n height: number;\n x: number;\n y: number;\n}\n\n/**\n * Calculates dimensions based on diagram data.\n *\n * This function is kept compatible with the original API and tests.\n *\n * It calculates the total diagram size from the table coordinates,\n * assuming the table width is 280px plus the configured padding.\n */\nexport const calculateDimensions = (\n diagramData:\n | DiagramData\n | undefined\n | null,\n getTableHeight:\n GetTableHeight,\n margin: number = 100,\n): Dimensions => {\n if (\n !diagramData?.tables?.length\n ) {\n return {\n width:\n 100 +\n margin * 2,\n\n height:\n 100 +\n margin * 2,\n\n x:\n -margin,\n\n y:\n -margin,\n };\n }\n\n const minX =\n Math.min(\n ...diagramData.tables.map(\n (table) =>\n table.x,\n ),\n );\n\n const minY =\n Math.min(\n ...diagramData.tables.map(\n (table) =>\n table.y,\n ),\n );\n\n const maxX =\n Math.max(\n ...diagramData.tables.map(\n (table) =>\n table.x +\n TABLE_WIDTH +\n TABLE_PADDING,\n ),\n );\n\n const maxY =\n Math.max(\n ...diagramData.tables.map(\n (table) =>\n table.y +\n getTableHeight(\n table.fields,\n ) +\n TABLE_PADDING,\n ),\n );\n\n return {\n x:\n minX -\n margin,\n\n y:\n minY -\n margin,\n\n width:\n Math.max(\n 100,\n maxX -\n minX +\n margin * 2,\n ),\n\n height:\n Math.max(\n 100,\n maxY -\n minY +\n margin * 2,\n ),\n };\n};\n\n/**\n * Calculates the visible bounds of an SVG's graphical content.\n *\n * This function is intended for generic SVG exports.\n *\n * It does NOT rely on:\n *\n * - width\n * - height\n * - viewport\n * - scroll container\n *\n * Instead, it attempts to determine the real graphical bounds.\n */\nexport const getSvgContentBounds = (\n svg: SVGSVGElement,\n): Dimensions | null => {\n const elements =\n Array.from(\n svg.querySelectorAll<SVGGraphicsElement>(\n 'g, path, rect, circle, ellipse, line, polyline, polygon, text, use, image',\n ),\n );\n\n let minX =\n Infinity;\n\n let minY =\n Infinity;\n\n let maxX =\n -Infinity;\n\n let maxY =\n -Infinity;\n\n const updateBounds =\n (\n x: number,\n y: number,\n width: number,\n height: number,\n ) => {\n if (\n !Number.isFinite(\n x,\n ) ||\n !Number.isFinite(\n y,\n ) ||\n !Number.isFinite(\n width,\n ) ||\n !Number.isFinite(\n height,\n )\n ) {\n return;\n }\n\n if (\n width < 0 ||\n height < 0\n ) {\n return;\n }\n\n minX =\n Math.min(\n minX,\n x,\n );\n\n minY =\n Math.min(\n minY,\n y,\n );\n\n maxX =\n Math.max(\n maxX,\n x + width,\n );\n\n maxY =\n Math.max(\n maxY,\n y + height,\n );\n };\n\n for (\n const element of elements\n ) {\n /*\n * Ignore the artificial canvas background.\n */\n if (\n element.matches(\n '[data-export-bg=\"canvas\"]',\n ) ||\n element.closest(\n '[data-export-bg=\"canvas\"]',\n )\n ) {\n continue;\n }\n\n try {\n const bbox =\n element.getBBox();\n\n updateBounds(\n bbox.x,\n bbox.y,\n bbox.width,\n bbox.height,\n );\n } catch {\n /*\n * getBBox() can fail in environments where\n * the SVG has not been rendered yet.\n */\n }\n }\n\n if (\n !Number.isFinite(\n minX,\n ) ||\n !Number.isFinite(\n minY,\n ) ||\n !Number.isFinite(\n maxX,\n ) ||\n !Number.isFinite(\n maxY,\n )\n ) {\n return null;\n }\n\n return {\n x:\n minX,\n\n y:\n minY,\n\n width:\n Math.max(\n 1,\n maxX - minX,\n ),\n\n height:\n Math.max(\n 1,\n maxY - minY,\n ),\n };\n};\n\n/**\n * Calculates the dimensions that should be used for a real SVG export.\n *\n * The content bounds are preferred.\n *\n * The returned x/y values are important because the content does not\n * necessarily start at 0,0.\n */\nexport const calculateSvgDimensions = (\n svg: SVGSVGElement,\n margin: number = 100,\n): Dimensions => {\n const bounds =\n getSvgContentBounds(\n svg,\n );\n\n if (bounds) {\n return {\n x:\n bounds.x -\n margin,\n\n y:\n bounds.y -\n margin,\n\n width:\n bounds.width +\n margin * 2,\n\n height:\n bounds.height +\n margin * 2,\n };\n }\n\n /*\n * Fallback to the SVG viewBox.\n */\n const viewBox =\n svg.viewBox?.baseVal;\n\n if (\n viewBox &&\n Number.isFinite(\n viewBox.width,\n ) &&\n Number.isFinite(\n viewBox.height,\n ) &&\n viewBox.width > 0 &&\n viewBox.height > 0\n ) {\n return {\n x:\n viewBox.x -\n margin,\n\n y:\n viewBox.y -\n margin,\n\n width:\n viewBox.width +\n margin * 2,\n\n height:\n viewBox.height +\n margin * 2,\n };\n }\n\n /*\n * Final fallback to width/height attributes.\n */\n const width =\n Number(\n svg.getAttribute(\n 'width',\n ),\n ) ||\n svg.clientWidth ||\n 1;\n\n const height =\n Number(\n svg.getAttribute(\n 'height',\n ),\n ) ||\n svg.clientHeight ||\n 1;\n\n return {\n x:\n -margin,\n\n y:\n -margin,\n\n width:\n width +\n margin * 2,\n\n height:\n height +\n margin * 2,\n };\n};\n\n/**\n * Calculates dimensions based on an SVG element and its scroll container.\n *\n * Kept for backwards compatibility.\n */\nexport const getScrollDimensions = (\n svg: SVGSVGElement,\n): Dimensions => {\n const scrollContainer =\n svg.parentElement ||\n svg.closest(\n '.overflow-auto',\n ) ||\n svg.closest(\n '.overflow-scroll',\n );\n\n let width =\n Number(\n svg.getAttribute(\n 'width',\n ),\n ) ||\n svg.clientWidth ||\n 1;\n\n let height =\n Number(\n svg.getAttribute(\n 'height',\n ),\n ) ||\n svg.clientHeight ||\n 1;\n\n if (\n scrollContainer\n ) {\n width =\n Math.max(\n width,\n scrollContainer.scrollWidth,\n );\n\n height =\n Math.max(\n height,\n scrollContainer.scrollHeight,\n );\n }\n\n return {\n x: 0,\n y: 0,\n width,\n height,\n };\n};","/**\n * Renders an SVG image to a high-quality canvas.\n *\n * The background can be:\n *\n * - 'transparent'\n * - any valid CSS color\n *\n * Examples:\n *\n * - '#C300FF'\n * - '#ffffff'\n * - 'rgb(195, 0, 255)'\n * - 'rgba(195, 0, 255, 0.5)'\n * - 'hsl(285, 100%, 50%)'\n */\nexport const renderHighQuality = async (\n image: HTMLImageElement,\n width: number,\n height: number,\n scale: number = 2,\n background: string = 'transparent',\n): Promise<HTMLCanvasElement> => {\n const MAX_CANVAS_SIZE = 16384;\n\n const safeWidth =\n Math.max(1, width);\n\n const safeHeight =\n Math.max(1, height);\n\n const safeScale =\n Math.max(0.1, scale);\n\n const effectiveScale = Math.min(\n safeScale,\n MAX_CANVAS_SIZE /\n Math.max(\n safeWidth,\n safeHeight,\n 1,\n ),\n );\n\n const canvas =\n document.createElement(\n 'canvas',\n );\n\n canvas.width = Math.max(\n 1,\n Math.floor(\n safeWidth *\n effectiveScale,\n ),\n );\n\n canvas.height = Math.max(\n 1,\n Math.floor(\n safeHeight *\n effectiveScale,\n ),\n );\n\n /*\n * Always keep alpha enabled.\n *\n * This is important because the user may provide:\n *\n * rgba(...)\n *\n * or transparent.\n */\n const context =\n canvas.getContext(\n '2d',\n {\n alpha: true,\n },\n );\n\n if (!context) {\n throw new Error(\n 'Unable to create 2D canvas context',\n );\n }\n\n context.imageSmoothingEnabled =\n true;\n\n context.imageSmoothingQuality =\n 'high';\n\n /*\n * Background handling.\n *\n * transparent:\n * Keep the canvas transparent.\n *\n * any other value:\n * Use it as a CSS color.\n */\n if (\n background !== 'transparent'\n ) {\n context.fillStyle =\n background;\n\n context.fillRect(\n 0,\n 0,\n canvas.width,\n canvas.height,\n );\n } else {\n context.clearRect(\n 0,\n 0,\n canvas.width,\n canvas.height,\n );\n }\n\n /*\n * The canvas already contains the\n * final scaled dimensions.\n */\n context.drawImage(\n image,\n 0,\n 0,\n safeWidth *\n effectiveScale,\n safeHeight *\n effectiveScale,\n );\n\n return canvas;\n};","/**\n * Downloads are handled elsewhere.\n *\n * This file contains SVG serialization and preparation utilities.\n */\n\n/**\n * Serializes an SVG element to a string.\n *\n * CSS custom properties are resolved using the current document styles.\n */\nexport const serializeSvg = (\n svg: SVGSVGElement,\n): string => {\n const serializer =\n new XMLSerializer();\n\n const styles =\n typeof document !== 'undefined'\n ? getComputedStyle(\n document.documentElement,\n )\n : null;\n\n let markup =\n serializer.serializeToString(\n svg,\n );\n\n if (styles) {\n markup = markup.replace(\n /var\\(--([^)]+)\\)/g,\n (\n _match,\n name: string,\n ) => {\n const value =\n styles\n .getPropertyValue(\n `--${name}`,\n )\n ?.trim();\n\n return value ||\n `var(--${name})`;\n },\n );\n }\n\n /*\n * Ensure SVG has the proper namespace.\n */\n if (\n !markup.includes(\n 'xmlns=\"http://www.w3.org/2000/svg\"',\n )\n ) {\n markup = markup.replace(\n '<svg',\n '<svg xmlns=\"http://www.w3.org/2000/svg\"',\n );\n }\n\n return markup;\n};\n\n/**\n * Creates a Blob URL from SVG markup.\n */\nexport const createSvgUrl = (\n svgMarkup: string,\n): string => {\n return URL.createObjectURL(\n new Blob(\n [svgMarkup],\n {\n type:\n 'image/svg+xml;charset=utf-8',\n },\n ),\n );\n};\n\n/**\n * Creates a clean SVG clone for export.\n *\n * The clone receives an explicit viewBox based on the REAL\n * content bounds instead of assuming the content starts at 0,0.\n *\n * The background can be:\n *\n * - 'transparent'\n * - any valid CSS color\n */\nexport const prepareSvgForExport = (\n sourceSvg: SVGSVGElement,\n width: number,\n height: number,\n background: string = 'transparent',\n margin: number = 0,\n sourceX: number = 0,\n sourceY: number = 0,\n): SVGSVGElement => {\n const clone =\n sourceSvg.cloneNode(\n true,\n ) as SVGSVGElement;\n\n /*\n * Remove elements explicitly marked as canvas backgrounds.\n *\n * These backgrounds are usually viewport-sized and would otherwise\n * make getBBox() consider the entire viewport instead of the\n * actual diagram.\n */\n const bgNodes =\n clone.querySelectorAll(\n '[data-export-bg=\"canvas\"]',\n );\n\n bgNodes.forEach(\n (node) => {\n node.remove();\n },\n );\n\n /*\n * The dimensions passed here already include the requested margin.\n */\n clone.setAttribute(\n 'width',\n String(width),\n );\n\n clone.setAttribute(\n 'height',\n String(height),\n );\n\n clone.setAttribute(\n 'viewBox',\n `${sourceX} ${sourceY} ${width} ${height}`,\n );\n\n /*\n * Prevent unexpected cropping or distortion.\n */\n clone.setAttribute(\n 'preserveAspectRatio',\n 'xMinYMin meet',\n );\n\n /*\n * Add an explicit background when requested.\n *\n * transparent:\n * No background is added.\n *\n * Any other CSS color:\n * The color is used as the SVG background.\n */\n if (\n background !== 'transparent'\n ) {\n const backgroundRect =\n document.createElementNS(\n 'http://www.w3.org/2000/svg',\n 'rect',\n );\n\n backgroundRect.setAttribute(\n 'x',\n String(sourceX),\n );\n\n backgroundRect.setAttribute(\n 'y',\n String(sourceY),\n );\n\n backgroundRect.setAttribute(\n 'width',\n String(width),\n );\n\n backgroundRect.setAttribute(\n 'height',\n String(height),\n );\n\n backgroundRect.setAttribute(\n 'fill',\n background,\n );\n\n /*\n * Keep the background behind the diagram.\n */\n clone.insertBefore(\n backgroundRect,\n clone.firstChild,\n );\n }\n\n /*\n * We intentionally do NOT manipulate arbitrary\n * transforms here.\n *\n * The viewBox handles positioning and margin.\n */\n return clone;\n};","/**\n * Downloads a Blob as a file.\n */\nexport const downloadBlob = (\n blob: Blob,\n fileName: string,\n): void => {\n const url =\n URL.createObjectURL(\n blob,\n );\n\n const link =\n document.createElement(\n 'a',\n );\n\n link.href = url;\n link.download = fileName;\n\n try {\n document.body?.appendChild(\n link,\n );\n\n link.click();\n\n document.body?.removeChild(\n link,\n );\n } catch {\n link.click();\n }\n\n /*\n * Give the browser a chance to start the download before\n * revoking the object URL.\n */\n setTimeout(() => {\n URL.revokeObjectURL(\n url,\n );\n }, 100);\n};\n\n/**\n * Generates a safe filename.\n */\nexport const generateFileName = (\n projectName:\n | string\n | undefined,\n format:\n | 'png'\n | 'svg',\n): string => {\n const safeName =\n (\n projectName ||\n 'diagram'\n )\n .replace(\n /\\s+/g,\n '_',\n )\n .toLowerCase()\n .replace(\n /[^a-z0-9_-]/g,\n '',\n );\n\n return `${safeName}.${format}`;\n};\n\n/**\n * Converts a canvas to a PNG Blob.\n */\nexport const canvasToBlob = (\n canvas: HTMLCanvasElement,\n): Promise<Blob> => {\n return new Promise(\n (\n resolve,\n reject,\n ) => {\n try {\n if (\n typeof canvas.toBlob !==\n 'function'\n ) {\n reject(\n new Error(\n 'Canvas.toBlob is not supported',\n ),\n );\n\n return;\n }\n\n canvas.toBlob(\n (\n blob,\n ) => {\n if (!blob) {\n reject(\n new Error(\n 'Failed to create PNG blob',\n ),\n );\n\n return;\n }\n\n resolve(blob);\n },\n 'image/png',\n );\n } catch (error) {\n reject(error);\n }\n },\n );\n};","import type {\n ExportOptions,\n DiagramData,\n GetTableHeight,\n} from './types';\n\nimport {\n calculateSvgDimensions,\n} from './calculateDimensions';\n\nimport {\n renderHighQuality,\n} from './renderHighQuality';\n\nimport {\n serializeSvg,\n createSvgUrl,\n prepareSvgForExport,\n} from './utils/serializer';\n\nimport {\n downloadBlob,\n generateFileName,\n canvasToBlob,\n} from './utils/download';\n\nexport interface ExportDiagramParams {\n svgRef: React.RefObject<SVGSVGElement>;\n\n diagramData?:\n | DiagramData\n | null;\n\n getTableHeight?:\n | GetTableHeight;\n\n projectName?: string;\n\n setExportAllTables?: (\n value: boolean,\n ) => void;\n\n showToast?: (\n message: string,\n type?: string,\n ) => void;\n}\n\n/**\n * Exports the complete SVG diagram regardless of the\n * visible viewport.\n *\n * The export process is:\n *\n * 1. Render all diagram content.\n * 2. Calculate the real SVG content bounds.\n * 3. Create an independent SVG clone.\n * 4. Set a viewBox covering the complete content.\n * 5. Apply the requested background.\n * 6. Convert the SVG to PNG when requested.\n *\n * Background examples:\n *\n * - 'white'\n * - 'transparent'\n * - '#C300FF'\n * - 'rgb(195, 0, 255)'\n * - 'rgba(195, 0, 255, 0.5)'\n */\nexport const exportDiagram = async (\n params: ExportDiagramParams,\n options: ExportOptions = {},\n): Promise<void> => {\n const {\n svgRef,\n diagramData,\n getTableHeight,\n projectName,\n setExportAllTables,\n showToast,\n } = params;\n\n const {\n format = 'png',\n background = 'white',\n scale = 2,\n margin = 100,\n fileName,\n onProgress,\n } = options;\n\n if (!svgRef.current) {\n onProgress?.({\n type: 'error',\n message:\n 'SVG reference not available',\n });\n\n return;\n }\n\n let objectUrl:\n string | null = null;\n\n try {\n onProgress?.({\n type: 'preparing',\n });\n\n showToast?.(\n 'Preparing export...',\n 'info',\n );\n\n /*\n * Tell the consuming application to render\n * all currently virtualized/cullable content.\n */\n setExportAllTables?.(\n true,\n );\n\n /*\n * Give React time to render the complete diagram.\n */\n await new Promise<void>(\n (resolve) => {\n setTimeout(\n resolve,\n 300,\n );\n },\n );\n\n onProgress?.({\n type: 'rendering',\n });\n\n const sourceSvg =\n svgRef.current;\n\n /*\n * Calculate the real SVG bounds.\n *\n * We intentionally do not use calculateDimensions()\n * because SVG content may extend beyond the viewport.\n */\n const dimensions =\n calculateSvgDimensions(\n sourceSvg,\n margin,\n );\n\n const {\n x,\n y,\n width,\n height,\n } = dimensions;\n\n /*\n * Prepare a completely independent SVG clone.\n *\n * The background is applied here for SVG exports\n * and is also preserved when converting to PNG.\n */\n const exportSvg =\n prepareSvgForExport(\n sourceSvg,\n width,\n height,\n background,\n margin,\n x,\n y,\n );\n\n onProgress?.({\n type: 'serializing',\n });\n\n /*\n * Serialize the prepared SVG.\n */\n const svgMarkup =\n serializeSvg(\n exportSvg,\n );\n\n /*\n * SVG export does not require canvas.\n */\n if (\n format === 'svg'\n ) {\n const blob =\n new Blob(\n [svgMarkup],\n {\n type:\n 'image/svg+xml;charset=utf-8',\n },\n );\n\n const finalFileName =\n fileName ||\n generateFileName(\n projectName,\n 'svg',\n );\n\n downloadBlob(\n blob,\n finalFileName,\n );\n\n onProgress?.({\n type: 'complete',\n });\n\n showToast?.(\n 'Export completed successfully',\n 'success',\n );\n\n return;\n }\n\n /*\n * Create an object URL for the SVG.\n */\n const image =\n new Image();\n\n objectUrl =\n createSvgUrl(\n svgMarkup,\n );\n\n await new Promise<void>(\n (\n resolve,\n reject,\n ) => {\n image.onload =\n () => {\n resolve();\n };\n\n image.onerror =\n () => {\n reject(\n new Error(\n 'Failed to load SVG image',\n ),\n );\n };\n\n image.src =\n objectUrl!;\n },\n );\n\n /*\n * Render the complete SVG into a high-quality canvas.\n *\n * The same background is passed to the canvas renderer,\n * allowing custom CSS colors for PNG exports.\n */\n const canvas =\n await renderHighQuality(\n image,\n width,\n height,\n scale,\n background,\n );\n\n onProgress?.({\n type: 'downloading',\n });\n\n /*\n * Convert the canvas to PNG.\n */\n const blob =\n await canvasToBlob(\n canvas,\n );\n\n const finalFileName =\n fileName ||\n generateFileName(\n projectName,\n 'png',\n );\n\n downloadBlob(\n blob,\n finalFileName,\n );\n\n onProgress?.({\n type: 'complete',\n });\n\n showToast?.(\n 'Export completed successfully',\n 'success',\n );\n } catch (error) {\n console.error(\n 'Export error:',\n error,\n );\n\n onProgress?.({\n type: 'error',\n message:\n 'Export failed',\n });\n\n showToast?.(\n 'Failed to export file',\n 'error',\n );\n } finally {\n /*\n * Always restore the normal rendering state.\n */\n setExportAllTables?.(\n false,\n );\n\n /*\n * Always release the object URL.\n */\n if (objectUrl) {\n URL.revokeObjectURL(\n objectUrl,\n );\n }\n }\n};"],"mappings":"AAAA,OACE,YAAAA,EACA,eAAAC,MACK,QCoBA,IAAMC,EAAsB,CACjCC,EAIAC,EAEAC,EAAiB,MACF,CACf,GACE,CAACF,GAAa,QAAQ,OAEtB,MAAO,CACL,MACE,IACAE,EAAS,EAEX,OACE,IACAA,EAAS,EAEX,EACE,CAACA,EAEH,EACE,CAACA,CACL,EAGF,IAAMC,EACJ,KAAK,IACH,GAAGH,EAAY,OAAO,IACnBI,GACCA,EAAM,CACV,CACF,EAEIC,EACJ,KAAK,IACH,GAAGL,EAAY,OAAO,IACnBI,GACCA,EAAM,CACV,CACF,EAEIE,EACJ,KAAK,IACH,GAAGN,EAAY,OAAO,IACnBI,GACCA,EAAM,EACN,IACA,EACJ,CACF,EAEIG,EACJ,KAAK,IACH,GAAGP,EAAY,OAAO,IACnBI,GACCA,EAAM,EACNH,EACEG,EAAM,MACR,EACA,EACJ,CACF,EAEF,MAAO,CACL,EACED,EACAD,EAEF,EACEG,EACAH,EAEF,MACE,KAAK,IACH,IACAI,EACEH,EACAD,EAAS,CACb,EAEF,OACE,KAAK,IACH,IACAK,EACEF,EACAH,EAAS,CACb,CACJ,CACF,EAgBaM,EACXC,GACsB,CACtB,IAAMC,EACJ,MAAM,KACJD,EAAI,iBACF,2EACF,CACF,EAEEN,EACF,IAEEE,EACF,IAEEC,EACF,KAEEC,EACF,KAEII,EACJ,CACEC,EACAC,EACAC,EACAC,IACG,CAED,CAAC,OAAO,SACNH,CACF,GACA,CAAC,OAAO,SACNC,CACF,GACA,CAAC,OAAO,SACNC,CACF,GACA,CAAC,OAAO,SACNC,CACF,GAMAD,EAAQ,GACRC,EAAS,IAKXZ,EACE,KAAK,IACHA,EACAS,CACF,EAEFP,EACE,KAAK,IACHA,EACAQ,CACF,EAEFP,EACE,KAAK,IACHA,EACAM,EAAIE,CACN,EAEFP,EACE,KAAK,IACHA,EACAM,EAAIE,CACN,EACJ,EAEF,QACQC,KAAWN,EAKjB,GACE,EAAAM,EAAQ,QACN,2BACF,GACAA,EAAQ,QACN,2BACF,GAKF,GAAI,CACF,IAAMC,EACJD,EAAQ,QAAQ,EAElBL,EACEM,EAAK,EACLA,EAAK,EACLA,EAAK,MACLA,EAAK,MACP,CACF,MAAQ,CAKR,CAGF,MACE,CAAC,OAAO,SACNd,CACF,GACA,CAAC,OAAO,SACNE,CACF,GACA,CAAC,OAAO,SACNC,CACF,GACA,CAAC,OAAO,SACNC,CACF,EAEO,KAGF,CACL,EACEJ,EAEF,EACEE,EAEF,MACE,KAAK,IACH,EACAC,EAAOH,CACT,EAEF,OACE,KAAK,IACH,EACAI,EAAOF,CACT,CACJ,CACF,EAUaa,EAAyB,CACpCT,EACAP,EAAiB,MACF,CACf,IAAMiB,EACJX,EACEC,CACF,EAEF,GAAIU,EACF,MAAO,CACL,EACEA,EAAO,EACPjB,EAEF,EACEiB,EAAO,EACPjB,EAEF,MACEiB,EAAO,MACPjB,EAAS,EAEX,OACEiB,EAAO,OACPjB,EAAS,CACb,EAMF,IAAMkB,EACJX,EAAI,SAAS,QAEf,GACEW,GACA,OAAO,SACLA,EAAQ,KACV,GACA,OAAO,SACLA,EAAQ,MACV,GACAA,EAAQ,MAAQ,GAChBA,EAAQ,OAAS,EAEjB,MAAO,CACL,EACEA,EAAQ,EACRlB,EAEF,EACEkB,EAAQ,EACRlB,EAEF,MACEkB,EAAQ,MACRlB,EAAS,EAEX,OACEkB,EAAQ,OACRlB,EAAS,CACb,EAMF,IAAMY,EACJ,OACEL,EAAI,aACF,OACF,CACF,GACAA,EAAI,aACJ,EAEIM,EACJ,OACEN,EAAI,aACF,QACF,CACF,GACAA,EAAI,cACJ,EAEF,MAAO,CACL,EACE,CAACP,EAEH,EACE,CAACA,EAEH,MACEY,EACAZ,EAAS,EAEX,OACEa,EACAb,EAAS,CACb,CACF,EAOamB,EACXZ,GACe,CACf,IAAMa,EACJb,EAAI,eACJA,EAAI,QACF,gBACF,GACAA,EAAI,QACF,kBACF,EAEEK,EACF,OACEL,EAAI,aACF,OACF,CACF,GACAA,EAAI,aACJ,EAEEM,EACF,OACEN,EAAI,aACF,QACF,CACF,GACAA,EAAI,cACJ,EAEF,OACEa,IAEAR,EACE,KAAK,IACHA,EACAQ,EAAgB,WAClB,EAEFP,EACE,KAAK,IACHA,EACAO,EAAgB,YAClB,GAGG,CACL,EAAG,EACH,EAAG,EACH,MAAAR,EACA,OAAAC,CACF,CACF,EClbO,IAAMQ,EAAoB,MAC/BC,EACAC,EACAC,EACAC,EAAgB,EAChBC,EAAqB,gBACU,CAG/B,IAAMC,EACJ,KAAK,IAAI,EAAGJ,CAAK,EAEbK,EACJ,KAAK,IAAI,EAAGJ,CAAM,EAEdK,EACJ,KAAK,IAAI,GAAKJ,CAAK,EAEfK,EAAiB,KAAK,IAC1BD,EACA,MACE,KAAK,IACHF,EACAC,EACA,CACF,CACJ,EAEMG,EACJ,SAAS,cACP,QACF,EAEFA,EAAO,MAAQ,KAAK,IAClB,EACA,KAAK,MACHJ,EACEG,CACJ,CACF,EAEAC,EAAO,OAAS,KAAK,IACnB,EACA,KAAK,MACHH,EACEE,CACJ,CACF,EAWA,IAAME,EACJD,EAAO,WACL,KACA,CACE,MAAO,EACT,CACF,EAEF,GAAI,CAACC,EACH,MAAM,IAAI,MACR,oCACF,EAGF,OAAAA,EAAQ,sBACN,GAEFA,EAAQ,sBACN,OAYAN,IAAe,eAEfM,EAAQ,UACNN,EAEFM,EAAQ,SACN,EACA,EACAD,EAAO,MACPA,EAAO,MACT,GAEAC,EAAQ,UACN,EACA,EACAD,EAAO,MACPA,EAAO,MACT,EAOFC,EAAQ,UACNV,EACA,EACA,EACAK,EACEG,EACFF,EACEE,CACJ,EAEOC,CACT,EChIO,IAAME,EACXC,GACW,CACX,IAAMC,EACJ,IAAI,cAEAC,EACJ,OAAO,SAAa,IAChB,iBACE,SAAS,eACX,EACA,KAEFC,EACFF,EAAW,kBACTD,CACF,EAEF,OAAIE,IACFC,EAASA,EAAO,QACd,oBACA,CACEC,EACAC,IAGEH,EACG,iBACC,KAAKG,CAAI,EACX,GACE,KAAK,GAGT,SAASA,CAAI,GAEnB,GAOCF,EAAO,SACN,oCACF,IAEAA,EAASA,EAAO,QACd,OACA,yCACF,GAGKA,CACT,EAKaG,EACXC,GAEO,IAAI,gBACT,IAAI,KACF,CAACA,CAAS,EACV,CACE,KACE,6BACJ,CACF,CACF,EAcWC,EAAsB,CACjCC,EACAC,EACAC,EACAC,EAAqB,cACrBC,EAAiB,EACjBC,EAAkB,EAClBC,EAAkB,IACA,CAClB,IAAMC,EACJP,EAAU,UACR,EACF,EAuDF,GA7CEO,EAAM,iBACJ,2BACF,EAEM,QACLC,GAAS,CACRA,EAAK,OAAO,CACd,CACF,EAKAD,EAAM,aACJ,QACA,OAAON,CAAK,CACd,EAEAM,EAAM,aACJ,SACA,OAAOL,CAAM,CACf,EAEAK,EAAM,aACJ,UACA,GAAGF,CAAO,IAAIC,CAAO,IAAIL,CAAK,IAAIC,CAAM,EAC1C,EAKAK,EAAM,aACJ,sBACA,eACF,EAYEJ,IAAe,cACf,CACA,IAAMM,EACJ,SAAS,gBACP,6BACA,MACF,EAEFA,EAAe,aACb,IACA,OAAOJ,CAAO,CAChB,EAEAI,EAAe,aACb,IACA,OAAOH,CAAO,CAChB,EAEAG,EAAe,aACb,QACA,OAAOR,CAAK,CACd,EAEAQ,EAAe,aACb,SACA,OAAOP,CAAM,CACf,EAEAO,EAAe,aACb,OACAN,CACF,EAKAI,EAAM,aACJE,EACAF,EAAM,UACR,CACF,CAQA,OAAOA,CACT,EChNO,IAAMG,EAAe,CAC1BC,EACAC,IACS,CACT,IAAMC,EACJ,IAAI,gBACFF,CACF,EAEIG,EACJ,SAAS,cACP,GACF,EAEFA,EAAK,KAAOD,EACZC,EAAK,SAAWF,EAEhB,GAAI,CACF,SAAS,MAAM,YACbE,CACF,EAEAA,EAAK,MAAM,EAEX,SAAS,MAAM,YACbA,CACF,CACF,MAAQ,CACNA,EAAK,MAAM,CACb,CAMA,WAAW,IAAM,CACf,IAAI,gBACFD,CACF,CACF,EAAG,GAAG,CACR,EAKaE,EAAmB,CAC9BC,EAGAC,IAmBO,IAbHD,GACA,WAEC,QACC,OACA,GACF,EACC,YAAY,EACZ,QACC,eACA,EACF,CAEc,IAAIC,CAAM,GAMjBC,EACXC,GAEO,IAAI,QACT,CACEC,EACAC,IACG,CACH,GAAI,CACF,GACE,OAAOF,EAAO,QACd,WACA,CACAE,EACE,IAAI,MACF,gCACF,CACF,EAEA,MACF,CAEAF,EAAO,OAEHR,GACG,CACH,GAAI,CAACA,EAAM,CACTU,EACE,IAAI,MACF,2BACF,CACF,EAEA,MACF,CAEAD,EAAQT,CAAI,CACd,EACA,WACF,CACF,OAASW,EAAO,CACdD,EAAOC,CAAK,CACd,CACF,CACF,ECpDK,IAAMC,EAAgB,MAC3BC,EACAC,EAAyB,CAAC,IACR,CAClB,GAAM,CACJ,OAAAC,EACA,YAAAC,EACA,eAAAC,EACA,YAAAC,EACA,mBAAAC,EACA,UAAAC,CACF,EAAIP,EAEE,CACJ,OAAAQ,EAAS,MACT,WAAAC,EAAa,QACb,MAAAC,EAAQ,EACR,OAAAC,EAAS,IACT,SAAAC,EACA,WAAAC,CACF,EAAIZ,EAEJ,GAAI,CAACC,EAAO,QAAS,CACnBW,IAAa,CACX,KAAM,QACN,QACE,6BACJ,CAAC,EAED,MACF,CAEA,IAAIC,EACc,KAElB,GAAI,CACFD,IAAa,CACX,KAAM,WACR,CAAC,EAEDN,IACE,sBACA,MACF,EAMAD,IACE,EACF,EAKA,MAAM,IAAI,QACPS,GAAY,CACX,WACEA,EACA,GACF,CACF,CACF,EAEAF,IAAa,CACX,KAAM,WACR,CAAC,EAED,IAAMG,EACJd,EAAO,QAQHe,EACJC,EACEF,EACAL,CACF,EAEI,CACJ,EAAAQ,EACA,EAAAC,EACA,MAAAC,EACA,OAAAC,CACF,EAAIL,EAQEM,EACJC,EACER,EACAK,EACAC,EACAb,EACAE,EACAQ,EACAC,CACF,EAEFP,IAAa,CACX,KAAM,aACR,CAAC,EAKD,IAAMY,EACJC,EACEH,CACF,EAKF,GACEf,IAAW,MACX,CACA,IAAMmB,EACJ,IAAI,KACF,CAACF,CAAS,EACV,CACE,KACE,6BACJ,CACF,EAEIG,EACJhB,GACAiB,EACExB,EACA,KACF,EAEFyB,EACEH,EACAC,CACF,EAEAf,IAAa,CACX,KAAM,UACR,CAAC,EAEDN,IACE,gCACA,SACF,EAEA,MACF,CAKA,IAAMwB,EACJ,IAAI,MAENjB,EACEkB,EACEP,CACF,EAEF,MAAM,IAAI,QACR,CACEV,EACAkB,IACG,CACHF,EAAM,OACJ,IAAM,CACJhB,EAAQ,CACV,EAEFgB,EAAM,QACJ,IAAM,CACJE,EACE,IAAI,MACF,0BACF,CACF,CACF,EAEFF,EAAM,IACJjB,CACJ,CACF,EAQA,IAAMoB,EACJ,MAAMC,EACJJ,EACAV,EACAC,EACAZ,EACAD,CACF,EAEFI,IAAa,CACX,KAAM,aACR,CAAC,EAKD,IAAMc,EACJ,MAAMS,EACJF,CACF,EAEIN,EACJhB,GACAiB,EACExB,EACA,KACF,EAEFyB,EACEH,EACAC,CACF,EAEAf,IAAa,CACX,KAAM,UACR,CAAC,EAEDN,IACE,gCACA,SACF,CACF,OAAS8B,EAAO,CACd,QAAQ,MACN,gBACAA,CACF,EAEAxB,IAAa,CACX,KAAM,QACN,QACE,eACJ,CAAC,EAEDN,IACE,wBACA,OACF,CACF,QAAE,CAIAD,IACE,EACF,EAKIQ,GACF,IAAI,gBACFA,CACF,CAEJ,CACF,ELnTO,IAAMwB,EAAmB,CAAC,CAC/B,OAAAC,EACA,YAAAC,EACA,eAAAC,EACA,YAAAC,CACF,IAOO,CACL,GAAM,CACJC,EACAC,CACF,EAAIC,EAAS,EAAK,EAEZ,CACJC,EACAC,CACF,EAAIF,EAAS,EAAK,EAEZG,EACJC,EACE,MACEC,EAAyB,CAAC,IACvB,CACHN,EACE,EACF,EAEA,GAAI,CACF,MAAMO,EACJ,CACE,OAAAZ,EACA,YAAAC,EACA,eAAAC,EACA,YAAAC,EACA,mBAAAK,CACF,EACAG,CACF,CACF,QAAE,CACAN,EACE,EACF,EAEAG,EACE,EACF,CACF,CACF,EACA,CACER,EACAC,EACAC,EACAC,CACF,CACF,EAEF,MAAO,CACL,YAAAC,EAEA,cACEK,EAEF,gBAAAF,EAEA,mBAAAC,CACF,CACF","names":["useState","useCallback","calculateDimensions","diagramData","getTableHeight","margin","minX","table","minY","maxX","maxY","getSvgContentBounds","svg","elements","updateBounds","x","y","width","height","element","bbox","calculateSvgDimensions","bounds","viewBox","getScrollDimensions","scrollContainer","renderHighQuality","image","width","height","scale","background","safeWidth","safeHeight","safeScale","effectiveScale","canvas","context","serializeSvg","svg","serializer","styles","markup","_match","name","createSvgUrl","svgMarkup","prepareSvgForExport","sourceSvg","width","height","background","margin","sourceX","sourceY","clone","node","backgroundRect","downloadBlob","blob","fileName","url","link","generateFileName","projectName","format","canvasToBlob","canvas","resolve","reject","error","exportDiagram","params","options","svgRef","diagramData","getTableHeight","projectName","setExportAllTables","showToast","format","background","scale","margin","fileName","onProgress","objectUrl","resolve","sourceSvg","dimensions","calculateSvgDimensions","x","y","width","height","exportSvg","prepareSvgForExport","svgMarkup","serializeSvg","blob","finalFileName","generateFileName","downloadBlob","image","createSvgUrl","reject","canvas","renderHighQuality","canvasToBlob","error","useDiagramExport","svgRef","diagramData","getTableHeight","projectName","isExporting","setIsExporting","useState","exportAllTables","setExportAllTables","handleExport","useCallback","options","exportDiagram"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ok-canvas-export",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "description": "Export complete SVG diagrams and canvases as high-quality images with full render pass to bypass viewport culling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",