@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.
Files changed (42) hide show
  1. package/README.md +164 -3
  2. package/dist/{Mermaid.client-KMNEPBQJ.cjs → Mermaid.client-2TAFAXPW.cjs} +100 -143
  3. package/dist/Mermaid.client-2TAFAXPW.cjs.map +1 -0
  4. package/dist/{Mermaid.client-3WPNJ4DF.mjs → Mermaid.client-HG24D5KB.mjs} +100 -143
  5. package/dist/Mermaid.client-HG24D5KB.mjs.map +1 -0
  6. package/dist/index.cjs +4 -9
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +2 -2
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.mjs +4 -9
  11. package/dist/index.mjs.map +1 -1
  12. package/package.json +11 -6
  13. package/src/tools/Mermaid/Mermaid.client.tsx +40 -56
  14. package/src/tools/Mermaid/Mermaid.story.tsx +195 -110
  15. package/src/tools/Mermaid/builders/FlowDiagram/FlowDiagram.ts +96 -0
  16. package/src/tools/Mermaid/builders/FlowDiagram/functions/getEdges.ts +50 -0
  17. package/src/tools/Mermaid/builders/FlowDiagram/functions/getNodes.ts +43 -0
  18. package/src/tools/Mermaid/builders/FlowDiagram/functions/getStyles.ts +90 -0
  19. package/src/tools/Mermaid/builders/FlowDiagram/functions/index.ts +8 -0
  20. package/src/tools/Mermaid/builders/FlowDiagram/index.ts +16 -0
  21. package/src/tools/Mermaid/builders/FlowDiagram/types.ts +130 -0
  22. package/src/tools/Mermaid/builders/JourneyDiagram/JourneyDiagram.ts +88 -0
  23. package/src/tools/Mermaid/builders/JourneyDiagram/index.ts +12 -0
  24. package/src/tools/Mermaid/builders/JourneyDiagram/types.ts +48 -0
  25. package/src/tools/Mermaid/builders/SequenceDiagram/SequenceDiagram.ts +123 -0
  26. package/src/tools/Mermaid/builders/SequenceDiagram/functions/getActivations.ts +30 -0
  27. package/src/tools/Mermaid/builders/SequenceDiagram/functions/getBlocks.ts +112 -0
  28. package/src/tools/Mermaid/builders/SequenceDiagram/functions/getMessages.ts +85 -0
  29. package/src/tools/Mermaid/builders/SequenceDiagram/functions/getNotes.ts +94 -0
  30. package/src/tools/Mermaid/builders/SequenceDiagram/functions/index.ts +16 -0
  31. package/src/tools/Mermaid/builders/SequenceDiagram/index.ts +17 -0
  32. package/src/tools/Mermaid/builders/SequenceDiagram/types.ts +147 -0
  33. package/src/tools/Mermaid/builders/core/DiagramStore.ts +138 -0
  34. package/src/tools/Mermaid/builders/core/index.ts +8 -0
  35. package/src/tools/Mermaid/builders/core/sanitize.ts +78 -0
  36. package/src/tools/Mermaid/builders/core/theme.ts +42 -0
  37. package/src/tools/Mermaid/builders/core/types.ts +183 -0
  38. package/src/tools/Mermaid/builders/index.ts +96 -0
  39. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +78 -54
  40. package/src/tools/Mermaid/index.tsx +51 -12
  41. package/dist/Mermaid.client-3WPNJ4DF.mjs.map +0 -1
  42. 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 { Mermaid } from '@djangocfg/ui-tools';
257
+ import { LazyMermaid } from '@djangocfg/ui-tools';
241
258
 
242
- <Mermaid chart={`
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 react = require('react');
5
- var i18n = require('@djangocfg/i18n');
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
- var MermaidCodeViewer = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({
28
- chart,
29
- renderPreview
30
- }) => {
31
- const [activeTab, setActiveTab] = react.useState("preview");
32
- const [copied, setCopied] = react.useState(false);
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
- }, "MermaidCodeViewer");
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.jsx(
68
+ /* @__PURE__ */ jsxRuntime.jsxs(
118
69
  "div",
119
70
  {
120
- className: "fixed inset-0 z-9999 flex items-center justify-center p-4",
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: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative bg-card rounded-sm shadow-xl max-h-[95vh] flex flex-col border border-border ${isVertical ? "w-auto min-w-[400px] max-w-[600px]" : "min-w-[600px] max-w-[90vw] w-auto"}`, children: [
124
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between py-4 px-6 border-b border-border", children: [
125
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium text-foreground py-0 my-0", children: diagramTitle }),
126
- /* @__PURE__ */ jsxRuntime.jsx(
127
- "button",
128
- {
129
- onClick: onClose,
130
- className: "text-muted-foreground hover:text-foreground transition-colors",
131
- children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
132
- }
133
- )
134
- ] }),
135
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
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
- chart,
139
- renderPreview: () => /* @__PURE__ */ jsxRuntime.jsx(
140
- "div",
141
- {
142
- ref: fullscreenRef,
143
- dangerouslySetInnerHTML: { __html: svgContent }
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(({ chart, className = "", isCompact = false, bare = false }) => {
419
- const t = i18n.useTypedT();
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 handleClick = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => {
439
- if (svgContent && !bare) {
413
+ const handleOpenFullscreen = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((e) => {
414
+ e.stopPropagation();
415
+ if (svgContent) {
440
416
  openFullscreen();
441
417
  }
442
- }, "handleClick");
443
- if (bare) {
444
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${className}`, children: [
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
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
457
- /* @__PURE__ */ jsxRuntime.jsxs(
458
- "div",
459
- {
460
- className: `relative bg-card rounded-sm border border-border overflow-hidden cursor-pointer hover:shadow-sm transition-shadow ${className}`,
461
- onClick: handleClick,
462
- children: [
463
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 border-b border-border bg-muted/50", children: [
464
- /* @__PURE__ */ jsxRuntime.jsx("h6", { className: "text-sm font-semibold text-foreground", children: labels.title }),
465
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-1", children: labels.clickToView })
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-KMNEPBQJ.cjs.map
503
- //# sourceMappingURL=Mermaid.client-KMNEPBQJ.cjs.map
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"]}