@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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/package.json +77 -0
  4. package/src/FloatingToolbar/FloatingToolbar.css +5 -0
  5. package/src/FloatingToolbar/actions/CopyAction.tsx +31 -0
  6. package/src/FloatingToolbar/actions/DownloadAction.tsx +51 -0
  7. package/src/FloatingToolbar/actions/ExpandAction.tsx +33 -0
  8. package/src/FloatingToolbar/actions/FullscreenAction.tsx +38 -0
  9. package/src/FloatingToolbar/actions/index.ts +4 -0
  10. package/src/FloatingToolbar/hooks/useScrollIsolation.ts +62 -0
  11. package/src/FloatingToolbar/index.tsx +184 -0
  12. package/src/Mermaid.client.tsx +97 -0
  13. package/src/builders/FlowDiagram/FlowDiagram.ts +96 -0
  14. package/src/builders/FlowDiagram/functions/getEdges.ts +50 -0
  15. package/src/builders/FlowDiagram/functions/getNodes.ts +43 -0
  16. package/src/builders/FlowDiagram/functions/getStyles.ts +90 -0
  17. package/src/builders/FlowDiagram/functions/index.ts +8 -0
  18. package/src/builders/FlowDiagram/index.ts +16 -0
  19. package/src/builders/FlowDiagram/types.ts +130 -0
  20. package/src/builders/JourneyDiagram/JourneyDiagram.ts +88 -0
  21. package/src/builders/JourneyDiagram/index.ts +12 -0
  22. package/src/builders/JourneyDiagram/types.ts +48 -0
  23. package/src/builders/SequenceDiagram/SequenceDiagram.ts +158 -0
  24. package/src/builders/SequenceDiagram/functions/getActivations.ts +30 -0
  25. package/src/builders/SequenceDiagram/functions/getBlocks.ts +112 -0
  26. package/src/builders/SequenceDiagram/functions/getMessages.ts +85 -0
  27. package/src/builders/SequenceDiagram/functions/getNotes.ts +94 -0
  28. package/src/builders/SequenceDiagram/functions/index.ts +16 -0
  29. package/src/builders/SequenceDiagram/index.ts +18 -0
  30. package/src/builders/SequenceDiagram/types.ts +192 -0
  31. package/src/builders/core/DiagramStore.ts +138 -0
  32. package/src/builders/core/index.ts +8 -0
  33. package/src/builders/core/sanitize.ts +83 -0
  34. package/src/builders/core/theme.ts +42 -0
  35. package/src/builders/core/types.ts +183 -0
  36. package/src/builders/index.ts +96 -0
  37. package/src/components/MermaidCodeViewer.tsx +95 -0
  38. package/src/components/MermaidErrorPanel.tsx +31 -0
  39. package/src/components/MermaidFullscreenModal.tsx +201 -0
  40. package/src/hooks/index.ts +4 -0
  41. package/src/hooks/useMermaidCleanup.ts +70 -0
  42. package/src/hooks/useMermaidFullscreen.ts +46 -0
  43. package/src/hooks/useMermaidRenderer.ts +329 -0
  44. package/src/hooks/useMermaidValidation.ts +97 -0
  45. package/src/index.tsx +79 -0
  46. package/src/lazy.tsx +40 -0
  47. package/src/mermaid.stories.tsx +217 -0
  48. package/src/types.ts +28 -0
  49. package/src/utils/mermaid-helpers.ts +157 -0
