@djangocfg/widget-diagram 0.1.1
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/LICENSE +21 -0
- package/README.md +84 -0
- package/package.json +77 -0
- package/src/FloatingToolbar/FloatingToolbar.css +5 -0
- package/src/FloatingToolbar/actions/CopyAction.tsx +31 -0
- package/src/FloatingToolbar/actions/DownloadAction.tsx +51 -0
- package/src/FloatingToolbar/actions/ExpandAction.tsx +33 -0
- package/src/FloatingToolbar/actions/FullscreenAction.tsx +38 -0
- package/src/FloatingToolbar/actions/index.ts +4 -0
- package/src/FloatingToolbar/hooks/useScrollIsolation.ts +62 -0
- package/src/FloatingToolbar/index.tsx +184 -0
- package/src/Mermaid.client.tsx +97 -0
- package/src/builders/FlowDiagram/FlowDiagram.ts +96 -0
- package/src/builders/FlowDiagram/functions/getEdges.ts +50 -0
- package/src/builders/FlowDiagram/functions/getNodes.ts +43 -0
- package/src/builders/FlowDiagram/functions/getStyles.ts +90 -0
- package/src/builders/FlowDiagram/functions/index.ts +8 -0
- package/src/builders/FlowDiagram/index.ts +16 -0
- package/src/builders/FlowDiagram/types.ts +130 -0
- package/src/builders/JourneyDiagram/JourneyDiagram.ts +88 -0
- package/src/builders/JourneyDiagram/index.ts +12 -0
- package/src/builders/JourneyDiagram/types.ts +48 -0
- package/src/builders/SequenceDiagram/SequenceDiagram.ts +158 -0
- package/src/builders/SequenceDiagram/functions/getActivations.ts +30 -0
- package/src/builders/SequenceDiagram/functions/getBlocks.ts +112 -0
- package/src/builders/SequenceDiagram/functions/getMessages.ts +85 -0
- package/src/builders/SequenceDiagram/functions/getNotes.ts +94 -0
- package/src/builders/SequenceDiagram/functions/index.ts +16 -0
- package/src/builders/SequenceDiagram/index.ts +18 -0
- package/src/builders/SequenceDiagram/types.ts +192 -0
- package/src/builders/core/DiagramStore.ts +138 -0
- package/src/builders/core/index.ts +8 -0
- package/src/builders/core/sanitize.ts +83 -0
- package/src/builders/core/theme.ts +42 -0
- package/src/builders/core/types.ts +183 -0
- package/src/builders/index.ts +96 -0
- package/src/components/MermaidCodeViewer.tsx +95 -0
- package/src/components/MermaidErrorPanel.tsx +31 -0
- package/src/components/MermaidFullscreenModal.tsx +201 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useMermaidCleanup.ts +70 -0
- package/src/hooks/useMermaidFullscreen.ts +46 -0
- package/src/hooks/useMermaidRenderer.ts +329 -0
- package/src/hooks/useMermaidValidation.ts +97 -0
- package/src/index.tsx +79 -0
- package/src/lazy.tsx +40 -0
- package/src/mermaid.stories.tsx +217 -0
- package/src/types.ts +28 -0
- package/src/utils/mermaid-helpers.ts +157 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DiagramStore - Base class for building Mermaid diagrams
|
|
3
|
+
* Accumulates diagram lines with proper indentation
|
|
4
|
+
* @module Mermaid/builders/core/DiagramStore
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface DiagramStoreOptions {
|
|
8
|
+
/** Indentation string (default: ' ' - two spaces) */
|
|
9
|
+
indent?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const DEFAULT_OPTIONS: Required<DiagramStoreOptions> = {
|
|
13
|
+
indent: ' ',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Base store for accumulating Mermaid diagram code
|
|
18
|
+
* Handles indentation and line management
|
|
19
|
+
*/
|
|
20
|
+
export class DiagramStore {
|
|
21
|
+
protected lines: string[] = [];
|
|
22
|
+
protected indentLevel = 0;
|
|
23
|
+
protected options: Required<DiagramStoreOptions>;
|
|
24
|
+
|
|
25
|
+
constructor(header: string, options: DiagramStoreOptions = {}) {
|
|
26
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
27
|
+
this.lines.push(header);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Add a line to the diagram with current indentation
|
|
32
|
+
*/
|
|
33
|
+
add(line: string): this {
|
|
34
|
+
const indent = this.options.indent.repeat(this.indentLevel);
|
|
35
|
+
this.lines.push(indent + line);
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Add a raw line without indentation
|
|
41
|
+
*/
|
|
42
|
+
addRaw(line: string): this {
|
|
43
|
+
this.lines.push(line);
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Add an empty line
|
|
49
|
+
*/
|
|
50
|
+
addBlank(): this {
|
|
51
|
+
this.lines.push('');
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Add a comment line
|
|
57
|
+
*/
|
|
58
|
+
addComment(comment: string): this {
|
|
59
|
+
return this.add(`%% ${comment}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Increase indentation level
|
|
64
|
+
*/
|
|
65
|
+
indent(): this {
|
|
66
|
+
this.indentLevel++;
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Decrease indentation level
|
|
72
|
+
*/
|
|
73
|
+
dedent(): this {
|
|
74
|
+
if (this.indentLevel > 0) {
|
|
75
|
+
this.indentLevel--;
|
|
76
|
+
}
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Execute a callback within an indented block
|
|
82
|
+
* @param header - Block header line
|
|
83
|
+
* @param fn - Callback to execute inside the block
|
|
84
|
+
* @param footer - Block footer line (default: 'end')
|
|
85
|
+
*/
|
|
86
|
+
block(header: string, fn: () => void, footer = 'end'): this {
|
|
87
|
+
this.add(header);
|
|
88
|
+
this.indent();
|
|
89
|
+
fn();
|
|
90
|
+
this.dedent();
|
|
91
|
+
this.add(footer);
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Execute a callback within a subgraph block
|
|
97
|
+
* @param name - Subgraph name/title
|
|
98
|
+
* @param fn - Callback to execute inside the subgraph
|
|
99
|
+
*/
|
|
100
|
+
subgraph(name: string, fn: () => void): this {
|
|
101
|
+
return this.block(`subgraph ${name}`, fn);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Add direction directive (for subgraphs)
|
|
106
|
+
*/
|
|
107
|
+
direction(dir: 'TB' | 'BT' | 'LR' | 'RL'): this {
|
|
108
|
+
return this.add(`direction ${dir}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the current indentation string
|
|
113
|
+
*/
|
|
114
|
+
getIndent(): string {
|
|
115
|
+
return this.options.indent.repeat(this.indentLevel);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get the current indentation level
|
|
120
|
+
*/
|
|
121
|
+
getIndentLevel(): number {
|
|
122
|
+
return this.indentLevel;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Convert the diagram to a Mermaid string
|
|
127
|
+
*/
|
|
128
|
+
toString(): string {
|
|
129
|
+
return this.lines.join('\n');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get all lines (for debugging)
|
|
134
|
+
*/
|
|
135
|
+
getLines(): readonly string[] {
|
|
136
|
+
return this.lines;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitization utilities for Mermaid diagrams
|
|
3
|
+
* @module Mermaid/builders/core/sanitize
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Characters that need to be escaped or removed in Mermaid labels
|
|
8
|
+
*/
|
|
9
|
+
const UNSAFE_CHARS = /["\n\r\\<>{}|]/g;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Mermaid reserved keywords that cannot be used as node IDs.
|
|
13
|
+
*
|
|
14
|
+
* Stored LOWERCASE because the lookup lowercases its input. Held as `classDef`
|
|
15
|
+
* and `linkStyle`, those two never matched — a node called `classDef` emitted a
|
|
16
|
+
* style directive where a node was meant, and what followed was read as its
|
|
17
|
+
* arguments.
|
|
18
|
+
*/
|
|
19
|
+
const RESERVED_WORDS = new Set([
|
|
20
|
+
'end',
|
|
21
|
+
'graph',
|
|
22
|
+
'subgraph',
|
|
23
|
+
'direction',
|
|
24
|
+
'click',
|
|
25
|
+
'style',
|
|
26
|
+
'class',
|
|
27
|
+
'classdef',
|
|
28
|
+
'linkstyle',
|
|
29
|
+
'callback',
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Sanitize a label for use in Mermaid diagrams
|
|
34
|
+
* Removes or escapes characters that could break the diagram syntax
|
|
35
|
+
*/
|
|
36
|
+
export function sanitizeLabel(label: string): string {
|
|
37
|
+
return label
|
|
38
|
+
.replace(UNSAFE_CHARS, '')
|
|
39
|
+
.replace(/&/g, '&')
|
|
40
|
+
.replace(/'/g, "'")
|
|
41
|
+
.trim();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Convert an ID to a valid Mermaid node ID
|
|
46
|
+
* - Removes special characters
|
|
47
|
+
* - Adds optional prefix to ensure uniqueness
|
|
48
|
+
* - Ensures ID starts with a letter
|
|
49
|
+
* - Escapes reserved Mermaid keywords
|
|
50
|
+
*/
|
|
51
|
+
export function toNodeId(id: string, prefix = ''): string {
|
|
52
|
+
// Remove special characters and spaces
|
|
53
|
+
const cleanId = id.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
54
|
+
|
|
55
|
+
// Ensure starts with letter
|
|
56
|
+
let safeId = /^[a-zA-Z]/.test(cleanId) ? cleanId : `n${cleanId}`;
|
|
57
|
+
|
|
58
|
+
// Escape reserved words by adding underscore suffix
|
|
59
|
+
if (RESERVED_WORDS.has(safeId.toLowerCase())) {
|
|
60
|
+
safeId = `${safeId}_`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return prefix ? `${prefix}_${safeId}` : safeId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Escape a string for use in quoted Mermaid labels
|
|
68
|
+
*/
|
|
69
|
+
export function escapeQuoted(str: string): string {
|
|
70
|
+
return str
|
|
71
|
+
.replace(/\\/g, '\\\\')
|
|
72
|
+
.replace(/"/g, '\\"')
|
|
73
|
+
.replace(/\n/g, '<br/>');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Format a label with optional subtitle using HTML-like syntax
|
|
78
|
+
*/
|
|
79
|
+
export function formatLabel(main: string, subtitle?: string): string {
|
|
80
|
+
const sanitized = sanitizeLabel(main);
|
|
81
|
+
if (!subtitle) return sanitized;
|
|
82
|
+
return `${sanitized}<br/><small>${sanitizeLabel(subtitle)}</small>`;
|
|
83
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme utilities for Mermaid builders
|
|
3
|
+
*
|
|
4
|
+
* Re-exports palette hooks from @djangocfg/ui-core for convenience.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import { useStylePresets, useBoxColors } from './theme';
|
|
9
|
+
*
|
|
10
|
+
* function MyDiagram() {
|
|
11
|
+
* const presets = useStylePresets();
|
|
12
|
+
* const boxes = useBoxColors();
|
|
13
|
+
*
|
|
14
|
+
* const flow = FlowDiagram();
|
|
15
|
+
* flow.style.define('success', presets.success);
|
|
16
|
+
*
|
|
17
|
+
* const { rect } = SequenceDiagram({ ... });
|
|
18
|
+
* rect(boxes.primary, () => { ... });
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @module Mermaid/builders/core/theme
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// Re-export from ui-core palette
|
|
26
|
+
export {
|
|
27
|
+
// Types
|
|
28
|
+
type ThemePalette,
|
|
29
|
+
type StyleColors,
|
|
30
|
+
type StylePresets,
|
|
31
|
+
type BoxColors,
|
|
32
|
+
|
|
33
|
+
// Hooks
|
|
34
|
+
useThemePalette,
|
|
35
|
+
useStylePresets,
|
|
36
|
+
useBoxColors,
|
|
37
|
+
|
|
38
|
+
// Utils
|
|
39
|
+
hslToHex,
|
|
40
|
+
hslToRgbString,
|
|
41
|
+
hslToRgba,
|
|
42
|
+
} from '@djangocfg/ui-core/styles/palette';
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for Mermaid diagram builders
|
|
3
|
+
* @module Mermaid/builders/core/types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Node Types
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
export type NodeId = string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Node shapes supported by Mermaid flowcharts
|
|
14
|
+
* @see https://mermaid.js.org/syntax/flowchart.html#node-shapes
|
|
15
|
+
*/
|
|
16
|
+
export type NodeShape =
|
|
17
|
+
| 'rect' // [text]
|
|
18
|
+
| 'round' // (text)
|
|
19
|
+
| 'stadium' // ([text])
|
|
20
|
+
| 'subroutine' // [[text]]
|
|
21
|
+
| 'cylinder' // [(text)]
|
|
22
|
+
| 'circle' // ((text))
|
|
23
|
+
| 'asymmetric' // >text]
|
|
24
|
+
| 'rhombus' // {text}
|
|
25
|
+
| 'hexagon' // {{text}}
|
|
26
|
+
| 'parallelogram' // [/text/]
|
|
27
|
+
| 'parallelogramAlt' // [\text\]
|
|
28
|
+
| 'trapezoid' // [/text\]
|
|
29
|
+
| 'trapezoidAlt' // [\text/]
|
|
30
|
+
| 'doubleCircle'; // (((text)))
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Shape syntax lookup for Mermaid
|
|
34
|
+
*/
|
|
35
|
+
export const NODE_SHAPE_SYNTAX: Record<NodeShape, [string, string]> = {
|
|
36
|
+
rect: ['[', ']'],
|
|
37
|
+
round: ['(', ')'],
|
|
38
|
+
stadium: ['([', '])'],
|
|
39
|
+
subroutine: ['[[', ']]'],
|
|
40
|
+
cylinder: ['[(', ')]'],
|
|
41
|
+
circle: ['((', '))'],
|
|
42
|
+
asymmetric: ['>', ']'],
|
|
43
|
+
rhombus: ['{', '}'],
|
|
44
|
+
hexagon: ['{{', '}}'],
|
|
45
|
+
parallelogram: ['[/', '/]'],
|
|
46
|
+
parallelogramAlt: ['[\\', '\\]'],
|
|
47
|
+
trapezoid: ['[/', '\\]'],
|
|
48
|
+
trapezoidAlt: ['[\\', '/]'],
|
|
49
|
+
doubleCircle: ['(((', ')))'],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Edge Types
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Edge line styles
|
|
58
|
+
*/
|
|
59
|
+
export type EdgeLineStyle = 'solid' | 'dotted' | 'thick';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Arrow head types
|
|
63
|
+
*/
|
|
64
|
+
export type ArrowHead = 'arrow' | 'open' | 'circle' | 'cross' | 'none';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Edge syntax lookup
|
|
68
|
+
*/
|
|
69
|
+
export const EDGE_SYNTAX: Record<EdgeLineStyle, Record<ArrowHead, string>> = {
|
|
70
|
+
solid: {
|
|
71
|
+
arrow: '-->',
|
|
72
|
+
open: '---',
|
|
73
|
+
circle: '--o',
|
|
74
|
+
cross: '--x',
|
|
75
|
+
none: '---',
|
|
76
|
+
},
|
|
77
|
+
dotted: {
|
|
78
|
+
arrow: '-..->',
|
|
79
|
+
open: '-.-',
|
|
80
|
+
circle: '-.-o',
|
|
81
|
+
cross: '-.-x',
|
|
82
|
+
none: '-.-',
|
|
83
|
+
},
|
|
84
|
+
thick: {
|
|
85
|
+
arrow: '==>',
|
|
86
|
+
open: '===',
|
|
87
|
+
circle: '==o',
|
|
88
|
+
cross: '==x',
|
|
89
|
+
none: '===',
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Simplified edge syntax for common cases
|
|
95
|
+
*/
|
|
96
|
+
export const SIMPLE_EDGE_SYNTAX = {
|
|
97
|
+
solid: '-->',
|
|
98
|
+
dotted: '-.->',
|
|
99
|
+
thick: '==>',
|
|
100
|
+
solidOpen: '---',
|
|
101
|
+
dottedOpen: '-.-',
|
|
102
|
+
} as const;
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// Style Types
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* CSS-like style properties for nodes
|
|
110
|
+
*/
|
|
111
|
+
export interface StyleProperties {
|
|
112
|
+
fill?: string;
|
|
113
|
+
stroke?: string;
|
|
114
|
+
'stroke-width'?: string;
|
|
115
|
+
color?: string;
|
|
116
|
+
'font-weight'?: 'bold' | 'normal';
|
|
117
|
+
'stroke-dasharray'?: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Style class definition
|
|
122
|
+
*/
|
|
123
|
+
export interface StyleClass {
|
|
124
|
+
name: string;
|
|
125
|
+
properties: StyleProperties;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// Flow Diagram Types
|
|
130
|
+
// ============================================================================
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Flow diagram directions
|
|
134
|
+
*/
|
|
135
|
+
export type FlowDirection = 'TB' | 'BT' | 'LR' | 'RL';
|
|
136
|
+
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// Sequence Diagram Types
|
|
139
|
+
// ============================================================================
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Participant types in sequence diagrams
|
|
143
|
+
*/
|
|
144
|
+
export type ParticipantType = 'participant' | 'actor';
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Message arrow types in sequence diagrams
|
|
148
|
+
*/
|
|
149
|
+
export const MESSAGE_ARROWS = {
|
|
150
|
+
sync: '->>', // Synchronous call
|
|
151
|
+
syncReply: '-->>', // Synchronous reply
|
|
152
|
+
async: '-)', // Async call
|
|
153
|
+
asyncReply: '--)', // Async reply
|
|
154
|
+
solid: '->', // Solid line
|
|
155
|
+
dotted: '-->', // Dotted line
|
|
156
|
+
cross: '-x', // Cross (failure)
|
|
157
|
+
crossDotted: '--x', // Dotted cross
|
|
158
|
+
} as const;
|
|
159
|
+
|
|
160
|
+
export type MessageArrowType = keyof typeof MESSAGE_ARROWS;
|
|
161
|
+
|
|
162
|
+
// ============================================================================
|
|
163
|
+
// Journey Diagram Types
|
|
164
|
+
// ============================================================================
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Task score (1-5, where 5 is best)
|
|
168
|
+
*/
|
|
169
|
+
export type TaskScore = 1 | 2 | 3 | 4 | 5;
|
|
170
|
+
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// Utility Types
|
|
173
|
+
// ============================================================================
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Makes all properties in T writable (removes readonly)
|
|
177
|
+
*/
|
|
178
|
+
export type Writable<T> = { -readonly [P in keyof T]: T[P] };
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Extracts keys from a const object
|
|
182
|
+
*/
|
|
183
|
+
export type KeysOf<T> = keyof T & string;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mermaid Diagram Builders
|
|
3
|
+
*
|
|
4
|
+
* Declarative, type-safe builders for creating Mermaid diagrams.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import {
|
|
9
|
+
* FlowDiagram,
|
|
10
|
+
* SequenceDiagram,
|
|
11
|
+
* JourneyDiagram,
|
|
12
|
+
* useStylePresets,
|
|
13
|
+
* useBoxColors
|
|
14
|
+
* } from '@djangocfg/widget-diagram';
|
|
15
|
+
*
|
|
16
|
+
* function MyDiagram() {
|
|
17
|
+
* const presets = useStylePresets();
|
|
18
|
+
* const boxes = useBoxColors();
|
|
19
|
+
*
|
|
20
|
+
* // Flow diagram
|
|
21
|
+
* const flow = FlowDiagram<'A' | 'B'>({ direction: 'LR' });
|
|
22
|
+
* flow.node('A').rect('Start');
|
|
23
|
+
* flow.edge('A').to('B').solid();
|
|
24
|
+
* flow.style.define('highlight', presets.success);
|
|
25
|
+
*
|
|
26
|
+
* // Sequence diagram
|
|
27
|
+
* const { d, rect } = SequenceDiagram({
|
|
28
|
+
* Alice: 'participant',
|
|
29
|
+
* Bob: 'actor',
|
|
30
|
+
* });
|
|
31
|
+
* rect(boxes.primary, () => {
|
|
32
|
+
* d.Alice.sync.Bob.msg('Hello!');
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* // Journey diagram
|
|
36
|
+
* const journey = JourneyDiagram({ title: 'User Flow' });
|
|
37
|
+
* journey.section('Start').task('Open app', 5, 'User');
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @module Mermaid/builders
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
// Core
|
|
45
|
+
export { DiagramStore, type DiagramStoreOptions } from './core/DiagramStore';
|
|
46
|
+
export { sanitizeLabel, toNodeId, escapeQuoted, formatLabel } from './core/sanitize';
|
|
47
|
+
export * from './core/types';
|
|
48
|
+
|
|
49
|
+
// Theme hooks and utilities (re-exported from @djangocfg/ui-core/styles/palette)
|
|
50
|
+
export {
|
|
51
|
+
useThemePalette,
|
|
52
|
+
useStylePresets,
|
|
53
|
+
useBoxColors,
|
|
54
|
+
hslToHex,
|
|
55
|
+
hslToRgbString,
|
|
56
|
+
hslToRgba,
|
|
57
|
+
type ThemePalette,
|
|
58
|
+
type StyleColors,
|
|
59
|
+
type StylePresets,
|
|
60
|
+
type BoxColors,
|
|
61
|
+
} from './core/theme';
|
|
62
|
+
|
|
63
|
+
// FlowDiagram
|
|
64
|
+
export { FlowDiagram, STYLE_PRESETS } from './FlowDiagram';
|
|
65
|
+
export type {
|
|
66
|
+
FlowDiagramOptions,
|
|
67
|
+
FlowDiagramBuilder,
|
|
68
|
+
NodeBuilder,
|
|
69
|
+
EdgeBuilder,
|
|
70
|
+
EdgeEndBuilder,
|
|
71
|
+
StyleBuilder,
|
|
72
|
+
SubgraphBuilder,
|
|
73
|
+
} from './FlowDiagram';
|
|
74
|
+
|
|
75
|
+
// SequenceDiagram
|
|
76
|
+
export { SequenceDiagram } from './SequenceDiagram';
|
|
77
|
+
export type {
|
|
78
|
+
ParticipantsObject,
|
|
79
|
+
SequenceDiagramOptions,
|
|
80
|
+
SequenceDiagramBuilder,
|
|
81
|
+
MessageBuilder,
|
|
82
|
+
MessageArrows,
|
|
83
|
+
MessageTarget,
|
|
84
|
+
MessageAction,
|
|
85
|
+
NoteBuilder,
|
|
86
|
+
ActivationBuilder,
|
|
87
|
+
} from './SequenceDiagram';
|
|
88
|
+
|
|
89
|
+
// JourneyDiagram
|
|
90
|
+
export { JourneyDiagram } from './JourneyDiagram';
|
|
91
|
+
export type {
|
|
92
|
+
JourneyDiagramOptions,
|
|
93
|
+
JourneyDiagramBuilder,
|
|
94
|
+
SectionBuilder,
|
|
95
|
+
TaskDefinition,
|
|
96
|
+
} from './JourneyDiagram';
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
interface MermaidCodeViewerProps {
|
|
6
|
+
chart: string;
|
|
7
|
+
renderPreview: () => React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const MermaidCodeViewer: React.FC<MermaidCodeViewerProps> = ({
|
|
11
|
+
chart,
|
|
12
|
+
renderPreview,
|
|
13
|
+
}) => {
|
|
14
|
+
const [activeTab, setActiveTab] = useState<'preview' | 'code'>('preview');
|
|
15
|
+
const [copied, setCopied] = useState(false);
|
|
16
|
+
|
|
17
|
+
const handleCopy = async () => {
|
|
18
|
+
await navigator.clipboard.writeText(chart);
|
|
19
|
+
setCopied(true);
|
|
20
|
+
setTimeout(() => setCopied(false), 2000);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="flex flex-col h-full">
|
|
25
|
+
{/* Tabs */}
|
|
26
|
+
<div className="flex items-center justify-between border-b border-border px-4">
|
|
27
|
+
<div className="flex">
|
|
28
|
+
<button
|
|
29
|
+
onClick={() => setActiveTab('preview')}
|
|
30
|
+
className={`px-4 py-3 text-sm font-medium transition-colors relative ${
|
|
31
|
+
activeTab === 'preview'
|
|
32
|
+
? 'text-foreground'
|
|
33
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
34
|
+
}`}
|
|
35
|
+
>
|
|
36
|
+
Preview
|
|
37
|
+
{activeTab === 'preview' && (
|
|
38
|
+
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary" />
|
|
39
|
+
)}
|
|
40
|
+
</button>
|
|
41
|
+
<button
|
|
42
|
+
onClick={() => setActiveTab('code')}
|
|
43
|
+
className={`px-4 py-3 text-sm font-medium transition-colors relative ${
|
|
44
|
+
activeTab === 'code'
|
|
45
|
+
? 'text-foreground'
|
|
46
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
47
|
+
}`}
|
|
48
|
+
>
|
|
49
|
+
Code
|
|
50
|
+
{activeTab === 'code' && (
|
|
51
|
+
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary" />
|
|
52
|
+
)}
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{/* Copy button - show only on Code tab */}
|
|
57
|
+
{activeTab === 'code' && (
|
|
58
|
+
<button
|
|
59
|
+
onClick={handleCopy}
|
|
60
|
+
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"
|
|
61
|
+
>
|
|
62
|
+
{copied ? (
|
|
63
|
+
<>
|
|
64
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
65
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
66
|
+
</svg>
|
|
67
|
+
Copied!
|
|
68
|
+
</>
|
|
69
|
+
) : (
|
|
70
|
+
<>
|
|
71
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
72
|
+
<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" />
|
|
73
|
+
</svg>
|
|
74
|
+
Copy
|
|
75
|
+
</>
|
|
76
|
+
)}
|
|
77
|
+
</button>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{/* Content */}
|
|
82
|
+
<div className="flex-1 overflow-auto">
|
|
83
|
+
{activeTab === 'preview' ? (
|
|
84
|
+
<div className="p-6 flex items-center justify-center min-h-full">
|
|
85
|
+
{renderPreview()}
|
|
86
|
+
</div>
|
|
87
|
+
) : (
|
|
88
|
+
<pre className="p-6 text-sm font-mono text-foreground bg-muted/30 h-full overflow-auto">
|
|
89
|
+
<code>{chart}</code>
|
|
90
|
+
</pre>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { AlertTriangle } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
interface MermaidErrorPanelProps {
|
|
7
|
+
/** Human-readable error message from the Mermaid parser. */
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Inline error panel shown when a Mermaid diagram fails to parse.
|
|
13
|
+
*
|
|
14
|
+
* Replaces the previous `innerHTML`-injected markup — keeps rendering in
|
|
15
|
+
* React, uses semantic `destructive` tokens, and stays accessible
|
|
16
|
+
* (`role="alert"`).
|
|
17
|
+
*/
|
|
18
|
+
export const MermaidErrorPanel: React.FC<MermaidErrorPanelProps> = ({ message }) => {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
role="alert"
|
|
22
|
+
className="flex items-start gap-3 rounded-md border border-destructive/30 bg-destructive/10 p-4 text-destructive"
|
|
23
|
+
>
|
|
24
|
+
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" aria-hidden="true" />
|
|
25
|
+
<div className="min-w-0">
|
|
26
|
+
<p className="text-sm font-semibold">Diagram syntax error</p>
|
|
27
|
+
<p className="mt-0.5 break-words text-sm text-destructive/90">{message}</p>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
};
|