@djangocfg/ui-tools 2.1.157 → 2.1.159
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 +164 -3
- package/dist/{Mermaid.client-KMNEPBQJ.cjs → Mermaid.client-2TAFAXPW.cjs} +100 -143
- package/dist/Mermaid.client-2TAFAXPW.cjs.map +1 -0
- package/dist/{Mermaid.client-3WPNJ4DF.mjs → Mermaid.client-HG24D5KB.mjs} +100 -143
- package/dist/Mermaid.client-HG24D5KB.mjs.map +1 -0
- package/dist/index.cjs +4 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +4 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -6
- package/src/tools/Mermaid/Mermaid.client.tsx +40 -56
- package/src/tools/Mermaid/Mermaid.story.tsx +195 -110
- package/src/tools/Mermaid/builders/FlowDiagram/FlowDiagram.ts +96 -0
- package/src/tools/Mermaid/builders/FlowDiagram/functions/getEdges.ts +50 -0
- package/src/tools/Mermaid/builders/FlowDiagram/functions/getNodes.ts +43 -0
- package/src/tools/Mermaid/builders/FlowDiagram/functions/getStyles.ts +90 -0
- package/src/tools/Mermaid/builders/FlowDiagram/functions/index.ts +8 -0
- package/src/tools/Mermaid/builders/FlowDiagram/index.ts +16 -0
- package/src/tools/Mermaid/builders/FlowDiagram/types.ts +130 -0
- package/src/tools/Mermaid/builders/JourneyDiagram/JourneyDiagram.ts +88 -0
- package/src/tools/Mermaid/builders/JourneyDiagram/index.ts +12 -0
- package/src/tools/Mermaid/builders/JourneyDiagram/types.ts +48 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/SequenceDiagram.ts +123 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/functions/getActivations.ts +30 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/functions/getBlocks.ts +112 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/functions/getMessages.ts +85 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/functions/getNotes.ts +94 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/functions/index.ts +16 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/index.ts +17 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/types.ts +147 -0
- package/src/tools/Mermaid/builders/core/DiagramStore.ts +138 -0
- package/src/tools/Mermaid/builders/core/index.ts +8 -0
- package/src/tools/Mermaid/builders/core/sanitize.ts +78 -0
- package/src/tools/Mermaid/builders/core/theme.ts +42 -0
- package/src/tools/Mermaid/builders/core/types.ts +183 -0
- package/src/tools/Mermaid/builders/index.ts +96 -0
- package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +78 -54
- package/src/tools/Mermaid/index.tsx +51 -12
- package/dist/Mermaid.client-3WPNJ4DF.mjs.map +0 -1
- package/dist/Mermaid.client-KMNEPBQJ.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ This package contains heavy components that are loaded lazily to keep your initi
|
|
|
28
28
|
|------|-------------|-------------|
|
|
29
29
|
| `Gallery` | ~50KB | Image/video gallery with carousel, grid, lightbox |
|
|
30
30
|
| `Map` | ~800KB | MapLibre GL maps with markers, clusters, layers |
|
|
31
|
-
| `Mermaid` | ~800KB | Diagram rendering |
|
|
31
|
+
| `Mermaid` | ~800KB | Diagram rendering with declarative builders |
|
|
32
32
|
| `PrettyCode` | ~500KB | Code syntax highlighting |
|
|
33
33
|
| `OpenapiViewer` | ~400KB | OpenAPI schema viewer & playground |
|
|
34
34
|
| `JsonForm` | ~300KB | JSON Schema form generator |
|
|
@@ -49,8 +49,21 @@ import { Gallery, GalleryLightbox } from '@djangocfg/ui-tools/gallery';
|
|
|
49
49
|
|
|
50
50
|
// Only loads Map (~800KB)
|
|
51
51
|
import { MapContainer, MapMarker } from '@djangocfg/ui-tools/map';
|
|
52
|
+
|
|
53
|
+
// Mermaid builders (no heavy Mermaid dependency until render)
|
|
54
|
+
import { FlowDiagram, SequenceDiagram, JourneyDiagram } from '@djangocfg/ui-tools/mermaid';
|
|
52
55
|
```
|
|
53
56
|
|
|
57
|
+
## Exports
|
|
58
|
+
|
|
59
|
+
| Path | Content |
|
|
60
|
+
|------|---------|
|
|
61
|
+
| `@djangocfg/ui-tools` | All tools with lazy loading |
|
|
62
|
+
| `@djangocfg/ui-tools/gallery` | Gallery components & hooks |
|
|
63
|
+
| `@djangocfg/ui-tools/map` | Map components & utilities |
|
|
64
|
+
| `@djangocfg/ui-tools/mermaid` | Mermaid component & declarative builders |
|
|
65
|
+
| `@djangocfg/ui-tools/styles` | CSS styles |
|
|
66
|
+
|
|
54
67
|
## Gallery
|
|
55
68
|
|
|
56
69
|
Full-featured image/video gallery with carousel, grid view, and fullscreen lightbox.
|
|
@@ -236,10 +249,14 @@ import { HybridAudioPlayer } from '@djangocfg/ui-tools';
|
|
|
236
249
|
|
|
237
250
|
## Mermaid Diagrams
|
|
238
251
|
|
|
252
|
+
Render Mermaid diagrams with fullscreen zoom support and type-safe declarative builders.
|
|
253
|
+
|
|
254
|
+
### Basic Usage
|
|
255
|
+
|
|
239
256
|
```tsx
|
|
240
|
-
import {
|
|
257
|
+
import { LazyMermaid } from '@djangocfg/ui-tools';
|
|
241
258
|
|
|
242
|
-
<
|
|
259
|
+
<LazyMermaid chart={`
|
|
243
260
|
graph TD
|
|
244
261
|
A[Start] --> B{Decision}
|
|
245
262
|
B -->|Yes| C[Action]
|
|
@@ -247,6 +264,150 @@ import { Mermaid } from '@djangocfg/ui-tools';
|
|
|
247
264
|
`} />
|
|
248
265
|
```
|
|
249
266
|
|
|
267
|
+
### Props
|
|
268
|
+
|
|
269
|
+
| Prop | Type | Default | Description |
|
|
270
|
+
|------|------|---------|-------------|
|
|
271
|
+
| `chart` | `string` | - | Mermaid diagram syntax |
|
|
272
|
+
| `className` | `string` | `''` | Additional CSS classes |
|
|
273
|
+
| `isCompact` | `boolean` | `false` | Compact rendering mode |
|
|
274
|
+
| `fullscreen` | `boolean` | `true` | Enable fullscreen button with pinch-zoom |
|
|
275
|
+
|
|
276
|
+
### Declarative Builders
|
|
277
|
+
|
|
278
|
+
Type-safe builders for creating Mermaid diagrams programmatically:
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
import { LazyMermaid } from '@djangocfg/ui-tools';
|
|
282
|
+
import {
|
|
283
|
+
FlowDiagram,
|
|
284
|
+
SequenceDiagram,
|
|
285
|
+
JourneyDiagram,
|
|
286
|
+
useStylePresets,
|
|
287
|
+
useBoxColors,
|
|
288
|
+
} from '@djangocfg/ui-tools/mermaid';
|
|
289
|
+
|
|
290
|
+
function MyDiagram() {
|
|
291
|
+
const presets = useStylePresets();
|
|
292
|
+
const boxes = useBoxColors();
|
|
293
|
+
|
|
294
|
+
// Flow diagram with type-safe nodes
|
|
295
|
+
type Nodes = 'start' | 'check' | 'success' | 'finish';
|
|
296
|
+
const flow = FlowDiagram<Nodes>({ direction: 'TB' });
|
|
297
|
+
|
|
298
|
+
flow.node('start').rect('Start');
|
|
299
|
+
flow.node('check').rhombus('Is it working?');
|
|
300
|
+
flow.node('success').rect('Great!');
|
|
301
|
+
flow.node('finish').stadium('End');
|
|
302
|
+
|
|
303
|
+
flow.edge('start').to('check').solid();
|
|
304
|
+
flow.edge('check').to('success').solid('Yes');
|
|
305
|
+
flow.edge('check').to('finish').solid('No');
|
|
306
|
+
flow.edge('success').to('finish').solid();
|
|
307
|
+
|
|
308
|
+
// Apply theme-aware styles
|
|
309
|
+
flow.style.define('success', presets.success);
|
|
310
|
+
flow.style.apply('success', 'success', 'finish');
|
|
311
|
+
|
|
312
|
+
return <LazyMermaid chart={flow.toString()} />;
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Available Builders
|
|
317
|
+
|
|
318
|
+
| Builder | Description |
|
|
319
|
+
|---------|-------------|
|
|
320
|
+
| `FlowDiagram<Nodes>` | Flowcharts with nodes, edges, subgraphs |
|
|
321
|
+
| `SequenceDiagram` | Sequence diagrams with participants, messages |
|
|
322
|
+
| `JourneyDiagram` | User journey diagrams with sections, tasks |
|
|
323
|
+
|
|
324
|
+
### Theme Hooks
|
|
325
|
+
|
|
326
|
+
| Hook | Description |
|
|
327
|
+
|------|-------------|
|
|
328
|
+
| `useThemePalette()` | Full palette from CSS variables |
|
|
329
|
+
| `useStylePresets()` | Pre-built style configs (success, warning, etc.) |
|
|
330
|
+
| `useBoxColors()` | Colors for sequence diagram boxes |
|
|
331
|
+
|
|
332
|
+
### FlowDiagram API
|
|
333
|
+
|
|
334
|
+
```tsx
|
|
335
|
+
const flow = FlowDiagram<'A' | 'B' | 'C'>({ direction: 'TB' });
|
|
336
|
+
|
|
337
|
+
// Nodes
|
|
338
|
+
flow.node('A').rect('Rectangle');
|
|
339
|
+
flow.node('B').round('Rounded');
|
|
340
|
+
flow.node('C').rhombus('Diamond');
|
|
341
|
+
flow.node('D').stadium('Stadium');
|
|
342
|
+
flow.node('E').cylinder('Database');
|
|
343
|
+
flow.node('F').hexagon('Hexagon');
|
|
344
|
+
|
|
345
|
+
// Edges
|
|
346
|
+
flow.edge('A').to('B').solid();
|
|
347
|
+
flow.edge('A').to('B').solid('with label');
|
|
348
|
+
flow.edge('A').to('B').dotted();
|
|
349
|
+
flow.edge('A').to('B').thick();
|
|
350
|
+
|
|
351
|
+
// Subgraphs
|
|
352
|
+
flow.subgraph('Group Name', (sub) => {
|
|
353
|
+
sub.direction('LR');
|
|
354
|
+
sub.node('X').rect('Inside');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// Styles
|
|
358
|
+
flow.style.define('myStyle', { fill: '#f00', stroke: '#000' });
|
|
359
|
+
flow.style.apply('myStyle', 'A', 'B');
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### SequenceDiagram API
|
|
363
|
+
|
|
364
|
+
```tsx
|
|
365
|
+
const { d, rect, alt, loop, toString } = SequenceDiagram({
|
|
366
|
+
User: 'actor',
|
|
367
|
+
App: 'participant',
|
|
368
|
+
API: 'participant',
|
|
369
|
+
}, { autoNumber: true });
|
|
370
|
+
|
|
371
|
+
// Messages
|
|
372
|
+
d.User.sync.App.msg('Click button');
|
|
373
|
+
d.App.async.API.msg('Fetch data');
|
|
374
|
+
d.API.asyncReply.App.msg('Response');
|
|
375
|
+
|
|
376
|
+
// Blocks
|
|
377
|
+
rect('#rgba(0,100,200,0.2)', () => {
|
|
378
|
+
d.User.sync.App.msg('Login');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
alt('Success', () => {
|
|
382
|
+
d.App.syncReply.User.msg('Welcome!');
|
|
383
|
+
}).else('Failure', () => {
|
|
384
|
+
d.App.syncReply.User.msg('Error');
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
loop('Every 5s', () => {
|
|
388
|
+
d.App.async.API.msg('Heartbeat');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
return toString();
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### JourneyDiagram API
|
|
395
|
+
|
|
396
|
+
```tsx
|
|
397
|
+
const journey = JourneyDiagram({ title: 'User Onboarding' });
|
|
398
|
+
|
|
399
|
+
journey.section('Discovery')
|
|
400
|
+
.task('Visit landing page', 5, 'User')
|
|
401
|
+
.task('Read features', 4, 'User');
|
|
402
|
+
|
|
403
|
+
journey.section('Sign Up')
|
|
404
|
+
.task('Click Sign Up', 5, 'User')
|
|
405
|
+
.task('Fill form', 2, 'User')
|
|
406
|
+
.task('Verify email', 4, ['User', 'System']);
|
|
407
|
+
|
|
408
|
+
return journey.toString();
|
|
409
|
+
```
|
|
410
|
+
|
|
250
411
|
## Code Highlighting
|
|
251
412
|
|
|
252
413
|
```tsx
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chunkWGEGR3DF_cjs = require('./chunk-WGEGR3DF.cjs');
|
|
4
|
-
var
|
|
5
|
-
var
|
|
4
|
+
var lucideReact = require('lucide-react');
|
|
5
|
+
var components = require('@djangocfg/ui-core/components');
|
|
6
6
|
var hooks = require('@djangocfg/ui-core/hooks');
|
|
7
|
+
var react = require('react');
|
|
7
8
|
var reactDom = require('react-dom');
|
|
9
|
+
var reactZoomPanPinch = require('react-zoom-pan-pinch');
|
|
8
10
|
var jsxRuntime = require('react/jsx-runtime');
|
|
9
11
|
var mermaid = require('mermaid');
|
|
10
12
|
|
|
@@ -24,73 +26,24 @@ var applyMermaidTextColors = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((container
|
|
|
24
26
|
});
|
|
25
27
|
}
|
|
26
28
|
}, "applyMermaidTextColors");
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const handleCopy = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(async () => {
|
|
34
|
-
await navigator.clipboard.writeText(chart);
|
|
35
|
-
setCopied(true);
|
|
36
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
37
|
-
}, "handleCopy");
|
|
38
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-full", children: [
|
|
39
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-b border-border px-4", children: [
|
|
40
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex", children: [
|
|
41
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
42
|
-
"button",
|
|
43
|
-
{
|
|
44
|
-
onClick: () => setActiveTab("preview"),
|
|
45
|
-
className: `px-4 py-3 text-sm font-medium transition-colors relative ${activeTab === "preview" ? "text-foreground" : "text-muted-foreground hover:text-foreground"}`,
|
|
46
|
-
children: [
|
|
47
|
-
"Preview",
|
|
48
|
-
activeTab === "preview" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-0 left-0 right-0 h-0.5 bg-primary" })
|
|
49
|
-
]
|
|
50
|
-
}
|
|
51
|
-
),
|
|
52
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
53
|
-
"button",
|
|
54
|
-
{
|
|
55
|
-
onClick: () => setActiveTab("code"),
|
|
56
|
-
className: `px-4 py-3 text-sm font-medium transition-colors relative ${activeTab === "code" ? "text-foreground" : "text-muted-foreground hover:text-foreground"}`,
|
|
57
|
-
children: [
|
|
58
|
-
"Code",
|
|
59
|
-
activeTab === "code" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-0 left-0 right-0 h-0.5 bg-primary" })
|
|
60
|
-
]
|
|
61
|
-
}
|
|
62
|
-
)
|
|
63
|
-
] }),
|
|
64
|
-
activeTab === "code" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
65
|
-
"button",
|
|
66
|
-
{
|
|
67
|
-
onClick: handleCopy,
|
|
68
|
-
className: "flex items-center gap-2 px-3 py-1.5 text-xs font-medium bg-primary/10 hover:bg-primary/20 text-primary rounded transition-colors",
|
|
69
|
-
children: copied ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
70
|
-
/* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }),
|
|
71
|
-
"Copied!"
|
|
72
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
73
|
-
/* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" }) }),
|
|
74
|
-
"Copy"
|
|
75
|
-
] })
|
|
76
|
-
}
|
|
77
|
-
)
|
|
78
|
-
] }),
|
|
79
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: activeTab === "preview" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6 flex items-center justify-center min-h-full", children: renderPreview() }) : /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "p-6 text-sm font-mono text-foreground bg-muted/30 h-full overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: chart }) }) })
|
|
29
|
+
function ZoomControls() {
|
|
30
|
+
const { zoomIn, zoomOut, resetTransform } = reactZoomPanPinch.useControls();
|
|
31
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2 z-10", children: [
|
|
32
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.Button, { variant: "secondary", size: "icon", onClick: () => zoomOut(), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZoomOut, { className: "h-4 w-4" }) }),
|
|
33
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.Button, { variant: "secondary", size: "icon", onClick: () => resetTransform(), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { className: "h-4 w-4" }) }),
|
|
34
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.Button, { variant: "secondary", size: "icon", onClick: () => zoomIn(), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZoomIn, { className: "h-4 w-4" }) })
|
|
80
35
|
] });
|
|
81
|
-
}
|
|
36
|
+
}
|
|
37
|
+
chunkWGEGR3DF_cjs.__name(ZoomControls, "ZoomControls");
|
|
82
38
|
var MermaidFullscreenModal = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({
|
|
83
39
|
isOpen,
|
|
84
40
|
svgContent,
|
|
85
41
|
isVertical,
|
|
86
42
|
theme,
|
|
87
|
-
chart,
|
|
88
43
|
fullscreenRef,
|
|
89
44
|
onClose,
|
|
90
45
|
onBackdropClick
|
|
91
46
|
}) => {
|
|
92
|
-
const t = i18n.useTypedT();
|
|
93
|
-
const diagramTitle = react.useMemo(() => t("tools.diagram.title"), [t]);
|
|
94
47
|
react.useEffect(() => {
|
|
95
48
|
if (isOpen && fullscreenRef.current) {
|
|
96
49
|
const getCSSVariable = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((variable) => {
|
|
@@ -100,52 +53,75 @@ var MermaidFullscreenModal = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({
|
|
|
100
53
|
}, "getCSSVariable");
|
|
101
54
|
const textColor = theme === "dark" ? getCSSVariable("--foreground") || "hsl(0 0% 90%)" : getCSSVariable("--foreground") || "hsl(222.2 84% 4.9%)";
|
|
102
55
|
applyMermaidTextColors(fullscreenRef.current, textColor);
|
|
103
|
-
const svgElement = fullscreenRef.current.querySelector("svg");
|
|
104
|
-
if (svgElement) {
|
|
105
|
-
svgElement.style.display = "block";
|
|
106
|
-
svgElement.style.height = "auto";
|
|
107
|
-
svgElement.style.maxWidth = "100%";
|
|
108
|
-
if (isVertical) {
|
|
109
|
-
svgElement.style.maxWidth = "600px";
|
|
110
|
-
svgElement.style.margin = "0 auto";
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
56
|
}
|
|
114
|
-
}, [isOpen, theme, isVertical, fullscreenRef]);
|
|
57
|
+
}, [isOpen, theme, isVertical, fullscreenRef, svgContent]);
|
|
58
|
+
react.useEffect(() => {
|
|
59
|
+
if (!isOpen) return;
|
|
60
|
+
const handleKeyDown = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((e) => {
|
|
61
|
+
if (e.key === "Escape") onClose();
|
|
62
|
+
}, "handleKeyDown");
|
|
63
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
64
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
65
|
+
}, [isOpen, onClose]);
|
|
115
66
|
if (!isOpen || typeof document === "undefined") return null;
|
|
116
67
|
return reactDom.createPortal(
|
|
117
|
-
/* @__PURE__ */ jsxRuntime.
|
|
68
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
118
69
|
"div",
|
|
119
70
|
{
|
|
120
|
-
className: "fixed inset-0 z-9999
|
|
121
|
-
style: { backgroundColor: "rgb(0 0 0 / 0.75)" },
|
|
71
|
+
className: "fixed inset-0 z-9999 bg-background/95 backdrop-blur-sm",
|
|
122
72
|
onClick: onBackdropClick,
|
|
123
|
-
children:
|
|
124
|
-
/* @__PURE__ */ jsxRuntime.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
"
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
MermaidCodeViewer,
|
|
73
|
+
children: [
|
|
74
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
75
|
+
components.Button,
|
|
76
|
+
{
|
|
77
|
+
variant: "ghost",
|
|
78
|
+
size: "icon",
|
|
79
|
+
className: "absolute top-4 right-4 z-10",
|
|
80
|
+
onClick: onClose,
|
|
81
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-5 w-5" })
|
|
82
|
+
}
|
|
83
|
+
),
|
|
84
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
85
|
+
reactZoomPanPinch.TransformWrapper,
|
|
137
86
|
{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
87
|
+
initialScale: 1,
|
|
88
|
+
minScale: 0.1,
|
|
89
|
+
maxScale: 10,
|
|
90
|
+
centerOnInit: true,
|
|
91
|
+
wheel: { step: 0.1 },
|
|
92
|
+
pinch: { step: 5 },
|
|
93
|
+
doubleClick: { mode: "reset" },
|
|
94
|
+
children: [
|
|
95
|
+
/* @__PURE__ */ jsxRuntime.jsx(ZoomControls, {}),
|
|
96
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
97
|
+
reactZoomPanPinch.TransformComponent,
|
|
98
|
+
{
|
|
99
|
+
wrapperStyle: {
|
|
100
|
+
width: "100%",
|
|
101
|
+
height: "100%"
|
|
102
|
+
},
|
|
103
|
+
contentStyle: {
|
|
104
|
+
width: "100%",
|
|
105
|
+
height: "100%",
|
|
106
|
+
display: "flex",
|
|
107
|
+
alignItems: "center",
|
|
108
|
+
justifyContent: "center"
|
|
109
|
+
},
|
|
110
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
111
|
+
"div",
|
|
112
|
+
{
|
|
113
|
+
ref: fullscreenRef,
|
|
114
|
+
className: "p-8",
|
|
115
|
+
dangerouslySetInnerHTML: { __html: svgContent },
|
|
116
|
+
onClick: (e) => e.stopPropagation()
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
]
|
|
146
122
|
}
|
|
147
|
-
)
|
|
148
|
-
]
|
|
123
|
+
)
|
|
124
|
+
]
|
|
149
125
|
}
|
|
150
126
|
),
|
|
151
127
|
document.body
|
|
@@ -415,14 +391,13 @@ function useMermaidRenderer({ chart, theme, isCompact = false }) {
|
|
|
415
391
|
};
|
|
416
392
|
}
|
|
417
393
|
chunkWGEGR3DF_cjs.__name(useMermaidRenderer, "useMermaidRenderer");
|
|
418
|
-
var Mermaid = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({
|
|
419
|
-
|
|
394
|
+
var Mermaid = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({
|
|
395
|
+
chart,
|
|
396
|
+
className = "",
|
|
397
|
+
isCompact = false,
|
|
398
|
+
fullscreen = true
|
|
399
|
+
}) => {
|
|
420
400
|
const theme = hooks.useResolvedTheme();
|
|
421
|
-
const labels = react.useMemo(() => ({
|
|
422
|
-
title: t("tools.diagram.title"),
|
|
423
|
-
clickToView: t("tools.diagram.clickToView"),
|
|
424
|
-
loading: t("ui.form.loading")
|
|
425
|
-
}), [t]);
|
|
426
401
|
const { mermaidRef, svgContent, isVertical, isRendering } = useMermaidRenderer({
|
|
427
402
|
chart,
|
|
428
403
|
theme,
|
|
@@ -435,13 +410,14 @@ var Mermaid = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({ chart, className = "",
|
|
|
435
410
|
closeFullscreen,
|
|
436
411
|
handleBackdropClick
|
|
437
412
|
} = useMermaidFullscreen();
|
|
438
|
-
const
|
|
439
|
-
|
|
413
|
+
const handleOpenFullscreen = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((e) => {
|
|
414
|
+
e.stopPropagation();
|
|
415
|
+
if (svgContent) {
|
|
440
416
|
openFullscreen();
|
|
441
417
|
}
|
|
442
|
-
}, "
|
|
443
|
-
|
|
444
|
-
|
|
418
|
+
}, "handleOpenFullscreen");
|
|
419
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
420
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${className}`, children: [
|
|
445
421
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
446
422
|
"div",
|
|
447
423
|
{
|
|
@@ -450,38 +426,19 @@ var Mermaid = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({ chart, className = "",
|
|
|
450
426
|
style: { isolation: "isolate" }
|
|
451
427
|
}
|
|
452
428
|
),
|
|
453
|
-
isRendering && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-6 w-6 border-b-2 border-primary" }) })
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
] }),
|
|
467
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative p-4 overflow-hidden", children: [
|
|
468
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
469
|
-
"div",
|
|
470
|
-
{
|
|
471
|
-
ref: mermaidRef,
|
|
472
|
-
className: "flex justify-center items-center min-h-[200px]",
|
|
473
|
-
style: { isolation: "isolate" }
|
|
474
|
-
}
|
|
475
|
-
),
|
|
476
|
-
isRendering && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-background/50 backdrop-blur-sm", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2", children: [
|
|
477
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-primary" }),
|
|
478
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: labels.loading })
|
|
479
|
-
] }) })
|
|
480
|
-
] })
|
|
481
|
-
]
|
|
482
|
-
}
|
|
483
|
-
),
|
|
484
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
429
|
+
isRendering && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-6 w-6 border-b-2 border-primary" }) }),
|
|
430
|
+
fullscreen && svgContent && !isRendering && /* @__PURE__ */ jsxRuntime.jsx(
|
|
431
|
+
components.Button,
|
|
432
|
+
{
|
|
433
|
+
variant: "secondary",
|
|
434
|
+
size: "icon",
|
|
435
|
+
className: "absolute bottom-2 right-2 h-8 w-8 opacity-60 hover:opacity-100 transition-opacity",
|
|
436
|
+
onClick: handleOpenFullscreen,
|
|
437
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-4 w-4" })
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
] }),
|
|
441
|
+
fullscreen && /* @__PURE__ */ jsxRuntime.jsx(
|
|
485
442
|
MermaidFullscreenModal,
|
|
486
443
|
{
|
|
487
444
|
isOpen: isFullscreen,
|
|
@@ -499,5 +456,5 @@ var Mermaid = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({ chart, className = "",
|
|
|
499
456
|
var Mermaid_client_default = Mermaid;
|
|
500
457
|
|
|
501
458
|
module.exports = Mermaid_client_default;
|
|
502
|
-
//# sourceMappingURL=Mermaid.client-
|
|
503
|
-
//# sourceMappingURL=Mermaid.client-
|
|
459
|
+
//# sourceMappingURL=Mermaid.client-2TAFAXPW.cjs.map
|
|
460
|
+
//# sourceMappingURL=Mermaid.client-2TAFAXPW.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/Mermaid/utils/mermaid-helpers.ts","../src/tools/Mermaid/components/MermaidFullscreenModal.tsx","../src/tools/Mermaid/hooks/useMermaidFullscreen.ts","../src/tools/Mermaid/hooks/useMermaidCleanup.ts","../src/tools/Mermaid/hooks/useMermaidValidation.ts","../src/tools/Mermaid/hooks/useMermaidRenderer.ts","../src/tools/Mermaid/Mermaid.client.tsx"],"names":["__name","useControls","jsxs","jsx","Button","ZoomOut","RotateCcw","ZoomIn","useEffect","createPortal","X","TransformWrapper","TransformComponent","useState","useRef","useCallback","applyMermaidTextColors","mermaid","useResolvedTheme","Fragment","Maximize2"],"mappings":"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","file":"Mermaid.client-2TAFAXPW.cjs","sourcesContent":["/**\n * Helper utilities for Mermaid diagram rendering\n */\n\n// Utility function to apply text colors to Mermaid SVG\nexport const applyMermaidTextColors = (container: HTMLElement, textColor: string) => {\n const svgElement = container.querySelector('svg');\n if (svgElement) {\n // SVG text elements use 'fill'\n svgElement.querySelectorAll('text').forEach((el) => {\n (el as SVGElement).style.fill = textColor;\n });\n\n // HTML elements inside foreignObject use 'color'\n svgElement.querySelectorAll('.nodeLabel, .edgeLabel').forEach((el) => {\n (el as HTMLElement).style.color = textColor;\n });\n }\n};\n\n// Detect if diagram is vertical (tall and narrow)\nexport const isVerticalDiagram = (svgElement: SVGSVGElement): boolean => {\n const viewBox = svgElement.getAttribute('viewBox');\n if (viewBox) {\n const [, , width, height] = viewBox.split(' ').map(Number);\n return height > width * 1.5;\n }\n const bbox = svgElement.getBBox?.();\n if (bbox) {\n return bbox.height > bbox.width * 1.5;\n }\n return false;\n};\n","'use client';\n\nimport React, { useEffect } from 'react';\nimport { createPortal } from 'react-dom';\nimport { X, ZoomIn, ZoomOut, RotateCcw } from 'lucide-react';\nimport { TransformWrapper, TransformComponent, useControls } from 'react-zoom-pan-pinch';\n\nimport { Button } from '@djangocfg/ui-core/components';\nimport { applyMermaidTextColors } from '../utils/mermaid-helpers';\n\ninterface MermaidFullscreenModalProps {\n isOpen: boolean;\n svgContent: string;\n isVertical: boolean;\n theme: string;\n chart: string;\n fullscreenRef: React.RefObject<HTMLDivElement | null>;\n onClose: () => void;\n onBackdropClick: (e: React.MouseEvent) => void;\n}\n\n// Zoom controls component\nfunction ZoomControls() {\n const { zoomIn, zoomOut, resetTransform } = useControls();\n\n return (\n <div className=\"absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2 z-10\">\n <Button variant=\"secondary\" size=\"icon\" onClick={() => zoomOut()}>\n <ZoomOut className=\"h-4 w-4\" />\n </Button>\n <Button variant=\"secondary\" size=\"icon\" onClick={() => resetTransform()}>\n <RotateCcw className=\"h-4 w-4\" />\n </Button>\n <Button variant=\"secondary\" size=\"icon\" onClick={() => zoomIn()}>\n <ZoomIn className=\"h-4 w-4\" />\n </Button>\n </div>\n );\n}\n\nexport const MermaidFullscreenModal: React.FC<MermaidFullscreenModalProps> = ({\n isOpen,\n svgContent,\n isVertical,\n theme,\n fullscreenRef,\n onClose,\n onBackdropClick,\n}) => {\n // Apply text colors\n useEffect(() => {\n if (isOpen && fullscreenRef.current) {\n const getCSSVariable = (variable: string) => {\n if (typeof document === 'undefined') return '';\n const value = getComputedStyle(document.documentElement).getPropertyValue(variable).trim();\n return value ? `hsl(${value})` : '';\n };\n\n const textColor = theme === 'dark'\n ? getCSSVariable('--foreground') || 'hsl(0 0% 90%)'\n : getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)';\n\n applyMermaidTextColors(fullscreenRef.current, textColor);\n }\n }, [isOpen, theme, isVertical, fullscreenRef, svgContent]);\n\n // Handle escape key\n useEffect(() => {\n if (!isOpen) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') onClose();\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [isOpen, onClose]);\n\n if (!isOpen || typeof document === 'undefined') return null;\n\n return createPortal(\n <div\n className=\"fixed inset-0 z-9999 bg-background/95 backdrop-blur-sm\"\n onClick={onBackdropClick}\n >\n {/* Close button */}\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"absolute top-4 right-4 z-10\"\n onClick={onClose}\n >\n <X className=\"h-5 w-5\" />\n </Button>\n\n {/* Zoomable diagram */}\n <TransformWrapper\n initialScale={1}\n minScale={0.1}\n maxScale={10}\n centerOnInit\n wheel={{ step: 0.1 }}\n pinch={{ step: 5 }}\n doubleClick={{ mode: 'reset' }}\n >\n <ZoomControls />\n <TransformComponent\n wrapperStyle={{\n width: '100%',\n height: '100%',\n }}\n contentStyle={{\n width: '100%',\n height: '100%',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <div\n ref={fullscreenRef}\n className=\"p-8\"\n dangerouslySetInnerHTML={{ __html: svgContent }}\n onClick={(e) => e.stopPropagation()}\n />\n </TransformComponent>\n </TransformWrapper>\n </div>,\n document.body\n );\n};\n","/**\n * Hook for managing Mermaid fullscreen modal\n */\n\nimport { useEffect, useRef, useState } from 'react';\n\nexport function useMermaidFullscreen() {\n const [isFullscreen, setIsFullscreen] = useState(false);\n const fullscreenRef = useRef<HTMLDivElement>(null);\n\n const openFullscreen = () => setIsFullscreen(true);\n const closeFullscreen = () => setIsFullscreen(false);\n\n const handleBackdropClick = (e: React.MouseEvent) => {\n if (e.target === e.currentTarget) {\n closeFullscreen();\n }\n };\n\n // Handle ESC key\n useEffect(() => {\n const handleEscKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && isFullscreen) {\n closeFullscreen();\n }\n };\n\n if (isFullscreen) {\n document.addEventListener('keydown', handleEscKey);\n document.body.style.overflow = 'hidden';\n }\n\n return () => {\n document.removeEventListener('keydown', handleEscKey);\n document.body.style.overflow = 'unset';\n };\n }, [isFullscreen]);\n\n return {\n isFullscreen,\n fullscreenRef,\n openFullscreen,\n closeFullscreen,\n handleBackdropClick,\n };\n}\n","/**\n * Hook for cleaning up orphaned Mermaid DOM nodes\n */\n\nimport { useCallback, useEffect } from 'react';\n\nexport function useMermaidCleanup() {\n const cleanupMermaidErrors = useCallback(() => {\n if (typeof document === 'undefined') return;\n\n // Remove all orphaned mermaid elements from body\n // Mermaid can append: SVGs, divs with errors, text nodes\n\n // 1. Remove elements with mermaid-* IDs directly in body\n document.querySelectorAll('[id^=\"mermaid-\"]').forEach((node) => {\n if (node.parentNode === document.body) {\n node.remove();\n }\n });\n\n // 2. Remove elements with d prefix (mermaid diagram IDs) directly in body\n document.querySelectorAll('[id^=\"d\"]').forEach((node) => {\n if (node.parentNode === document.body && node.id.match(/^d\\d+$/)) {\n node.remove();\n }\n });\n\n // 3. Remove orphaned SVG elements that mermaid creates in body\n document.querySelectorAll('body > svg').forEach((node) => {\n // Check if it's a mermaid SVG (has mermaid classes or aria-roledescription)\n if (node.getAttribute('aria-roledescription') ||\n node.classList.contains('mermaid') ||\n node.querySelector('.mermaid') ||\n node.id?.includes('mermaid')) {\n node.remove();\n }\n });\n\n // 4. Remove any orphaned error divs with \"Syntax error\" text\n document.querySelectorAll('body > div').forEach((node) => {\n const text = node.textContent || '';\n if (text.includes('Syntax error in text') ||\n text.includes('mermaid version') ||\n node.id?.startsWith('mermaid-') ||\n node.id?.startsWith('d') && node.id.match(/^d\\d+$/)) {\n node.remove();\n }\n });\n\n // 5. Remove orphaned pre elements with error info\n document.querySelectorAll('body > pre').forEach((node) => {\n const text = node.textContent || '';\n if (text.includes('Syntax error') || text.includes('mermaid')) {\n node.remove();\n }\n });\n }, []);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n cleanupMermaidErrors();\n };\n }, [cleanupMermaidErrors]);\n\n // Removed periodic cleanup - it causes unnecessary re-renders\n // Cleanup only happens on unmount now\n\n return { cleanupMermaidErrors };\n}\n","/**\n * Hook for validating Mermaid code completeness\n */\n\nimport { useCallback } from 'react';\n\nexport function useMermaidValidation() {\n const isMermaidCodeComplete = useCallback((code: string): boolean => {\n if (!code || code.trim().length === 0) return false;\n\n const trimmed = code.trim();\n\n // Check if code has basic structure\n const lines = trimmed.split('\\n');\n if (lines.length < 2) return false; // Need at least diagram type + one element\n\n // Check for common incomplete patterns\n const lastLine = lines[lines.length - 1].trim();\n\n // Incomplete if last line ends with arrow without destination\n if (lastLine.match(/-->?\\s*$/)) return false;\n if (lastLine.match(/-->\\|[^|]*\\|\\s*$/)) return false;\n\n // Incomplete if last line ends with opening bracket/parenthesis\n if (lastLine.match(/[\\[({]\\s*$/)) return false;\n\n return true;\n }, []);\n\n return { isMermaidCodeComplete };\n}\n","/**\n * Hook for rendering Mermaid diagrams with debounce and validation\n */\n\nimport mermaid from 'mermaid';\nimport { useEffect, useRef, useState } from 'react';\n\nimport { useMermaidCleanup } from './useMermaidCleanup';\nimport { useMermaidValidation } from './useMermaidValidation';\n\ninterface UseMermaidRendererProps {\n chart: string;\n theme: string;\n isCompact?: boolean;\n}\n\ninterface MermaidRenderResult {\n mermaidRef: React.RefObject<HTMLDivElement>;\n svgContent: string;\n isVertical: boolean;\n isRendering: boolean;\n}\n\n// Utility function to apply text colors to Mermaid SVG\nconst applyMermaidTextColors = (container: HTMLElement, textColor: string) => {\n const svgElement = container.querySelector('svg');\n if (svgElement) {\n // SVG text elements use 'fill'\n svgElement.querySelectorAll('text').forEach((el) => {\n (el as SVGElement).style.fill = textColor;\n });\n\n // HTML elements inside foreignObject use 'color'\n svgElement.querySelectorAll('.nodeLabel, .edgeLabel').forEach((el) => {\n (el as HTMLElement).style.color = textColor;\n });\n }\n};\n\n// Detect if diagram is vertical (tall and narrow)\nconst isVerticalDiagram = (svgElement: SVGSVGElement): boolean => {\n const viewBox = svgElement.getAttribute('viewBox');\n if (viewBox) {\n const [, , width, height] = viewBox.split(' ').map(Number);\n return height > width * 1.5;\n }\n const bbox = svgElement.getBBox?.();\n if (bbox) {\n return bbox.height > bbox.width * 1.5;\n }\n return false;\n};\n\nexport function useMermaidRenderer({ chart, theme, isCompact = false }: UseMermaidRendererProps): MermaidRenderResult {\n const mermaidRef = useRef<HTMLDivElement>(null);\n const renderTimerRef = useRef<NodeJS.Timeout | null>(null);\n const [svgContent, setSvgContent] = useState<string>('');\n const [isVertical, setIsVertical] = useState(false);\n const [isRendering, setIsRendering] = useState(false);\n\n const { isMermaidCodeComplete } = useMermaidValidation();\n const { cleanupMermaidErrors } = useMermaidCleanup();\n\n useEffect(() => {\n // Get CSS variables for semantic colors\n const getCSSVariable = (variable: string) => {\n if (typeof document === 'undefined') return '';\n const value = getComputedStyle(document.documentElement).getPropertyValue(variable).trim();\n if (!value) return '';\n // If value is already a complete color (hex, rgb, hsl with parentheses), return as-is\n if (value.startsWith('#') || value.startsWith('rgb') || value.startsWith('hsl(')) {\n return value;\n }\n // Otherwise assume it's HSL components and wrap in hsl()\n return `hsl(${value})`;\n };\n\n const diagramFontSize = isCompact ? '12px' : '14px';\n\n const themeVariables = theme === 'dark' ? {\n primaryColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',\n primaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',\n primaryBorderColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',\n secondaryColor: getCSSVariable('--muted') || 'hsl(217.2 32.6% 17.5%)',\n secondaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',\n secondaryBorderColor: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',\n tertiaryColor: getCSSVariable('--accent') || 'hsl(217.2 32.6% 20%)',\n tertiaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',\n tertiaryBorderColor: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',\n mainBkg: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',\n textColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',\n nodeBorder: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',\n nodeTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',\n secondBkg: getCSSVariable('--muted') || 'hsl(217.2 32.6% 17.5%)',\n lineColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',\n edgeLabelBackground: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',\n clusterBkg: getCSSVariable('--muted') || 'hsl(217.2 32.6% 12%)',\n clusterBorder: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',\n background: getCSSVariable('--background') || 'hsl(222.2 84% 4.9%)',\n labelBackground: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',\n labelTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',\n errorBkgColor: getCSSVariable('--destructive') || 'hsl(0 62.8% 30.6%)',\n errorTextColor: 'hsl(210 40% 98%)',\n fontSize: diagramFontSize,\n fontFamily: 'Inter, system-ui, sans-serif',\n } : {\n primaryColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',\n primaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',\n primaryBorderColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',\n secondaryColor: getCSSVariable('--secondary') || 'hsl(210 40% 96.1%)',\n secondaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',\n secondaryBorderColor: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',\n tertiaryColor: getCSSVariable('--muted') || 'hsl(210 40% 96.1%)',\n tertiaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',\n tertiaryBorderColor: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',\n mainBkg: getCSSVariable('--card') || 'hsl(0 0% 100%)',\n textColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',\n nodeBorder: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',\n nodeTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',\n secondBkg: getCSSVariable('--muted') || 'hsl(210 40% 96.1%)',\n lineColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',\n edgeLabelBackground: getCSSVariable('--card') || 'hsl(0 0% 100%)',\n clusterBkg: getCSSVariable('--accent') || 'hsl(210 40% 98%)',\n clusterBorder: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',\n background: getCSSVariable('--background') || 'hsl(0 0% 100%)',\n labelBackground: getCSSVariable('--card') || 'hsl(0 0% 100%)',\n labelTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',\n errorBkgColor: getCSSVariable('--destructive') || 'hsl(0 84.2% 60.2%)',\n errorTextColor: 'hsl(210 40% 98%)',\n fontSize: diagramFontSize,\n fontFamily: 'Inter, system-ui, sans-serif',\n };\n\n mermaid.initialize({\n startOnLoad: false,\n theme: 'base',\n securityLevel: 'loose',\n suppressErrorRendering: true, // Prevent mermaid from appending errors to body\n fontFamily: 'Inter, system-ui, sans-serif',\n flowchart: {\n useMaxWidth: true,\n htmlLabels: true,\n curve: 'basis',\n },\n themeVariables,\n });\n\n const renderChart = async () => {\n if (!mermaidRef.current || !chart) return;\n\n // Validate code completeness\n if (!isMermaidCodeComplete(chart)) {\n setIsRendering(true);\n return;\n }\n\n try {\n setIsRendering(true);\n\n // Clear container\n if (mermaidRef.current) {\n mermaidRef.current.innerHTML = '';\n }\n\n const id = `mermaid-${Math.random().toString(36).substring(2, 9)}`;\n const { svg } = await mermaid.render(id, chart);\n\n if (mermaidRef.current) {\n const textColor = theme === 'dark'\n ? getCSSVariable('--foreground') || 'hsl(0 0% 90%)'\n : getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)';\n\n const processedSvg = svg.replace(\n /<svg /,\n `<svg style=\"--mermaid-text-color: ${textColor};\" `\n );\n\n mermaidRef.current.innerHTML = processedSvg;\n setSvgContent(processedSvg);\n\n applyMermaidTextColors(mermaidRef.current, textColor);\n\n const svgElement = mermaidRef.current.querySelector('svg');\n if (svgElement) {\n svgElement.style.maxWidth = '100%';\n svgElement.style.height = 'auto';\n svgElement.style.display = 'block';\n setIsVertical(isVerticalDiagram(svgElement));\n }\n }\n\n setIsRendering(false);\n } catch (error) {\n console.error('Mermaid rendering error:', error);\n setIsRendering(false);\n cleanupMermaidErrors();\n\n if (mermaidRef.current) {\n mermaidRef.current.innerHTML = `\n <div class=\"p-4 text-destructive bg-destructive/10 border border-destructive/20 rounded-sm\">\n <p class=\"font-semibold\">Mermaid Diagram Error</p>\n <p class=\"text-sm\">${error instanceof Error ? error.message : 'Unknown error'}</p>\n </div>\n `;\n }\n }\n };\n\n // Clear previous timer\n if (renderTimerRef.current) {\n clearTimeout(renderTimerRef.current);\n }\n\n // Debounce: wait 500ms after last update\n renderTimerRef.current = setTimeout(() => {\n renderChart();\n }, 500);\n\n return () => {\n if (renderTimerRef.current) {\n clearTimeout(renderTimerRef.current);\n }\n };\n }, [chart, theme, isCompact, isMermaidCodeComplete, cleanupMermaidErrors]);\n\n return {\n mermaidRef,\n svgContent,\n isVertical,\n isRendering,\n };\n}\n","'use client';\n\nimport React from 'react';\nimport { Maximize2 } from 'lucide-react';\n\nimport { Button } from '@djangocfg/ui-core/components';\nimport { useResolvedTheme } from '@djangocfg/ui-core/hooks';\nimport { MermaidFullscreenModal } from './components/MermaidFullscreenModal';\nimport { useMermaidFullscreen } from './hooks/useMermaidFullscreen';\nimport { useMermaidRenderer } from './hooks/useMermaidRenderer';\n\ninterface MermaidProps {\n chart: string;\n className?: string;\n isCompact?: boolean;\n /** Enable click-to-fullscreen functionality (default: true) */\n fullscreen?: boolean;\n}\n\nconst Mermaid: React.FC<MermaidProps> = ({\n chart,\n className = '',\n isCompact = false,\n fullscreen = true,\n}) => {\n const theme = useResolvedTheme();\n\n // Rendering logic\n const { mermaidRef, svgContent, isVertical, isRendering } = useMermaidRenderer({\n chart,\n theme,\n isCompact,\n });\n\n // Fullscreen modal logic (only used if fullscreen prop is true)\n const {\n isFullscreen,\n fullscreenRef,\n openFullscreen,\n closeFullscreen,\n handleBackdropClick,\n } = useMermaidFullscreen();\n\n const handleOpenFullscreen = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (svgContent) {\n openFullscreen();\n }\n };\n\n return (\n <>\n <div className={`relative ${className}`}>\n <div\n ref={mermaidRef}\n className=\"flex justify-center items-center\"\n style={{ isolation: 'isolate' }}\n />\n {isRendering && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <div className=\"animate-spin rounded-full h-6 w-6 border-b-2 border-primary\" />\n </div>\n )}\n\n {/* Fullscreen button in bottom-right corner */}\n {fullscreen && svgContent && !isRendering && (\n <Button\n variant=\"secondary\"\n size=\"icon\"\n className=\"absolute bottom-2 right-2 h-8 w-8 opacity-60 hover:opacity-100 transition-opacity\"\n onClick={handleOpenFullscreen}\n >\n <Maximize2 className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n\n {fullscreen && (\n <MermaidFullscreenModal\n isOpen={isFullscreen}\n svgContent={svgContent}\n isVertical={isVertical}\n theme={theme}\n chart={chart}\n fullscreenRef={fullscreenRef}\n onClose={closeFullscreen}\n onBackdropClick={handleBackdropClick}\n />\n )}\n </>\n );\n};\n\nexport default Mermaid;\n"]}
|