@djangocfg/ui-tools 2.1.157 → 2.1.159
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -3
- package/dist/{Mermaid.client-KMNEPBQJ.cjs → Mermaid.client-2TAFAXPW.cjs} +100 -143
- package/dist/Mermaid.client-2TAFAXPW.cjs.map +1 -0
- package/dist/{Mermaid.client-3WPNJ4DF.mjs → Mermaid.client-HG24D5KB.mjs} +100 -143
- package/dist/Mermaid.client-HG24D5KB.mjs.map +1 -0
- package/dist/index.cjs +4 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +4 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -6
- package/src/tools/Mermaid/Mermaid.client.tsx +40 -56
- package/src/tools/Mermaid/Mermaid.story.tsx +195 -110
- package/src/tools/Mermaid/builders/FlowDiagram/FlowDiagram.ts +96 -0
- package/src/tools/Mermaid/builders/FlowDiagram/functions/getEdges.ts +50 -0
- package/src/tools/Mermaid/builders/FlowDiagram/functions/getNodes.ts +43 -0
- package/src/tools/Mermaid/builders/FlowDiagram/functions/getStyles.ts +90 -0
- package/src/tools/Mermaid/builders/FlowDiagram/functions/index.ts +8 -0
- package/src/tools/Mermaid/builders/FlowDiagram/index.ts +16 -0
- package/src/tools/Mermaid/builders/FlowDiagram/types.ts +130 -0
- package/src/tools/Mermaid/builders/JourneyDiagram/JourneyDiagram.ts +88 -0
- package/src/tools/Mermaid/builders/JourneyDiagram/index.ts +12 -0
- package/src/tools/Mermaid/builders/JourneyDiagram/types.ts +48 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/SequenceDiagram.ts +123 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/functions/getActivations.ts +30 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/functions/getBlocks.ts +112 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/functions/getMessages.ts +85 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/functions/getNotes.ts +94 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/functions/index.ts +16 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/index.ts +17 -0
- package/src/tools/Mermaid/builders/SequenceDiagram/types.ts +147 -0
- package/src/tools/Mermaid/builders/core/DiagramStore.ts +138 -0
- package/src/tools/Mermaid/builders/core/index.ts +8 -0
- package/src/tools/Mermaid/builders/core/sanitize.ts +78 -0
- package/src/tools/Mermaid/builders/core/theme.ts +42 -0
- package/src/tools/Mermaid/builders/core/types.ts +183 -0
- package/src/tools/Mermaid/builders/index.ts +96 -0
- package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +78 -54
- package/src/tools/Mermaid/index.tsx +51 -12
- package/dist/Mermaid.client-3WPNJ4DF.mjs.map +0 -1
- package/dist/Mermaid.client-KMNEPBQJ.cjs.map +0 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message builder functions for SequenceDiagram
|
|
3
|
+
* @module Mermaid/builders/SequenceDiagram/functions/getMessages
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DiagramStore } from '../../core/DiagramStore';
|
|
7
|
+
import { MESSAGE_ARROWS } from '../../core/types';
|
|
8
|
+
import { sanitizeLabel } from '../../core/sanitize';
|
|
9
|
+
import type { MessageBuilder, MessageAction, MessageTarget, MessageArrows } from '../types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create message actions for a specific from -> arrow -> to combination
|
|
13
|
+
*/
|
|
14
|
+
function createMessageAction(
|
|
15
|
+
store: DiagramStore,
|
|
16
|
+
from: string,
|
|
17
|
+
arrow: string,
|
|
18
|
+
to: string,
|
|
19
|
+
): MessageAction {
|
|
20
|
+
return {
|
|
21
|
+
msg(text: string) {
|
|
22
|
+
store.add(`${from}${arrow}${to}: ${sanitizeLabel(text)}`);
|
|
23
|
+
},
|
|
24
|
+
activate(text: string) {
|
|
25
|
+
store.add(`${from}${arrow}+${to}: ${sanitizeLabel(text)}`);
|
|
26
|
+
},
|
|
27
|
+
deactivate(text: string) {
|
|
28
|
+
store.add(`${from}${arrow}-${to}: ${sanitizeLabel(text)}`);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create message target for a specific from -> arrow combination
|
|
35
|
+
*/
|
|
36
|
+
function createMessageTarget<P extends string>(
|
|
37
|
+
store: DiagramStore,
|
|
38
|
+
from: string,
|
|
39
|
+
arrow: string,
|
|
40
|
+
participants: readonly P[],
|
|
41
|
+
): MessageTarget<P> {
|
|
42
|
+
const target = {} as MessageTarget<P>;
|
|
43
|
+
|
|
44
|
+
participants.forEach((to) => {
|
|
45
|
+
target[to] = createMessageAction(store, from, arrow, to);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return target;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create arrow selector for a specific from participant
|
|
53
|
+
*/
|
|
54
|
+
function createMessageArrows<P extends string>(
|
|
55
|
+
store: DiagramStore,
|
|
56
|
+
from: P,
|
|
57
|
+
participants: readonly P[],
|
|
58
|
+
): MessageArrows<P> {
|
|
59
|
+
return {
|
|
60
|
+
sync: createMessageTarget(store, from, MESSAGE_ARROWS.sync, participants),
|
|
61
|
+
syncReply: createMessageTarget(store, from, MESSAGE_ARROWS.syncReply, participants),
|
|
62
|
+
async: createMessageTarget(store, from, MESSAGE_ARROWS.async, participants),
|
|
63
|
+
asyncReply: createMessageTarget(store, from, MESSAGE_ARROWS.asyncReply, participants),
|
|
64
|
+
solid: createMessageTarget(store, from, MESSAGE_ARROWS.solid, participants),
|
|
65
|
+
dotted: createMessageTarget(store, from, MESSAGE_ARROWS.dotted, participants),
|
|
66
|
+
cross: createMessageTarget(store, from, MESSAGE_ARROWS.cross, participants),
|
|
67
|
+
crossDotted: createMessageTarget(store, from, MESSAGE_ARROWS.crossDotted, participants),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create the full message builder for all participants
|
|
73
|
+
*/
|
|
74
|
+
export function createMessageBuilder<P extends string>(
|
|
75
|
+
store: DiagramStore,
|
|
76
|
+
participants: readonly P[],
|
|
77
|
+
): MessageBuilder<P> {
|
|
78
|
+
const builder = {} as MessageBuilder<P>;
|
|
79
|
+
|
|
80
|
+
participants.forEach((from) => {
|
|
81
|
+
builder[from] = createMessageArrows(store, from, participants);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return builder;
|
|
85
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Note builder functions for SequenceDiagram
|
|
3
|
+
* @module Mermaid/builders/SequenceDiagram/functions/getNotes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DiagramStore } from '../../core/DiagramStore';
|
|
7
|
+
import { sanitizeLabel } from '../../core/sanitize';
|
|
8
|
+
import type { NoteBuilder, NoteAction, NoteSideTarget, NoteOverTarget } from '../types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a single note action
|
|
12
|
+
*/
|
|
13
|
+
function createNoteAction(
|
|
14
|
+
store: DiagramStore,
|
|
15
|
+
position: string,
|
|
16
|
+
): NoteAction {
|
|
17
|
+
return {
|
|
18
|
+
msg(text: string) {
|
|
19
|
+
store.add(`Note ${position}: ${sanitizeLabel(text)}`);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create note targets for left/right positions
|
|
26
|
+
*/
|
|
27
|
+
function createNoteSideTarget<P extends string>(
|
|
28
|
+
store: DiagramStore,
|
|
29
|
+
position: 'left' | 'right',
|
|
30
|
+
participants: readonly P[],
|
|
31
|
+
): NoteSideTarget<P> {
|
|
32
|
+
const target = {} as NoteSideTarget<P>;
|
|
33
|
+
|
|
34
|
+
participants.forEach((p) => {
|
|
35
|
+
target[p] = createNoteAction(store, `${position} of ${p}`);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return target;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create note targets for "over" position (can span multiple participants)
|
|
43
|
+
*/
|
|
44
|
+
function createNoteOverTarget<P extends string>(
|
|
45
|
+
store: DiagramStore,
|
|
46
|
+
participants: readonly P[],
|
|
47
|
+
): NoteOverTarget<P> {
|
|
48
|
+
const target = {} as NoteOverTarget<P>;
|
|
49
|
+
|
|
50
|
+
participants.forEach((first) => {
|
|
51
|
+
// Create object with msg for single participant note
|
|
52
|
+
const obj = {
|
|
53
|
+
msg(text: string) {
|
|
54
|
+
store.add(`Note over ${first}: ${sanitizeLabel(text)}`);
|
|
55
|
+
},
|
|
56
|
+
} as NoteAction & { [K in P]: NoteAction };
|
|
57
|
+
|
|
58
|
+
// Add targets for spanning to other participants
|
|
59
|
+
participants.forEach((second) => {
|
|
60
|
+
if (first !== second) {
|
|
61
|
+
(obj as Record<string, NoteAction>)[second] = {
|
|
62
|
+
msg(text: string) {
|
|
63
|
+
store.add(`Note over ${first},${second}: ${sanitizeLabel(text)}`);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
} else {
|
|
67
|
+
// Self-reference just does single note
|
|
68
|
+
(obj as Record<string, NoteAction>)[second] = {
|
|
69
|
+
msg(text: string) {
|
|
70
|
+
store.add(`Note over ${first}: ${sanitizeLabel(text)}`);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
target[first] = obj;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return target;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create the full note builder
|
|
84
|
+
*/
|
|
85
|
+
export function createNoteBuilder<P extends string>(
|
|
86
|
+
store: DiagramStore,
|
|
87
|
+
participants: readonly P[],
|
|
88
|
+
): NoteBuilder<P> {
|
|
89
|
+
return {
|
|
90
|
+
leftOf: createNoteSideTarget(store, 'left', participants),
|
|
91
|
+
rightOf: createNoteSideTarget(store, 'right', participants),
|
|
92
|
+
over: createNoteOverTarget(store, participants),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SequenceDiagram function exports
|
|
3
|
+
* @module Mermaid/builders/SequenceDiagram/functions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { createMessageBuilder } from './getMessages';
|
|
7
|
+
export { createNoteBuilder } from './getNotes';
|
|
8
|
+
export { createActivationBuilder } from './getActivations';
|
|
9
|
+
export {
|
|
10
|
+
createLoopBuilder,
|
|
11
|
+
createAltBuilder,
|
|
12
|
+
createParBuilder,
|
|
13
|
+
createRectBuilder,
|
|
14
|
+
createCriticalBuilder,
|
|
15
|
+
createBreakBuilder,
|
|
16
|
+
} from './getBlocks';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SequenceDiagram builder exports
|
|
3
|
+
* @module Mermaid/builders/SequenceDiagram
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { SequenceDiagram } from './SequenceDiagram';
|
|
7
|
+
export type {
|
|
8
|
+
ParticipantsObject,
|
|
9
|
+
SequenceDiagramOptions,
|
|
10
|
+
SequenceDiagramBuilder,
|
|
11
|
+
MessageBuilder,
|
|
12
|
+
MessageArrows,
|
|
13
|
+
MessageTarget,
|
|
14
|
+
MessageAction,
|
|
15
|
+
NoteBuilder,
|
|
16
|
+
ActivationBuilder,
|
|
17
|
+
} from './types';
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for SequenceDiagram builder
|
|
3
|
+
* @module Mermaid/builders/SequenceDiagram/types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ParticipantType, MessageArrowType } from '../core/types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Participants definition object
|
|
10
|
+
*/
|
|
11
|
+
export type ParticipantsObject<P extends string> = {
|
|
12
|
+
readonly [K in P]: ParticipantType | { type: ParticipantType; alias?: string };
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options for SequenceDiagram builder
|
|
17
|
+
*/
|
|
18
|
+
export interface SequenceDiagramOptions {
|
|
19
|
+
/** Enable auto-numbering of messages */
|
|
20
|
+
autoNumber?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Message action builder
|
|
25
|
+
*/
|
|
26
|
+
export interface MessageAction {
|
|
27
|
+
/** Add a message with text */
|
|
28
|
+
msg(text: string): void;
|
|
29
|
+
/** Add a message that activates the target */
|
|
30
|
+
activate(text: string): void;
|
|
31
|
+
/** Add a message that deactivates the target */
|
|
32
|
+
deactivate(text: string): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Target participant selector for messages
|
|
37
|
+
*/
|
|
38
|
+
export type MessageTarget<P extends string> = {
|
|
39
|
+
[K in P]: MessageAction;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Arrow type selector for messages
|
|
44
|
+
*/
|
|
45
|
+
export type MessageArrows<P extends string> = {
|
|
46
|
+
/** Synchronous call ->> */
|
|
47
|
+
sync: MessageTarget<P>;
|
|
48
|
+
/** Synchronous reply -->> */
|
|
49
|
+
syncReply: MessageTarget<P>;
|
|
50
|
+
/** Async call -) */
|
|
51
|
+
async: MessageTarget<P>;
|
|
52
|
+
/** Async reply --) */
|
|
53
|
+
asyncReply: MessageTarget<P>;
|
|
54
|
+
/** Solid line -> */
|
|
55
|
+
solid: MessageTarget<P>;
|
|
56
|
+
/** Dotted line --> */
|
|
57
|
+
dotted: MessageTarget<P>;
|
|
58
|
+
/** Cross (failure) -x */
|
|
59
|
+
cross: MessageTarget<P>;
|
|
60
|
+
/** Dotted cross --x */
|
|
61
|
+
crossDotted: MessageTarget<P>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Message builder - from participant to arrow type to target
|
|
66
|
+
*/
|
|
67
|
+
export type MessageBuilder<P extends string> = {
|
|
68
|
+
[K in P]: MessageArrows<P>;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Note position types
|
|
73
|
+
*/
|
|
74
|
+
export type NotePosition = 'leftOf' | 'rightOf' | 'over';
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Single note action
|
|
78
|
+
*/
|
|
79
|
+
export interface NoteAction {
|
|
80
|
+
msg(text: string): void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Note target for "over" position (can span multiple participants)
|
|
85
|
+
*/
|
|
86
|
+
export type NoteOverTarget<P extends string> = {
|
|
87
|
+
[K in P]: NoteAction & {
|
|
88
|
+
/** Span to another participant */
|
|
89
|
+
[K2 in P]: NoteAction;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Note target for left/right positions
|
|
95
|
+
*/
|
|
96
|
+
export type NoteSideTarget<P extends string> = {
|
|
97
|
+
[K in P]: NoteAction;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Note builder
|
|
102
|
+
*/
|
|
103
|
+
export interface NoteBuilder<P extends string> {
|
|
104
|
+
leftOf: NoteSideTarget<P>;
|
|
105
|
+
rightOf: NoteSideTarget<P>;
|
|
106
|
+
over: NoteOverTarget<P>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Activation builder
|
|
111
|
+
*/
|
|
112
|
+
export type ActivationBuilder<P extends string> = {
|
|
113
|
+
[K in P]: {
|
|
114
|
+
activate(): void;
|
|
115
|
+
deactivate(): void;
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Main SequenceDiagram builder result
|
|
121
|
+
*/
|
|
122
|
+
export interface SequenceDiagramBuilder<P extends string> {
|
|
123
|
+
/** Message builder (d.Alice.sync.Bob.msg("Hello")) */
|
|
124
|
+
d: MessageBuilder<P>;
|
|
125
|
+
/** Note builder (note.over.Alice.msg("Thinking")) */
|
|
126
|
+
note: NoteBuilder<P>;
|
|
127
|
+
/** Activation builder (activate.Alice.activate()) */
|
|
128
|
+
activate: ActivationBuilder<P>;
|
|
129
|
+
/** Loop block */
|
|
130
|
+
loop(label: string, fn: () => void): void;
|
|
131
|
+
/** Optional/alternative block */
|
|
132
|
+
alt(label: string, fn: () => void): { else(label: string, fn: () => void): void };
|
|
133
|
+
/** Parallel block */
|
|
134
|
+
par(label: string, fn: () => void): { and(label: string, fn: () => void): void };
|
|
135
|
+
/** Colored rectangle box */
|
|
136
|
+
rect(color: string, fn: () => void): void;
|
|
137
|
+
/** Critical region */
|
|
138
|
+
critical(label: string, fn: () => void): { option(label: string, fn: () => void): void };
|
|
139
|
+
/** Break */
|
|
140
|
+
break(label: string, fn: () => void): void;
|
|
141
|
+
/** Add a comment */
|
|
142
|
+
comment(text: string): void;
|
|
143
|
+
/** Add blank line */
|
|
144
|
+
blank(): void;
|
|
145
|
+
/** Get the Mermaid string */
|
|
146
|
+
toString(): string;
|
|
147
|
+
}
|
|
@@ -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,78 @@
|
|
|
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
|
+
const RESERVED_WORDS = new Set([
|
|
15
|
+
'end',
|
|
16
|
+
'graph',
|
|
17
|
+
'subgraph',
|
|
18
|
+
'direction',
|
|
19
|
+
'click',
|
|
20
|
+
'style',
|
|
21
|
+
'class',
|
|
22
|
+
'classDef',
|
|
23
|
+
'linkStyle',
|
|
24
|
+
'callback',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sanitize a label for use in Mermaid diagrams
|
|
29
|
+
* Removes or escapes characters that could break the diagram syntax
|
|
30
|
+
*/
|
|
31
|
+
export function sanitizeLabel(label: string): string {
|
|
32
|
+
return label
|
|
33
|
+
.replace(UNSAFE_CHARS, '')
|
|
34
|
+
.replace(/&/g, '&')
|
|
35
|
+
.replace(/'/g, "'")
|
|
36
|
+
.trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convert an ID to a valid Mermaid node ID
|
|
41
|
+
* - Removes special characters
|
|
42
|
+
* - Adds optional prefix to ensure uniqueness
|
|
43
|
+
* - Ensures ID starts with a letter
|
|
44
|
+
* - Escapes reserved Mermaid keywords
|
|
45
|
+
*/
|
|
46
|
+
export function toNodeId(id: string, prefix = ''): string {
|
|
47
|
+
// Remove special characters and spaces
|
|
48
|
+
const cleanId = id.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
49
|
+
|
|
50
|
+
// Ensure starts with letter
|
|
51
|
+
let safeId = /^[a-zA-Z]/.test(cleanId) ? cleanId : `n${cleanId}`;
|
|
52
|
+
|
|
53
|
+
// Escape reserved words by adding underscore suffix
|
|
54
|
+
if (RESERVED_WORDS.has(safeId.toLowerCase())) {
|
|
55
|
+
safeId = `${safeId}_`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return prefix ? `${prefix}_${safeId}` : safeId;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Escape a string for use in quoted Mermaid labels
|
|
63
|
+
*/
|
|
64
|
+
export function escapeQuoted(str: string): string {
|
|
65
|
+
return str
|
|
66
|
+
.replace(/\\/g, '\\\\')
|
|
67
|
+
.replace(/"/g, '\\"')
|
|
68
|
+
.replace(/\n/g, '<br/>');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Format a label with optional subtitle using HTML-like syntax
|
|
73
|
+
*/
|
|
74
|
+
export function formatLabel(main: string, subtitle?: string): string {
|
|
75
|
+
const sanitized = sanitizeLabel(main);
|
|
76
|
+
if (!subtitle) return sanitized;
|
|
77
|
+
return `${sanitized}<br/><small>${sanitizeLabel(subtitle)}</small>`;
|
|
78
|
+
}
|
|
@@ -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';
|