@zukhruf/diagram 0.0.0-stage → 0.3.28
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 +325 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/compiler/diagram-compile.d.ts +16 -0
- package/dist/lib/compiler/diagram-compile.d.ts.map +1 -0
- package/dist/lib/compiler/diagram-compile.js +711 -0
- package/dist/lib/compiler/diagram-compile.js.map +1 -0
- package/dist/lib/layouts/graph/graph-layout.d.ts +153 -0
- package/dist/lib/layouts/graph/graph-layout.d.ts.map +1 -0
- package/dist/lib/layouts/graph/graph-layout.js +2345 -0
- package/dist/lib/layouts/graph/graph-layout.js.map +1 -0
- package/dist/lib/layouts/text-metrics.d.ts +7 -0
- package/dist/lib/layouts/text-metrics.d.ts.map +1 -0
- package/dist/lib/layouts/text-metrics.js +40 -0
- package/dist/lib/layouts/text-metrics.js.map +1 -0
- package/dist/lib/layouts/timeline/timeline-layout.d.ts +110 -0
- package/dist/lib/layouts/timeline/timeline-layout.d.ts.map +1 -0
- package/dist/lib/layouts/timeline/timeline-layout.js +226 -0
- package/dist/lib/layouts/timeline/timeline-layout.js.map +1 -0
- package/dist/lib/model/diagram-ir.d.ts +288 -0
- package/dist/lib/model/diagram-ir.d.ts.map +1 -0
- package/dist/lib/model/diagram-ir.js +2 -0
- package/dist/lib/model/diagram-ir.js.map +1 -0
- package/dist/lib/react/Diagram.d.ts +34 -0
- package/dist/lib/react/Diagram.d.ts.map +1 -0
- package/dist/lib/react/Diagram.js +56 -0
- package/dist/lib/react/Diagram.js.map +1 -0
- package/dist/lib/react/chrome/control-panel.d.ts +7 -0
- package/dist/lib/react/chrome/control-panel.d.ts.map +1 -0
- package/dist/lib/react/chrome/control-panel.js +63 -0
- package/dist/lib/react/chrome/control-panel.js.map +1 -0
- package/dist/lib/react/chrome/frame.d.ts +18 -0
- package/dist/lib/react/chrome/frame.d.ts.map +1 -0
- package/dist/lib/react/chrome/frame.js +432 -0
- package/dist/lib/react/chrome/frame.js.map +1 -0
- package/dist/lib/react/chrome/header.d.ts +6 -0
- package/dist/lib/react/chrome/header.d.ts.map +1 -0
- package/dist/lib/react/chrome/header.js +58 -0
- package/dist/lib/react/chrome/header.js.map +1 -0
- package/dist/lib/react/chrome/index.d.ts +5 -0
- package/dist/lib/react/chrome/index.d.ts.map +1 -0
- package/dist/lib/react/chrome/index.js +5 -0
- package/dist/lib/react/chrome/index.js.map +1 -0
- package/dist/lib/react/chrome/theme.d.ts +37 -0
- package/dist/lib/react/chrome/theme.d.ts.map +1 -0
- package/dist/lib/react/chrome/theme.js +66 -0
- package/dist/lib/react/chrome/theme.js.map +1 -0
- package/dist/lib/react/diagram-types.d.ts +87 -0
- package/dist/lib/react/diagram-types.d.ts.map +1 -0
- package/dist/lib/react/diagram-types.js +2 -0
- package/dist/lib/react/diagram-types.js.map +1 -0
- package/dist/lib/renderers/canvas/canvas-viewport.d.ts +11 -0
- package/dist/lib/renderers/canvas/canvas-viewport.d.ts.map +1 -0
- package/dist/lib/renderers/canvas/canvas-viewport.js +57 -0
- package/dist/lib/renderers/canvas/canvas-viewport.js.map +1 -0
- package/dist/lib/renderers/canvas/graph-canvas.d.ts +7 -0
- package/dist/lib/renderers/canvas/graph-canvas.d.ts.map +1 -0
- package/dist/lib/renderers/canvas/graph-canvas.js +397 -0
- package/dist/lib/renderers/canvas/graph-canvas.js.map +1 -0
- package/dist/lib/renderers/html/graph-html.d.ts +8 -0
- package/dist/lib/renderers/html/graph-html.d.ts.map +1 -0
- package/dist/lib/renderers/html/graph-html.js +1069 -0
- package/dist/lib/renderers/html/graph-html.js.map +1 -0
- package/dist/lib/renderers/shared/defs.d.ts +2 -0
- package/dist/lib/renderers/shared/defs.d.ts.map +1 -0
- package/dist/lib/renderers/shared/defs.js +158 -0
- package/dist/lib/renderers/shared/defs.js.map +1 -0
- package/dist/lib/renderers/shared/graph-parts.d.ts +53 -0
- package/dist/lib/renderers/shared/graph-parts.d.ts.map +1 -0
- package/dist/lib/renderers/shared/graph-parts.js +1601 -0
- package/dist/lib/renderers/shared/graph-parts.js.map +1 -0
- package/dist/lib/renderers/shared/node-content.d.ts +3 -0
- package/dist/lib/renderers/shared/node-content.d.ts.map +1 -0
- package/dist/lib/renderers/shared/node-content.js +6 -0
- package/dist/lib/renderers/shared/node-content.js.map +1 -0
- package/dist/lib/renderers/shared/node-parts.d.ts +36 -0
- package/dist/lib/renderers/shared/node-parts.d.ts.map +1 -0
- package/dist/lib/renderers/shared/node-parts.js +1206 -0
- package/dist/lib/renderers/shared/node-parts.js.map +1 -0
- package/dist/lib/renderers/shared/paper.d.ts +10 -0
- package/dist/lib/renderers/shared/paper.d.ts.map +1 -0
- package/dist/lib/renderers/shared/paper.js +131 -0
- package/dist/lib/renderers/shared/paper.js.map +1 -0
- package/dist/lib/renderers/shared/viewport-zoom.d.ts +4 -0
- package/dist/lib/renderers/shared/viewport-zoom.d.ts.map +1 -0
- package/dist/lib/renderers/shared/viewport-zoom.js +14 -0
- package/dist/lib/renderers/shared/viewport-zoom.js.map +1 -0
- package/dist/lib/renderers/svg/graph-svg.d.ts +10 -0
- package/dist/lib/renderers/svg/graph-svg.d.ts.map +1 -0
- package/dist/lib/renderers/svg/graph-svg.js +860 -0
- package/dist/lib/renderers/svg/graph-svg.js.map +1 -0
- package/dist/lib/renderers/svg/timeline-svg.d.ts +4 -0
- package/dist/lib/renderers/svg/timeline-svg.d.ts.map +1 -0
- package/dist/lib/renderers/svg/timeline-svg.js +1071 -0
- package/dist/lib/renderers/svg/timeline-svg.js.map +1 -0
- package/dist/lib/renderers/svg/viewport.d.ts +13 -0
- package/dist/lib/renderers/svg/viewport.d.ts.map +1 -0
- package/dist/lib/renderers/svg/viewport.js +48 -0
- package/dist/lib/renderers/svg/viewport.js.map +1 -0
- package/dist/lib/renderers/types.d.ts +18 -0
- package/dist/lib/renderers/types.d.ts.map +1 -0
- package/dist/lib/renderers/types.js +2 -0
- package/dist/lib/renderers/types.js.map +1 -0
- package/dist/lib/renderers/webgpu/flow-shader.d.ts +2 -0
- package/dist/lib/renderers/webgpu/flow-shader.d.ts.map +1 -0
- package/dist/lib/renderers/webgpu/flow-shader.js +67 -0
- package/dist/lib/renderers/webgpu/flow-shader.js.map +1 -0
- package/dist/lib/renderers/webgpu/graph-flow-effects.d.ts +22 -0
- package/dist/lib/renderers/webgpu/graph-flow-effects.d.ts.map +1 -0
- package/dist/lib/renderers/webgpu/graph-flow-effects.js +274 -0
- package/dist/lib/renderers/webgpu/graph-flow-effects.js.map +1 -0
- package/package.json +53 -4
- package/styles.css +1 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
import { Children, Fragment, isValidElement } from 'react';
|
|
2
|
+
export const DIAGRAM_PRIMITIVE = Symbol.for('@zukhruf/diagram/primitive');
|
|
3
|
+
const PRIMITIVE_KINDS = ['node', 'box', 'card', 'port', 'label', 'float', 'phantom', 'appliance', 'connector', 'junction', 'edge', 'arrow', 'wire', 'group', 'text', 'line', 'lane', 'signal', 'frame', 'marker', 'guide', 'waypoint', 'entry', 'exit', 'content'];
|
|
4
|
+
export function primitive(kind) {
|
|
5
|
+
return Object.assign(() => null, {
|
|
6
|
+
[DIAGRAM_PRIMITIVE]: kind
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export function primitiveKind(node) {
|
|
10
|
+
if (!isValidElement(node)) return null;
|
|
11
|
+
const {
|
|
12
|
+
type
|
|
13
|
+
} = node;
|
|
14
|
+
if (typeof type !== 'function' || !(DIAGRAM_PRIMITIVE in type)) return null;
|
|
15
|
+
const kind = type[DIAGRAM_PRIMITIVE];
|
|
16
|
+
return isPrimitiveKind(kind) ? kind : null;
|
|
17
|
+
}
|
|
18
|
+
function isPrimitiveKind(value) {
|
|
19
|
+
return PRIMITIVE_KINDS.some(kind => kind === value);
|
|
20
|
+
}
|
|
21
|
+
function emptyCompileResult() {
|
|
22
|
+
return {
|
|
23
|
+
nodes: [],
|
|
24
|
+
edges: [],
|
|
25
|
+
groups: [],
|
|
26
|
+
annotations: [],
|
|
27
|
+
lanes: [],
|
|
28
|
+
signals: [],
|
|
29
|
+
frames: [],
|
|
30
|
+
guides: []
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function mergeCompileResults(target, source) {
|
|
34
|
+
target.nodes.push(...source.nodes);
|
|
35
|
+
target.edges.push(...source.edges);
|
|
36
|
+
target.groups.push(...source.groups);
|
|
37
|
+
target.annotations.push(...source.annotations);
|
|
38
|
+
target.lanes.push(...source.lanes);
|
|
39
|
+
target.signals.push(...source.signals);
|
|
40
|
+
target.frames.push(...source.frames);
|
|
41
|
+
target.guides.push(...source.guides);
|
|
42
|
+
}
|
|
43
|
+
export function compileGraph(children, layout) {
|
|
44
|
+
const result = compileChildren(children, {});
|
|
45
|
+
if (result.lanes.length > 0 || result.signals.length > 0 || result.frames.length > 0) {
|
|
46
|
+
throw new Error('A graph diagram cannot contain Diagram.Lane, Diagram.Signal or Diagram.Frame; draw those with SvgTimelineDiagram.');
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
nodes: result.nodes,
|
|
50
|
+
edges: result.edges,
|
|
51
|
+
groups: result.groups,
|
|
52
|
+
annotations: result.annotations,
|
|
53
|
+
guides: result.guides,
|
|
54
|
+
layout
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function compileTimeline(children) {
|
|
58
|
+
const result = compileChildren(children, {});
|
|
59
|
+
if (result.nodes.length > 0 || result.edges.length > 0 || result.groups.length > 0 || result.annotations.length > 0 || result.guides.length > 0) {
|
|
60
|
+
throw new Error('A timeline diagram only accepts Diagram.Lane, Diagram.Signal and Diagram.Frame; draw other primitives with SvgGraphDiagram or CanvasGraphDiagram.');
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
lanes: result.lanes,
|
|
64
|
+
signals: result.signals,
|
|
65
|
+
frame: result.frames[0]
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function compileChildren(children, context) {
|
|
69
|
+
const result = emptyCompileResult();
|
|
70
|
+
Children.forEach(children, child => {
|
|
71
|
+
if (child === null || child === undefined || typeof child === 'boolean') return;
|
|
72
|
+
if (isTextNode(child)) {
|
|
73
|
+
if (String(child).trim().length === 0) return;
|
|
74
|
+
throw new Error(`Unexpected text "${String(child).trim()}" directly inside Diagram.`);
|
|
75
|
+
}
|
|
76
|
+
if (!isValidElement(child)) {
|
|
77
|
+
throw new Error('Diagram only accepts Diagram primitives as children.');
|
|
78
|
+
}
|
|
79
|
+
if (child.type === Fragment) {
|
|
80
|
+
throw new Error('Diagram does not accept React fragments. Place Diagram primitives directly or return an array of primitives.');
|
|
81
|
+
}
|
|
82
|
+
const kind = primitiveKind(child);
|
|
83
|
+
if (!kind) {
|
|
84
|
+
const name = typeof child.type === 'function' ? child.type.name : String(child.type);
|
|
85
|
+
throw new Error(`Diagram cannot compile <${name || 'Component'} />. Use direct Diagram primitives as children.`);
|
|
86
|
+
}
|
|
87
|
+
if (isNodeKind(kind)) {
|
|
88
|
+
result.nodes.push(compileNode(kind, child, context));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (isEdgeKind(kind)) {
|
|
92
|
+
result.edges.push(compileEdge(kind, child));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (kind === 'group') {
|
|
96
|
+
const group = compileGroup(child, context);
|
|
97
|
+
result.groups.push(group.group);
|
|
98
|
+
mergeCompileResults(result, group.children);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (kind === 'text') {
|
|
102
|
+
result.annotations.push(compileText(child));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (kind === 'lane') {
|
|
106
|
+
result.lanes.push(compileLane(child));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (kind === 'signal') {
|
|
110
|
+
result.signals.push(compileSignal(child));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (kind === 'frame') {
|
|
114
|
+
if (result.frames.length > 0) {
|
|
115
|
+
throw new Error('Diagram supports one Frame in lane/signal layout for now.');
|
|
116
|
+
}
|
|
117
|
+
result.frames.push(compileFrame(child));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (kind === 'guide') {
|
|
121
|
+
result.guides.push(requirePrimitiveProps(child));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
throw new Error(`<Diagram.${kind}> cannot be a direct child of Diagram.`);
|
|
125
|
+
});
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
function isTextNode(child) {
|
|
129
|
+
return typeof child === 'string' || typeof child === 'number';
|
|
130
|
+
}
|
|
131
|
+
function isNodeKind(kind) {
|
|
132
|
+
return kind === 'node' || kind === 'box' || kind === 'card' || kind === 'port' || kind === 'label' || kind === 'float' || kind === 'phantom' || kind === 'appliance' || kind === 'connector' || kind === 'junction' || kind === 'marker';
|
|
133
|
+
}
|
|
134
|
+
function isEdgeKind(kind) {
|
|
135
|
+
return kind === 'edge' || kind === 'arrow' || kind === 'wire';
|
|
136
|
+
}
|
|
137
|
+
function requirePrimitiveProps(child) {
|
|
138
|
+
// React's own guard types the element's props; only the calling primitive
|
|
139
|
+
// knows their shape P.
|
|
140
|
+
if (!isValidElement(child)) {
|
|
141
|
+
throw new Error('Diagram only accepts Diagram primitives as children.');
|
|
142
|
+
}
|
|
143
|
+
return child.props;
|
|
144
|
+
}
|
|
145
|
+
function compileNode(kind, child, context) {
|
|
146
|
+
if (kind === 'phantom') {
|
|
147
|
+
const props = requirePrimitiveProps(child);
|
|
148
|
+
const label = compileLabelChildren(props.children, 'Diagram.Phantom');
|
|
149
|
+
const hasLabel = label.length > 0;
|
|
150
|
+
return {
|
|
151
|
+
id: props.id,
|
|
152
|
+
label: hasLabel ? label : '',
|
|
153
|
+
row: props.row,
|
|
154
|
+
col: props.col,
|
|
155
|
+
width: props.width ?? 1,
|
|
156
|
+
height: props.height ?? 1,
|
|
157
|
+
...(props.group ?? context.groupId ? {
|
|
158
|
+
group: props.group ?? context.groupId
|
|
159
|
+
} : {}),
|
|
160
|
+
frame: 'none',
|
|
161
|
+
hidden: props.hidden ?? !hasLabel,
|
|
162
|
+
...(props.fontSize ? {
|
|
163
|
+
fontSize: props.fontSize
|
|
164
|
+
} : {}),
|
|
165
|
+
...(props.fontWeight ? {
|
|
166
|
+
fontWeight: props.fontWeight
|
|
167
|
+
} : {}),
|
|
168
|
+
...(props.textColor ? {
|
|
169
|
+
textColor: props.textColor
|
|
170
|
+
} : {}),
|
|
171
|
+
...(props.accent !== undefined ? {
|
|
172
|
+
accent: props.accent
|
|
173
|
+
} : {}),
|
|
174
|
+
...(props.textAlign ? {
|
|
175
|
+
textAlign: props.textAlign
|
|
176
|
+
} : {}),
|
|
177
|
+
...(props.connectable !== undefined ? {
|
|
178
|
+
connectable: props.connectable
|
|
179
|
+
} : {})
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (kind === 'junction') {
|
|
183
|
+
const props = requirePrimitiveProps(child);
|
|
184
|
+
const label = compileLabelChildren(props.children, 'Diagram.Junction');
|
|
185
|
+
const group = props.group ?? context.groupId;
|
|
186
|
+
return {
|
|
187
|
+
id: props.id,
|
|
188
|
+
kind: 'junction',
|
|
189
|
+
label: label.length > 0 ? label : '',
|
|
190
|
+
row: props.row,
|
|
191
|
+
col: props.col,
|
|
192
|
+
width: props.width ?? 16,
|
|
193
|
+
height: props.height ?? 16,
|
|
194
|
+
...(group ? {
|
|
195
|
+
group
|
|
196
|
+
} : {}),
|
|
197
|
+
frame: 'none',
|
|
198
|
+
connectable: true,
|
|
199
|
+
hidden: props.hidden,
|
|
200
|
+
accent: props.accent,
|
|
201
|
+
tone: props.tone,
|
|
202
|
+
fontSize: props.fontSize,
|
|
203
|
+
fontWeight: props.fontWeight,
|
|
204
|
+
textColor: props.textColor,
|
|
205
|
+
caption: props.caption,
|
|
206
|
+
captionSide: props.captionSide,
|
|
207
|
+
captionOffset: props.captionOffset,
|
|
208
|
+
captionColor: props.captionColor,
|
|
209
|
+
captionFontSize: props.captionFontSize,
|
|
210
|
+
captionFontWeight: props.captionFontWeight
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (kind === 'connector') {
|
|
214
|
+
const props = requirePrimitiveProps(child);
|
|
215
|
+
const label = compileLabelChildren(props.children, 'Diagram.Connector');
|
|
216
|
+
const group = props.group ?? context.groupId;
|
|
217
|
+
return {
|
|
218
|
+
id: props.id,
|
|
219
|
+
kind: 'connector',
|
|
220
|
+
label: label.length > 0 ? label : '',
|
|
221
|
+
row: props.row,
|
|
222
|
+
col: props.col,
|
|
223
|
+
colSpan: props.colSpan,
|
|
224
|
+
width: props.width ?? 148,
|
|
225
|
+
height: props.height ?? 40,
|
|
226
|
+
...(group ? {
|
|
227
|
+
group
|
|
228
|
+
} : {}),
|
|
229
|
+
frame: 'solid',
|
|
230
|
+
connectable: true,
|
|
231
|
+
rx: props.rx ?? 10,
|
|
232
|
+
accent: props.accent,
|
|
233
|
+
tone: props.tone,
|
|
234
|
+
texture: props.texture,
|
|
235
|
+
icon: props.icon,
|
|
236
|
+
iconSize: props.iconSize,
|
|
237
|
+
ports: props.ports,
|
|
238
|
+
fontSize: props.fontSize,
|
|
239
|
+
fontWeight: props.fontWeight,
|
|
240
|
+
textColor: props.textColor,
|
|
241
|
+
caption: props.caption,
|
|
242
|
+
captionSide: props.captionSide,
|
|
243
|
+
captionOffset: props.captionOffset,
|
|
244
|
+
captionColor: props.captionColor,
|
|
245
|
+
captionFontSize: props.captionFontSize,
|
|
246
|
+
captionFontWeight: props.captionFontWeight
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (kind === 'appliance') {
|
|
250
|
+
const props = requirePrimitiveProps(child);
|
|
251
|
+
const label = compileLabelChildren(props.children, 'Diagram.Appliance');
|
|
252
|
+
const group = props.group ?? context.groupId;
|
|
253
|
+
return {
|
|
254
|
+
id: props.id,
|
|
255
|
+
kind: 'appliance',
|
|
256
|
+
label: label.length > 0 ? label : '',
|
|
257
|
+
row: props.row,
|
|
258
|
+
col: props.col,
|
|
259
|
+
width: props.width ?? 112,
|
|
260
|
+
height: props.height ?? 72,
|
|
261
|
+
...(group ? {
|
|
262
|
+
group
|
|
263
|
+
} : {}),
|
|
264
|
+
frame: 'none',
|
|
265
|
+
connectable: true,
|
|
266
|
+
accent: props.accent,
|
|
267
|
+
tone: props.tone,
|
|
268
|
+
texture: props.texture,
|
|
269
|
+
ports: props.ports,
|
|
270
|
+
fontSize: props.fontSize,
|
|
271
|
+
fontWeight: props.fontWeight,
|
|
272
|
+
textColor: props.textColor,
|
|
273
|
+
caption: props.caption,
|
|
274
|
+
captionSide: props.captionSide,
|
|
275
|
+
captionOffset: props.captionOffset,
|
|
276
|
+
captionColor: props.captionColor,
|
|
277
|
+
captionFontSize: props.captionFontSize,
|
|
278
|
+
captionFontWeight: props.captionFontWeight,
|
|
279
|
+
appliancePorts: props.appliancePorts,
|
|
280
|
+
antenna: props.antenna,
|
|
281
|
+
slant: props.slant
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const props = requirePrimitiveProps(child);
|
|
285
|
+
const {
|
|
286
|
+
children: _children,
|
|
287
|
+
...nodeProps
|
|
288
|
+
} = props;
|
|
289
|
+
const {
|
|
290
|
+
labelChildren,
|
|
291
|
+
content
|
|
292
|
+
} = splitNodeContent(props.children, kind);
|
|
293
|
+
const label = compileLabelChildren(labelChildren, `Diagram.${kind}`);
|
|
294
|
+
const group = nodeProps.group ?? context.groupId;
|
|
295
|
+
return {
|
|
296
|
+
...nodeProps,
|
|
297
|
+
label: label.length > 0 ? label : '',
|
|
298
|
+
...(content.length > 0 ? {
|
|
299
|
+
content: content[0]
|
|
300
|
+
} : {}),
|
|
301
|
+
...(kind === 'marker' ? {
|
|
302
|
+
kind: 'marker',
|
|
303
|
+
width: nodeProps.width ?? 72,
|
|
304
|
+
height: nodeProps.height ?? 76
|
|
305
|
+
} : {}),
|
|
306
|
+
...(group ? {
|
|
307
|
+
group
|
|
308
|
+
} : {}),
|
|
309
|
+
frame: nodeFrame(kind, nodeProps.frame),
|
|
310
|
+
textAlign: kind === 'card' ? nodeProps.textAlign ?? 'start' : nodeProps.textAlign,
|
|
311
|
+
hidden: kind === 'marker' ? false : nodeProps.hidden,
|
|
312
|
+
connectable: kind === 'float' ? false : nodeProps.connectable,
|
|
313
|
+
...(kind === 'marker' ? {
|
|
314
|
+
frame: nodeProps.frame ?? 'none'
|
|
315
|
+
} : {})
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const CONTENT_HOSTS = new Set(['node', 'box', 'card']);
|
|
319
|
+
function splitNodeContent(children, kind) {
|
|
320
|
+
const labelChildren = [];
|
|
321
|
+
const content = [];
|
|
322
|
+
Children.forEach(children, child => {
|
|
323
|
+
if (!isValidElement(child) || primitiveKind(child) !== 'content') {
|
|
324
|
+
labelChildren.push(child);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (!CONTENT_HOSTS.has(kind)) {
|
|
328
|
+
throw new Error(`Diagram.Content goes inside Diagram.Box, Diagram.Card or Diagram.Node, not Diagram.${kind}.`);
|
|
329
|
+
}
|
|
330
|
+
if (content.length > 0) {
|
|
331
|
+
throw new Error(`Diagram.${kind} accepts one Diagram.Content.`);
|
|
332
|
+
}
|
|
333
|
+
content.push(requirePrimitiveProps(child).children);
|
|
334
|
+
});
|
|
335
|
+
return {
|
|
336
|
+
labelChildren,
|
|
337
|
+
content
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function nodeFrame(kind, explicit) {
|
|
341
|
+
if (explicit) return explicit;
|
|
342
|
+
if (kind === 'port') return 'transparent';
|
|
343
|
+
if (kind === 'label') return 'none';
|
|
344
|
+
if (kind === 'float') return 'flat';
|
|
345
|
+
if (kind === 'appliance') return 'none';
|
|
346
|
+
if (kind === 'marker') return 'none';
|
|
347
|
+
return 'solid';
|
|
348
|
+
}
|
|
349
|
+
function compileEdge(kind, child) {
|
|
350
|
+
const props = requirePrimitiveProps(child);
|
|
351
|
+
const {
|
|
352
|
+
children: _children,
|
|
353
|
+
badgeLabel,
|
|
354
|
+
...edgeProps
|
|
355
|
+
} = props;
|
|
356
|
+
const lines = [];
|
|
357
|
+
const waypoints = [...(props.waypoints ?? [])];
|
|
358
|
+
let entrySide = props.entrySide;
|
|
359
|
+
let exitSide = props.exitSide;
|
|
360
|
+
let entryGap = props.entryGap;
|
|
361
|
+
Children.forEach(props.children, edgeChild => {
|
|
362
|
+
if (edgeChild === null || edgeChild === undefined || typeof edgeChild === 'boolean') return;
|
|
363
|
+
if (isTextNode(edgeChild)) {
|
|
364
|
+
const text = String(edgeChild).trim();
|
|
365
|
+
if (text.length > 0) lines.push(text);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (!isValidElement(edgeChild)) {
|
|
369
|
+
throw new Error('Diagram edges only accept text, Line, Entry, Exit, and Waypoint children.');
|
|
370
|
+
}
|
|
371
|
+
if (edgeChild.type === Fragment) {
|
|
372
|
+
throw new Error('Diagram edges do not accept fragments. Use direct Line/Entry/Exit/Waypoint children.');
|
|
373
|
+
}
|
|
374
|
+
const edgeChildKind = primitiveKind(edgeChild);
|
|
375
|
+
if (edgeChildKind === 'line') {
|
|
376
|
+
lines.push(lineText(edgeChild));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (edgeChildKind === 'entry') {
|
|
380
|
+
const entry = requirePrimitiveProps(edgeChild);
|
|
381
|
+
entrySide = entry.side;
|
|
382
|
+
entryGap = entry.gap ?? entryGap;
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (edgeChildKind === 'exit') {
|
|
386
|
+
exitSide = requirePrimitiveProps(edgeChild).side;
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (edgeChildKind === 'waypoint') {
|
|
390
|
+
waypoints.push(requirePrimitiveProps(edgeChild));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
throw new Error(`Diagram edge cannot contain <Diagram.${edgeChildKind ?? 'Unknown'}>.`);
|
|
394
|
+
});
|
|
395
|
+
return {
|
|
396
|
+
...edgeProps,
|
|
397
|
+
...(lines.length > 0 ? {
|
|
398
|
+
label: lines
|
|
399
|
+
} : {}),
|
|
400
|
+
...(badgeLabel !== undefined ? {
|
|
401
|
+
badgeLabel: normalizeStringLines(badgeLabel)
|
|
402
|
+
} : {}),
|
|
403
|
+
...(waypoints.length > 0 ? {
|
|
404
|
+
waypoints
|
|
405
|
+
} : {}),
|
|
406
|
+
...(entrySide ? {
|
|
407
|
+
entrySide
|
|
408
|
+
} : {}),
|
|
409
|
+
...(exitSide ? {
|
|
410
|
+
exitSide
|
|
411
|
+
} : {}),
|
|
412
|
+
...(entryGap !== undefined ? {
|
|
413
|
+
entryGap
|
|
414
|
+
} : {}),
|
|
415
|
+
headless: kind === 'wire' ? true : edgeProps.headless
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function normalizeStringLines(value) {
|
|
419
|
+
return (Array.isArray(value) ? value : [value]).map(line => line.trim()).filter(Boolean);
|
|
420
|
+
}
|
|
421
|
+
function compileGroup(child, context) {
|
|
422
|
+
const props = requirePrimitiveProps(child);
|
|
423
|
+
const children = compileChildren(props.children, {
|
|
424
|
+
...context,
|
|
425
|
+
groupId: props.id
|
|
426
|
+
});
|
|
427
|
+
const bounds = props.rowStart !== undefined && props.rowEnd !== undefined && props.colStart !== undefined && props.colEnd !== undefined ? {
|
|
428
|
+
rowStart: props.rowStart,
|
|
429
|
+
rowEnd: props.rowEnd,
|
|
430
|
+
colStart: props.colStart,
|
|
431
|
+
colEnd: props.colEnd
|
|
432
|
+
} : inferGroupBounds(props.id, children.nodes, children.groups);
|
|
433
|
+
return {
|
|
434
|
+
group: {
|
|
435
|
+
id: props.id,
|
|
436
|
+
label: normalizeGroupLabel(props.label),
|
|
437
|
+
labelAlign: props.labelAlign,
|
|
438
|
+
labelSide: props.labelSide,
|
|
439
|
+
accent: props.accent,
|
|
440
|
+
tone: props.tone,
|
|
441
|
+
texture: props.texture,
|
|
442
|
+
rx: props.rx,
|
|
443
|
+
frame: props.frame,
|
|
444
|
+
fill: props.fill,
|
|
445
|
+
ports: props.ports,
|
|
446
|
+
parent: context.groupId,
|
|
447
|
+
tight: props.tight,
|
|
448
|
+
tightX: props.tightX,
|
|
449
|
+
tightY: props.tightY,
|
|
450
|
+
titleBand: props.titleBand,
|
|
451
|
+
titleGap: props.titleGap,
|
|
452
|
+
padding: props.padding,
|
|
453
|
+
dividers: props.dividers,
|
|
454
|
+
bounds
|
|
455
|
+
},
|
|
456
|
+
children
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function normalizeGroupLabel(label) {
|
|
460
|
+
if (Array.isArray(label)) {
|
|
461
|
+
return label.map(line => line.trim()).filter(Boolean);
|
|
462
|
+
}
|
|
463
|
+
return label?.trim() ?? '';
|
|
464
|
+
}
|
|
465
|
+
function inferGroupBounds(groupId, nodes, groups = []) {
|
|
466
|
+
const grouped = nodes.filter(node => node.group === groupId);
|
|
467
|
+
const childGroups = groups.filter(group => group.parent === groupId);
|
|
468
|
+
if (grouped.length === 0 && childGroups.length === 0) {
|
|
469
|
+
throw new Error(`Diagram.Group "${groupId}" needs bounds or direct node/group children so bounds can be inferred.`);
|
|
470
|
+
}
|
|
471
|
+
const bounds = emptyGroupBounds();
|
|
472
|
+
for (const node of grouped) {
|
|
473
|
+
if (node.row === undefined || node.col === undefined) return undefined;
|
|
474
|
+
includeGroupBounds(bounds, {
|
|
475
|
+
rowStart: node.row,
|
|
476
|
+
rowEnd: node.row,
|
|
477
|
+
colStart: node.col,
|
|
478
|
+
colEnd: node.col + (node.colSpan ?? 1) - 1
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
for (const group of childGroups) {
|
|
482
|
+
if (!group.bounds) return undefined;
|
|
483
|
+
includeGroupBounds(bounds, group.bounds);
|
|
484
|
+
}
|
|
485
|
+
return bounds;
|
|
486
|
+
}
|
|
487
|
+
function emptyGroupBounds() {
|
|
488
|
+
return {
|
|
489
|
+
rowStart: Number.POSITIVE_INFINITY,
|
|
490
|
+
rowEnd: Number.NEGATIVE_INFINITY,
|
|
491
|
+
colStart: Number.POSITIVE_INFINITY,
|
|
492
|
+
colEnd: Number.NEGATIVE_INFINITY
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
function includeGroupBounds(target, source) {
|
|
496
|
+
target.rowStart = Math.min(target.rowStart, source.rowStart);
|
|
497
|
+
target.rowEnd = Math.max(target.rowEnd, source.rowEnd);
|
|
498
|
+
target.colStart = Math.min(target.colStart, source.colStart);
|
|
499
|
+
target.colEnd = Math.max(target.colEnd, source.colEnd);
|
|
500
|
+
}
|
|
501
|
+
function compileText(child) {
|
|
502
|
+
const props = requirePrimitiveProps(child);
|
|
503
|
+
const {
|
|
504
|
+
children: _children,
|
|
505
|
+
...textProps
|
|
506
|
+
} = props;
|
|
507
|
+
const lines = compileStringLines(props.children, 'Diagram.Text');
|
|
508
|
+
return {
|
|
509
|
+
...textProps,
|
|
510
|
+
text: lines.length > 0 ? lines : ['']
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
function compileLane(child) {
|
|
514
|
+
const props = requirePrimitiveProps(child);
|
|
515
|
+
return {
|
|
516
|
+
id: props.id,
|
|
517
|
+
label: compileStringLines(props.children, 'Diagram.Lane'),
|
|
518
|
+
accent: props.accent
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function normalizeBoxed(boxed) {
|
|
522
|
+
if (boxed === true) return 'source';
|
|
523
|
+
if (!boxed) return undefined;
|
|
524
|
+
return boxed;
|
|
525
|
+
}
|
|
526
|
+
function compileSignal(child) {
|
|
527
|
+
const props = requirePrimitiveProps(child);
|
|
528
|
+
const {
|
|
529
|
+
children: _children,
|
|
530
|
+
boxed,
|
|
531
|
+
...signalProps
|
|
532
|
+
} = props;
|
|
533
|
+
const normalizedBoxed = normalizeBoxed(boxed);
|
|
534
|
+
return {
|
|
535
|
+
...signalProps,
|
|
536
|
+
label: compileStringLines(props.children, 'Diagram.Signal'),
|
|
537
|
+
...(normalizedBoxed ? {
|
|
538
|
+
boxed: normalizedBoxed
|
|
539
|
+
} : {})
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function compileFrame(child) {
|
|
543
|
+
const props = requirePrimitiveProps(child);
|
|
544
|
+
const signals = [];
|
|
545
|
+
Children.forEach(props.children, frameChild => {
|
|
546
|
+
if (frameChild === null || frameChild === undefined || typeof frameChild === 'boolean') return;
|
|
547
|
+
if (!isValidElement(frameChild) || primitiveKind(frameChild) !== 'signal') {
|
|
548
|
+
throw new Error('Diagram.Frame only accepts direct Diagram.Signal children.');
|
|
549
|
+
}
|
|
550
|
+
signals.push(compileSignal(frameChild));
|
|
551
|
+
});
|
|
552
|
+
return {
|
|
553
|
+
label: props.label,
|
|
554
|
+
signals
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
function compileStringLines(children, owner) {
|
|
558
|
+
const lines = [];
|
|
559
|
+
Children.forEach(children, child => {
|
|
560
|
+
if (child === null || child === undefined || typeof child === 'boolean') return;
|
|
561
|
+
if (isTextNode(child)) {
|
|
562
|
+
const text = String(child).trim();
|
|
563
|
+
if (text.length > 0) lines.push(text);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (!isValidElement(child)) {
|
|
567
|
+
throw new Error(`${owner} only accepts text or Diagram.Line children.`);
|
|
568
|
+
}
|
|
569
|
+
if (child.type === Fragment) {
|
|
570
|
+
throw new Error(`${owner} does not accept React fragments. Use direct text or Diagram.Line children.`);
|
|
571
|
+
}
|
|
572
|
+
if (primitiveKind(child) === 'line') {
|
|
573
|
+
lines.push(lineText(child));
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
throw new Error(`${owner} only accepts text or Diagram.Line children.`);
|
|
577
|
+
});
|
|
578
|
+
return lines;
|
|
579
|
+
}
|
|
580
|
+
function compileLabelChildren(children, owner) {
|
|
581
|
+
const lines = [];
|
|
582
|
+
Children.forEach(children, child => {
|
|
583
|
+
if (child === null || child === undefined || typeof child === 'boolean') return;
|
|
584
|
+
if (isTextNode(child)) {
|
|
585
|
+
const text = String(child).trim();
|
|
586
|
+
if (text.length > 0) lines.push(text);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
if (!isValidElement(child)) {
|
|
590
|
+
throw new Error(`${owner} only accepts text or Diagram.Line children.`);
|
|
591
|
+
}
|
|
592
|
+
if (child.type === Fragment) {
|
|
593
|
+
throw new Error(`${owner} does not accept React fragments. Use direct text or Diagram.Line children.`);
|
|
594
|
+
}
|
|
595
|
+
if (primitiveKind(child) === 'line') {
|
|
596
|
+
const props = requirePrimitiveProps(child);
|
|
597
|
+
lines.push({
|
|
598
|
+
text: lineText(child),
|
|
599
|
+
...(props.textColor ? {
|
|
600
|
+
textColor: props.textColor
|
|
601
|
+
} : {}),
|
|
602
|
+
...(props.fontWeight ? {
|
|
603
|
+
fontWeight: props.fontWeight
|
|
604
|
+
} : {})
|
|
605
|
+
});
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
throw new Error(`${owner} only accepts text or Diagram.Line children.`);
|
|
609
|
+
});
|
|
610
|
+
return lines;
|
|
611
|
+
}
|
|
612
|
+
function lineText(child) {
|
|
613
|
+
const props = requirePrimitiveProps(child);
|
|
614
|
+
return Children.toArray(props.children).map(part => {
|
|
615
|
+
if (!isTextNode(part)) {
|
|
616
|
+
throw new Error('Diagram.Line only accepts text children.');
|
|
617
|
+
}
|
|
618
|
+
return String(part).trim();
|
|
619
|
+
}).filter(Boolean).join('');
|
|
620
|
+
}
|
|
621
|
+
export function validateGraph(ir) {
|
|
622
|
+
const nodeIds = new Set();
|
|
623
|
+
for (const node of ir.nodes) {
|
|
624
|
+
if (nodeIds.has(node.id)) throw new Error(`Duplicate Diagram node id "${node.id}".`);
|
|
625
|
+
nodeIds.add(node.id);
|
|
626
|
+
}
|
|
627
|
+
const groupIds = new Set();
|
|
628
|
+
for (const group of ir.groups) {
|
|
629
|
+
if (groupIds.has(group.id)) throw new Error(`Duplicate Diagram group id "${group.id}".`);
|
|
630
|
+
groupIds.add(group.id);
|
|
631
|
+
}
|
|
632
|
+
const guideIds = new Set();
|
|
633
|
+
for (const guide of ir.guides) {
|
|
634
|
+
if (!guide.id) continue;
|
|
635
|
+
if (guideIds.has(guide.id)) throw new Error(`Duplicate Diagram guide id "${guide.id}".`);
|
|
636
|
+
guideIds.add(guide.id);
|
|
637
|
+
}
|
|
638
|
+
const nodesById = new Map(ir.nodes.map(node => [node.id, node]));
|
|
639
|
+
const groupsById = new Map(ir.groups.map(group => [group.id, group]));
|
|
640
|
+
for (const node of ir.nodes) {
|
|
641
|
+
if (groupsById.has(node.id)) {
|
|
642
|
+
throw new Error(`Diagram node id "${node.id}" conflicts with a Diagram group id. Node and group endpoint ids must be unique.`);
|
|
643
|
+
}
|
|
644
|
+
if (node.bodyFontSize !== undefined && (!Number.isFinite(node.bodyFontSize) || node.bodyFontSize <= 0)) {
|
|
645
|
+
throw new Error(`Diagram node "${node.id}" bodyFontSize must be a positive finite number, got ${node.bodyFontSize}.`);
|
|
646
|
+
}
|
|
647
|
+
if (node.bodyLineGap !== undefined && (!Number.isFinite(node.bodyLineGap) || node.bodyLineGap < 0)) {
|
|
648
|
+
throw new Error(`Diagram node "${node.id}" bodyLineGap must be a non-negative finite number, got ${node.bodyLineGap}.`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
for (const edge of ir.edges) {
|
|
652
|
+
if (edge.route !== undefined && edge.route !== 'orthogonal' && edge.route !== 'straight') {
|
|
653
|
+
throw new Error(`Diagram edge route must be "orthogonal" or "straight", got "${edge.route}".`);
|
|
654
|
+
}
|
|
655
|
+
if (edge.labelLineGap !== undefined && (!Number.isFinite(edge.labelLineGap) || edge.labelLineGap < 0)) {
|
|
656
|
+
throw new Error(`Diagram edge labelLineGap must be a non-negative finite number, got ${edge.labelLineGap}.`);
|
|
657
|
+
}
|
|
658
|
+
if (edge.exitOffset !== undefined && !Number.isFinite(edge.exitOffset)) {
|
|
659
|
+
throw new Error(`Diagram edge exitOffset must be a finite number, got ${edge.exitOffset}.`);
|
|
660
|
+
}
|
|
661
|
+
if (edge.entryOffset !== undefined && !Number.isFinite(edge.entryOffset)) {
|
|
662
|
+
throw new Error(`Diagram edge entryOffset must be a finite number, got ${edge.entryOffset}.`);
|
|
663
|
+
}
|
|
664
|
+
if (edge.stubDistance !== undefined && (!Number.isFinite(edge.stubDistance) || edge.stubDistance < 0)) {
|
|
665
|
+
throw new Error(`Diagram edge stubDistance must be a non-negative finite number, got ${edge.stubDistance}.`);
|
|
666
|
+
}
|
|
667
|
+
const sourceNodeId = endpointNodeId(edge.from);
|
|
668
|
+
const targetNodeId = endpointNodeId(edge.to);
|
|
669
|
+
if (!nodeIds.has(sourceNodeId) && !groupsById.has(sourceNodeId)) {
|
|
670
|
+
throw new Error(`Diagram edge ${edge.from}->${edge.to} references missing source "${sourceNodeId}".`);
|
|
671
|
+
}
|
|
672
|
+
if (!nodeIds.has(targetNodeId) && !groupsById.has(targetNodeId)) {
|
|
673
|
+
throw new Error(`Diagram edge ${edge.from}->${edge.to} references missing target "${targetNodeId}".`);
|
|
674
|
+
}
|
|
675
|
+
const sourcePortId = edge.from.includes(':') ? edge.from.slice(edge.from.indexOf(':') + 1) : undefined;
|
|
676
|
+
const targetPortId = edge.to.includes(':') ? edge.to.slice(edge.to.indexOf(':') + 1) : undefined;
|
|
677
|
+
if (sourcePortId && !(nodesById.get(sourceNodeId)?.ports?.some(port => port.id === sourcePortId) || groupsById.get(sourceNodeId)?.ports?.some(port => port.id === sourcePortId))) {
|
|
678
|
+
throw new Error(`Diagram edge ${edge.from}->${edge.to} references missing source port "${sourcePortId}" on "${sourceNodeId}".`);
|
|
679
|
+
}
|
|
680
|
+
if (targetPortId && !(nodesById.get(targetNodeId)?.ports?.some(port => port.id === targetPortId) || groupsById.get(targetNodeId)?.ports?.some(port => port.id === targetPortId))) {
|
|
681
|
+
throw new Error(`Diagram edge ${edge.from}->${edge.to} references missing target port "${targetPortId}" on "${targetNodeId}".`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
for (const annotation of ir.annotations) {
|
|
685
|
+
if (annotation.lineGap !== undefined && (!Number.isFinite(annotation.lineGap) || annotation.lineGap < 0)) {
|
|
686
|
+
throw new Error(`Diagram.Text lineGap must be a non-negative finite number, got ${annotation.lineGap}.`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
export function validateTimeline(ir) {
|
|
691
|
+
const laneIds = new Set();
|
|
692
|
+
for (const lane of ir.lanes) {
|
|
693
|
+
if (laneIds.has(lane.id)) throw new Error(`Duplicate Diagram lane id "${lane.id}".`);
|
|
694
|
+
laneIds.add(lane.id);
|
|
695
|
+
}
|
|
696
|
+
const signals = [...ir.signals, ...(ir.frame?.signals ?? [])];
|
|
697
|
+
for (const signal of signals) {
|
|
698
|
+
if (!laneIds.has(signal.from)) {
|
|
699
|
+
throw new Error(`Diagram signal ${signal.from}->${signal.to} references missing lane "${signal.from}".`);
|
|
700
|
+
}
|
|
701
|
+
if (!laneIds.has(signal.to)) {
|
|
702
|
+
throw new Error(`Diagram signal ${signal.from}->${signal.to} references missing lane "${signal.to}".`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
function endpointNodeId(ref) {
|
|
707
|
+
const separator = ref.indexOf(':');
|
|
708
|
+
if (separator === -1) return ref;
|
|
709
|
+
return ref.slice(0, separator);
|
|
710
|
+
}
|
|
711
|
+
//# sourceMappingURL=diagram-compile.js.map
|