@@ -0,0 +1,97 @@
1
+ 'use client';
2
+
3
+ import React, { useRef } from 'react';
4
+
5
+ import { useResolvedTheme } from '@djangocfg/ui-core/hooks';
6
+ import { FloatingToolbar } from './FloatingToolbar';
7
+ import { CopyAction, FullscreenAction } from './FloatingToolbar/actions';
8
+ import { MermaidErrorPanel } from './components/MermaidErrorPanel';
9
+ import { MermaidFullscreenModal } from './components/MermaidFullscreenModal';
10
+ import { useMermaidFullscreen } from './hooks/useMermaidFullscreen';
11
+ import { useMermaidRenderer } from './hooks/useMermaidRenderer';
12
+ import type { MermaidProps } from './types';
13
+
14
+
15
+ const Mermaid: React.FC<MermaidProps> = ({
16
+ chart,
17
+ className = '',
18
+ isCompact = false,
19
+ fullscreen = true,
20
+ scrollIsolation = false,
21
+ debounceMs = 300,
22
+ }) => {
23
+ const containerRef = useRef<HTMLDivElement>(null);
24
+ const theme = useResolvedTheme();
25
+
26
+ // Rendering logic
27
+ const { mermaidRef, svgContent, isVertical, isRendering, error } = useMermaidRenderer({
28
+ chart,
29
+ theme,
30
+ isCompact,
31
+ debounceMs,
32
+ });
33
+
34
+ // Fullscreen modal logic (only used if fullscreen prop is true)
35
+ const {
36
+ isFullscreen,
37
+ fullscreenRef,
38
+ openFullscreen,
39
+ closeFullscreen,
40
+ handleBackdropClick,
41
+ } = useMermaidFullscreen();
42
+
43
+ // Hoist derived UI state out of JSX (data-before-JSX).
44
+ const hasError = error !== null;
45
+ // The spinner needs a box to sit in while the first diagram renders
46
+ // (empty SVG host has no intrinsic height).
47
+ const showPlaceholderHeight = isRendering && !svgContent && !hasError;
48
+ const showToolbar = !!svgContent && !isRendering && !hasError;
49
+
50
+ return (
51
+ <>
52
+ <div ref={containerRef} className={`relative ${className}`}>
53
+ {hasError && <MermaidErrorPanel message={error} />}
54
+
55
+ <div
56
+ ref={mermaidRef}
57
+ className="flex justify-center items-center"
58
+ style={{
59
+ isolation: 'isolate',
60
+ minHeight: showPlaceholderHeight ? 100 : undefined,
61
+ display: hasError ? 'none' : undefined,
62
+ }}
63
+ />
64
+
65
+ {isRendering && !hasError && (
66
+ <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
67
+ <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary" />
68
+ </div>
69
+ )}
70
+
71
+ {showToolbar && (
72
+ <FloatingToolbar containerRef={containerRef} scrollIsolation={scrollIsolation}>
73
+ <CopyAction value={chart} title="Copy source" />
74
+ {fullscreen && (
75
+ <FullscreenAction onToggle={openFullscreen} title="Fullscreen" />
76
+ )}
77
+ </FloatingToolbar>
78
+ )}
79
+ </div>
80
+
81
+ {fullscreen && (
82
+ <MermaidFullscreenModal
83
+ isOpen={isFullscreen}
84
+ svgContent={svgContent}
85
+ isVertical={isVertical}
86
+ theme={theme}
87
+ chart={chart}
88
+ fullscreenRef={fullscreenRef}
89
+ onClose={closeFullscreen}
90
+ onBackdropClick={handleBackdropClick}
91
+ />
92
+ )}
93
+ </>
94
+ );
95
+ };
96
+
97
+ export default Mermaid;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * FlowDiagram Builder
3
+ * Declarative API for building Mermaid flowchart diagrams
4
+ * @module Mermaid/builders/FlowDiagram/FlowDiagram
5
+ */
6
+
7
+ import { DiagramStore } from '../core/DiagramStore';
8
+ import type { FlowDirection } from '../core/types';
9
+ import { createNodeBuilder } from './functions/getNodes';
10
+ import { createEdgeBuilder } from './functions/getEdges';
11
+ import { createStyleBuilder } from './functions/getStyles';
12
+ import type {
13
+ FlowDiagramOptions,
14
+ FlowDiagramBuilder,
15
+ SubgraphBuilder,
16
+ NodeBuilder,
17
+ } from './types';
18
+
19
+ const DEFAULT_OPTIONS: Required<FlowDiagramOptions> = {
20
+ direction: 'TB',
21
+ };
22
+
23
+ /**
24
+ * Create a FlowDiagram builder
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const flow = FlowDiagram<'A' | 'B' | 'C'>({ direction: 'LR' });
29
+ *
30
+ * flow.node('A').rect('Start');
31
+ * flow.node('B').circle('Process');
32
+ * flow.node('C').rect('End');
33
+ *
34
+ * flow.edge('A').to('B').solid();
35
+ * flow.edge('B').to('C').dotted('optional');
36
+ *
37
+ * flow.style.define('highlight', { fill: '#ff0', stroke: '#f00' });
38
+ * flow.style.apply('highlight', 'B');
39
+ *
40
+ * console.log(flow.toString());
41
+ * ```
42
+ *
43
+ * @param options - Diagram options
44
+ * @returns FlowDiagram builder instance
45
+ */
46
+ export function FlowDiagram<Nodes extends string = string>(
47
+ options: FlowDiagramOptions = {},
48
+ ): FlowDiagramBuilder<Nodes> {
49
+ const opts = { ...DEFAULT_OPTIONS, ...options };
50
+ const store = new DiagramStore(`graph ${opts.direction}`);
51
+ const styleBuilder = createStyleBuilder(store);
52
+
53
+ /**
54
+ * Create a subgraph builder that operates within the subgraph context
55
+ */
56
+ const createSubgraphBuilder = (): SubgraphBuilder<Nodes> => ({
57
+ direction(dir: FlowDirection) {
58
+ store.add(`direction ${dir}`);
59
+ },
60
+ node(id: Nodes): NodeBuilder<Nodes> {
61
+ return createNodeBuilder<Nodes>(store, id);
62
+ },
63
+ });
64
+
65
+ return {
66
+ node(id: Nodes): NodeBuilder<Nodes> {
67
+ return createNodeBuilder<Nodes>(store, id);
68
+ },
69
+
70
+ edge(from: Nodes) {
71
+ return createEdgeBuilder<Nodes>(store, from);
72
+ },
73
+
74
+ subgraph(name: string, fn: (sub: SubgraphBuilder<Nodes>) => void) {
75
+ store.add(`subgraph ${name}`);
76
+ store.indent();
77
+ fn(createSubgraphBuilder());
78
+ store.dedent();
79
+ store.add('end');
80
+ },
81
+
82
+ style: styleBuilder,
83
+
84
+ comment(text: string) {
85
+ store.addComment(text);
86
+ },
87
+
88
+ blank() {
89
+ store.addBlank();
90
+ },
91
+
92
+ toString() {
93
+ return store.toString();
94
+ },
95
+ };
96
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Edge builder functions for FlowDiagram
3
+ * @module Mermaid/builders/FlowDiagram/functions/getEdges
4
+ */
5
+
6
+ import { DiagramStore } from '../../core/DiagramStore';
7
+ import { SIMPLE_EDGE_SYNTAX } from '../../core/types';
8
+ import { toNodeId, sanitizeLabel } from '../../core/sanitize';
9
+ import type { EdgeBuilder, EdgeEndBuilder } from '../types';
10
+
11
+ /**
12
+ * Create an edge builder for a specific source node
13
+ */
14
+ export function createEdgeBuilder<Nodes extends string>(
15
+ store: DiagramStore,
16
+ fromId: Nodes,
17
+ ): EdgeBuilder<Nodes> {
18
+ const safeFromId = toNodeId(fromId);
19
+
20
+ return {
21
+ to(toId: Nodes): EdgeEndBuilder {
22
+ const safeToId = toNodeId(toId);
23
+
24
+ const addEdge = (arrow: string, label?: string) => {
25
+ if (label) {
26
+ store.add(`${safeFromId} ${arrow}|${sanitizeLabel(label)}| ${safeToId}`);
27
+ } else {
28
+ store.add(`${safeFromId} ${arrow} ${safeToId}`);
29
+ }
30
+ };
31
+
32
+ return {
33
+ solid: (label) => addEdge(SIMPLE_EDGE_SYNTAX.solid, label),
34
+ dotted: (label) => addEdge(SIMPLE_EDGE_SYNTAX.dotted, label),
35
+ thick: (label) => addEdge(SIMPLE_EDGE_SYNTAX.thick, label),
36
+ line: (label) => addEdge(SIMPLE_EDGE_SYNTAX.solidOpen, label),
37
+ dottedLine: (label) => addEdge(SIMPLE_EDGE_SYNTAX.dottedOpen, label),
38
+ };
39
+ },
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Create multiple edges at once (for convenience)
45
+ */
46
+ export function createEdgesBuilder<Nodes extends string>(
47
+ store: DiagramStore,
48
+ ): (from: Nodes) => EdgeBuilder<Nodes> {
49
+ return (from: Nodes) => createEdgeBuilder(store, from);
50
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Node builder functions for FlowDiagram
3
+ * @module Mermaid/builders/FlowDiagram/functions/getNodes
4
+ */
5
+
6
+ import { DiagramStore } from '../../core/DiagramStore';
7
+ import { NODE_SHAPE_SYNTAX, type NodeShape } from '../../core/types';
8
+ import { formatLabel, toNodeId } from '../../core/sanitize';
9
+ import type { NodeBuilder } from '../types';
10
+
11
+ /**
12
+ * Create a node builder for a specific node ID
13
+ */
14
+ export function createNodeBuilder<Nodes extends string>(
15
+ store: DiagramStore,
16
+ nodeId: Nodes,
17
+ ): NodeBuilder<Nodes> {
18
+ const safeId = toNodeId(nodeId);
19
+
20
+ const addNode = (shape: NodeShape, label: string, subtitle?: string) => {
21
+ const [open, close] = NODE_SHAPE_SYNTAX[shape];
22
+ const formattedLabel = formatLabel(label, subtitle);
23
+
24
+ // Use quoted format for complex labels
25
+ if (subtitle || label.includes(' ') || label.includes(':')) {
26
+ store.add(`${safeId}${open}"${formattedLabel}"${close}`);
27
+ } else {
28
+ store.add(`${safeId}${open}${formattedLabel}${close}`);
29
+ }
30
+ };
31
+
32
+ return {
33
+ rect: (label, subtitle) => addNode('rect', label, subtitle),
34
+ round: (label, subtitle) => addNode('round', label, subtitle),
35
+ stadium: (label, subtitle) => addNode('stadium', label, subtitle),
36
+ circle: (label, subtitle) => addNode('circle', label, subtitle),
37
+ rhombus: (label, subtitle) => addNode('rhombus', label, subtitle),
38
+ hexagon: (label, subtitle) => addNode('hexagon', label, subtitle),
39
+ cylinder: (label, subtitle) => addNode('cylinder', label, subtitle),
40
+ subroutine: (label, subtitle) => addNode('subroutine', label, subtitle),
41
+ doubleCircle: (label, subtitle) => addNode('doubleCircle', label, subtitle),
42
+ };
43
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Style builder functions for FlowDiagram
3
+ * @module Mermaid/builders/FlowDiagram/functions/getStyles
4
+ */
5
+
6
+ import { DiagramStore } from '../../core/DiagramStore';
7
+ import type { StyleProperties } from '../../core/types';
8
+ import { toNodeId } from '../../core/sanitize';
9
+ import type { StyleBuilder } from '../types';
10
+
11
+ /**
12
+ * Convert style properties to Mermaid classDef syntax
13
+ */
14
+ function propertiesToString(properties: StyleProperties): string {
15
+ const parts: string[] = [];
16
+
17
+ if (properties.fill) parts.push(`fill:${properties.fill}`);
18
+ if (properties.stroke) parts.push(`stroke:${properties.stroke}`);
19
+ if (properties['stroke-width']) parts.push(`stroke-width:${properties['stroke-width']}`);
20
+ if (properties.color) parts.push(`color:${properties.color}`);
21
+ if (properties['font-weight']) parts.push(`font-weight:${properties['font-weight']}`);
22
+ if (properties['stroke-dasharray']) parts.push(`stroke-dasharray:${properties['stroke-dasharray']}`);
23
+
24
+ return parts.join(',');
25
+ }
26
+
27
+ /**
28
+ * Create a style builder
29
+ */
30
+ export function createStyleBuilder(store: DiagramStore): StyleBuilder {
31
+ return {
32
+ define(name: string, properties: StyleProperties): void {
33
+ const propsStr = propertiesToString(properties);
34
+ store.add(`classDef ${name} ${propsStr}`);
35
+ },
36
+
37
+ apply(className: string, ...nodeIds: string[]): void {
38
+ if (nodeIds.length === 0) return;
39
+ const safeIds = nodeIds.map((id) => toNodeId(id)).join(',');
40
+ store.add(`class ${safeIds} ${className}`);
41
+ },
42
+ };
43
+ }
44
+
45
+ // ============================================================================
46
+ // Predefined Style Classes (Common patterns)
47
+ // ============================================================================
48
+
49
+ /**
50
+ * Common style presets for quick use
51
+ */
52
+ export const STYLE_PRESETS = {
53
+ /** Green success style */
54
+ success: {
55
+ fill: '#22c55e',
56
+ stroke: '#16a34a',
57
+ color: '#fff',
58
+ },
59
+ /** Blue primary style */
60
+ primary: {
61
+ fill: '#3b82f6',
62
+ stroke: '#1d4ed8',
63
+ color: '#fff',
64
+ },
65
+ /** Red error/danger style */
66
+ danger: {
67
+ fill: '#ef4444',
68
+ stroke: '#dc2626',
69
+ color: '#fff',
70
+ },
71
+ /** Yellow warning style */
72
+ warning: {
73
+ fill: '#f59e0b',
74
+ stroke: '#d97706',
75
+ color: '#fff',
76
+ },
77
+ /** Gray muted style */
78
+ muted: {
79
+ fill: '#6b7280',
80
+ stroke: '#4b5563',
81
+ color: '#fff',
82
+ },
83
+ /** Emerald/teal accent */
84
+ accent: {
85
+ fill: '#10b981',
86
+ stroke: '#047857',
87
+ color: '#fff',
88
+ 'font-weight': 'bold' as const,
89
+ },
90
+ } satisfies Record<string, StyleProperties>;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * FlowDiagram function exports
3
+ * @module Mermaid/builders/FlowDiagram/functions
4
+ */
5
+
6
+ export { createNodeBuilder } from './getNodes';
7
+ export { createEdgeBuilder, createEdgesBuilder } from './getEdges';
8
+ export { createStyleBuilder, STYLE_PRESETS } from './getStyles';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * FlowDiagram builder exports
3
+ * @module Mermaid/builders/FlowDiagram
4
+ */
5
+
6
+ export { FlowDiagram } from './FlowDiagram';
7
+ export { STYLE_PRESETS } from './functions/getStyles';
8
+ export type {
9
+ FlowDiagramOptions,
10
+ FlowDiagramBuilder,
11
+ NodeBuilder,
12
+ EdgeBuilder,
13
+ EdgeEndBuilder,
14
+ StyleBuilder,
15
+ SubgraphBuilder,
16
+ } from './types';
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Types for FlowDiagram builder
3
+ * @module Mermaid/builders/FlowDiagram/types
4
+ */
5
+
6
+ import type { FlowDirection, NodeShape, StyleProperties } from '../core/types';
7
+
8
+ /**
9
+ * Options for FlowDiagram builder
10
+ */
11
+ export interface FlowDiagramOptions {
12
+ /** Diagram direction (default: 'TB') */
13
+ direction?: FlowDirection;
14
+ }
15
+
16
+ /**
17
+ * Node definition
18
+ */
19
+ export interface NodeDefinition {
20
+ id: string;
21
+ label: string;
22
+ shape: NodeShape;
23
+ subtitle?: string;
24
+ }
25
+
26
+ /**
27
+ * Edge definition
28
+ */
29
+ export interface EdgeDefinition {
30
+ from: string;
31
+ to: string;
32
+ label?: string;
33
+ style: 'solid' | 'dotted' | 'thick';
34
+ }
35
+
36
+ /**
37
+ * Style class definition for flow diagrams
38
+ */
39
+ export interface FlowStyleClass {
40
+ name: string;
41
+ properties: StyleProperties;
42
+ }
43
+
44
+ /**
45
+ * Node builder interface - chainable API for creating nodes
46
+ */
47
+ export interface NodeBuilder<Nodes extends string> {
48
+ /** Create a rectangular node [label] */
49
+ rect(label: string, subtitle?: string): void;
50
+ /** Create a rounded node (label) */
51
+ round(label: string, subtitle?: string): void;
52
+ /** Create a stadium/pill node ([label]) */
53
+ stadium(label: string, subtitle?: string): void;
54
+ /** Create a circular node ((label)) */
55
+ circle(label: string, subtitle?: string): void;
56
+ /** Create a rhombus/diamond node {label} */
57
+ rhombus(label: string, subtitle?: string): void;
58
+ /** Create a hexagon node {{label}} */
59
+ hexagon(label: string, subtitle?: string): void;
60
+ /** Create a cylinder/database node [(label)] */
61
+ cylinder(label: string, subtitle?: string): void;
62
+ /** Create a subroutine node [[label]] */
63
+ subroutine(label: string, subtitle?: string): void;
64
+ /** Create a double circle node (((label))) */
65
+ doubleCircle(label: string, subtitle?: string): void;
66
+ }
67
+
68
+ /**
69
+ * Edge builder interface - chainable API for creating edges
70
+ */
71
+ export interface EdgeBuilder<Nodes extends string> {
72
+ /** Target node for the edge */
73
+ to(target: Nodes): EdgeEndBuilder;
74
+ }
75
+
76
+ /**
77
+ * Edge end builder - configures the edge after target is specified
78
+ */
79
+ export interface EdgeEndBuilder {
80
+ /** Solid arrow --> */
81
+ solid(label?: string): void;
82
+ /** Dotted arrow -.-> */
83
+ dotted(label?: string): void;
84
+ /** Thick arrow ==> */
85
+ thick(label?: string): void;
86
+ /** Solid line without arrow --- */
87
+ line(label?: string): void;
88
+ /** Dotted line without arrow -.- */
89
+ dottedLine(label?: string): void;
90
+ }
91
+
92
+ /**
93
+ * Style builder interface
94
+ */
95
+ export interface StyleBuilder {
96
+ /** Define a style class */
97
+ define(name: string, properties: StyleProperties): void;
98
+ /** Apply a class to nodes */
99
+ apply(className: string, ...nodeIds: string[]): void;
100
+ }
101
+
102
+ /**
103
+ * Subgraph builder interface
104
+ */
105
+ export interface SubgraphBuilder<Nodes extends string> {
106
+ /** Set direction inside subgraph */
107
+ direction(dir: FlowDirection): void;
108
+ /** Create a node inside subgraph */
109
+ node(id: Nodes): NodeBuilder<Nodes>;
110
+ }
111
+
112
+ /**
113
+ * Main FlowDiagram builder result
114
+ */
115
+ export interface FlowDiagramBuilder<Nodes extends string> {
116
+ /** Create a node */
117
+ node: (id: Nodes) => NodeBuilder<Nodes>;
118
+ /** Create an edge from a node */
119
+ edge: (from: Nodes) => EdgeBuilder<Nodes>;
120
+ /** Create a subgraph */
121
+ subgraph: (name: string, fn: (sub: SubgraphBuilder<Nodes>) => void) => void;
122
+ /** Style utilities */
123
+ style: StyleBuilder;
124
+ /** Add a comment */
125
+ comment: (text: string) => void;
126
+ /** Add a blank line */
127
+ blank: () => void;
128
+ /** Get the Mermaid string */
129
+ toString: () => string;
130
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * JourneyDiagram Builder
3
+ * Declarative API for building Mermaid user journey diagrams
4
+ * @module Mermaid/builders/JourneyDiagram/JourneyDiagram
5
+ */
6
+
7
+ import { DiagramStore } from '../core/DiagramStore';
8
+ import type { TaskScore } from '../core/types';
9
+ import { sanitizeLabel } from '../core/sanitize';
10
+ import type { JourneyDiagramOptions, JourneyDiagramBuilder, SectionBuilder } from './types';
11
+
12
+ /**
13
+ * Format actors for journey task
14
+ */
15
+ function formatActors(actors: string | string[]): string {
16
+ if (Array.isArray(actors)) {
17
+ return actors.map(sanitizeLabel).join(', ');
18
+ }
19
+ return sanitizeLabel(actors);
20
+ }
21
+
22
+ /**
23
+ * Create a JourneyDiagram builder
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const journey = JourneyDiagram({ title: 'User Registration Flow' });
28
+ *
29
+ * journey.section('Discovery')
30
+ * .task('Visit website', 5, 'User')
31
+ * .task('Read about product', 4, 'User');
32
+ *
33
+ * journey.section('Registration')
34
+ * .task('Click sign up', 5, 'User')
35
+ * .task('Fill form', 2, 'User')
36
+ * .task('Verify email', 3, ['User', 'System']);
37
+ *
38
+ * journey.section('Onboarding')
39
+ * .task('Complete profile', 4, 'User')
40
+ * .task('Start using app', 5, 'User');
41
+ *
42
+ * console.log(journey.toString());
43
+ * ```
44
+ *
45
+ * @param options - Diagram options (title is required)
46
+ * @returns JourneyDiagram builder instance
47
+ */
48
+ export function JourneyDiagram(options: JourneyDiagramOptions): JourneyDiagramBuilder {
49
+ const store = new DiagramStore('journey');
50
+ store.add(`title ${sanitizeLabel(options.title)}`);
51
+
52
+ /**
53
+ * Add a task line
54
+ */
55
+ const addTask = (name: string, score: TaskScore, actors: string | string[]) => {
56
+ const actorStr = formatActors(actors);
57
+ store.add(`${sanitizeLabel(name)}: ${score}: ${actorStr}`);
58
+ };
59
+
60
+ /**
61
+ * Create a section builder for chaining tasks
62
+ */
63
+ const createSectionBuilder = (): SectionBuilder => ({
64
+ task(name: string, score: TaskScore, actors: string | string[]) {
65
+ addTask(name, score, actors);
66
+ return this;
67
+ },
68
+ });
69
+
70
+ return {
71
+ section(name: string) {
72
+ store.add(`section ${sanitizeLabel(name)}`);
73
+ return createSectionBuilder();
74
+ },
75
+
76
+ task(name: string, score: TaskScore, actors: string | string[]) {
77
+ addTask(name, score, actors);
78
+ },
79
+
80
+ comment(text: string) {
81
+ store.addComment(text);
82
+ },
83
+
84
+ toString() {
85
+ return store.toString();
86
+ },
87
+ };
88
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * JourneyDiagram builder exports
3
+ * @module Mermaid/builders/JourneyDiagram
4
+ */
5
+
6
+ export { JourneyDiagram } from './JourneyDiagram';
7
+ export type {
8
+ JourneyDiagramOptions,
9
+ JourneyDiagramBuilder,
10
+ SectionBuilder,
11
+ TaskDefinition,
12
+ } from './types';
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Types for JourneyDiagram builder
3
+ * @module Mermaid/builders/JourneyDiagram/types
4
+ */
5
+
6
+ import type { TaskScore } from '../core/types';
7
+
8
+ /**
9
+ * Options for JourneyDiagram builder
10
+ */
11
+ export interface JourneyDiagramOptions {
12
+ /** Diagram title */
13
+ title: string;
14
+ }
15
+
16
+ /**
17
+ * Task definition
18
+ */
19
+ export interface TaskDefinition {
20
+ /** Task name/description */
21
+ name: string;
22
+ /** Score (1-5, where 5 is best) */
23
+ score: TaskScore;
24
+ /** Actor(s) involved */
25
+ actors: string | string[];
26
+ }
27
+
28
+ /**
29
+ * Section builder interface
30
+ */
31
+ export interface SectionBuilder {
32
+ /** Add a task to the current section */
33
+ task(name: string, score: TaskScore, actors: string | string[]): SectionBuilder;
34
+ }
35
+
36
+ /**
37
+ * Main JourneyDiagram builder result
38
+ */
39
+ export interface JourneyDiagramBuilder {
40
+ /** Add a new section */
41
+ section(name: string): SectionBuilder;
42
+ /** Add a task directly (without section - goes to last section or creates implicit one) */
43
+ task(name: string, score: TaskScore, actors: string | string[]): void;
44
+ /** Add a comment */
45
+ comment(text: string): void;
46
+ /** Get the Mermaid string */
47
+ toString(): string;
48
+ }