@graph-artifact/core 0.1.0
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 +62 -0
- package/dist/ThemeContext.d.ts +47 -0
- package/dist/ThemeContext.js +81 -0
- package/dist/components/DiagramCanvas.d.ts +8 -0
- package/dist/components/DiagramCanvas.js +19 -0
- package/dist/components/GraphCanvas.d.ts +9 -0
- package/dist/components/GraphCanvas.js +104 -0
- package/dist/components/NodeDetail.d.ts +9 -0
- package/dist/components/NodeDetail.js +127 -0
- package/dist/components/edges/RoutedEdge.d.ts +11 -0
- package/dist/components/edges/RoutedEdge.js +199 -0
- package/dist/components/nodes/ClassNode.d.ts +5 -0
- package/dist/components/nodes/ClassNode.js +62 -0
- package/dist/components/nodes/EntityNode.d.ts +5 -0
- package/dist/components/nodes/EntityNode.js +57 -0
- package/dist/components/nodes/FlowNode.d.ts +5 -0
- package/dist/components/nodes/FlowNode.js +144 -0
- package/dist/components/nodes/SequenceNodes.d.ts +33 -0
- package/dist/components/nodes/SequenceNodes.js +205 -0
- package/dist/components/nodes/StateNode.d.ts +5 -0
- package/dist/components/nodes/StateNode.js +71 -0
- package/dist/components/nodes/SubgraphNode.d.ts +5 -0
- package/dist/components/nodes/SubgraphNode.js +16 -0
- package/dist/config.d.ts +138 -0
- package/dist/config.js +165 -0
- package/dist/core.d.ts +12 -0
- package/dist/core.js +7 -0
- package/dist/diagrams/detect.d.ts +2 -0
- package/dist/diagrams/detect.js +8 -0
- package/dist/diagrams/plugins.d.ts +2 -0
- package/dist/diagrams/plugins.js +45 -0
- package/dist/diagrams/registry.d.ts +3 -0
- package/dist/diagrams/registry.js +13 -0
- package/dist/diagrams/types.d.ts +7 -0
- package/dist/diagrams/types.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/layout/dagre/index.d.ts +31 -0
- package/dist/layout/dagre/index.js +224 -0
- package/dist/layout/dagre/nodeSizing.d.ts +32 -0
- package/dist/layout/dagre/nodeSizing.js +202 -0
- package/dist/layout/edges/buildEdges.d.ts +18 -0
- package/dist/layout/edges/buildEdges.js +405 -0
- package/dist/layout/edges/classify.d.ts +13 -0
- package/dist/layout/edges/classify.js +36 -0
- package/dist/layout/edges/diamondHandles.d.ts +23 -0
- package/dist/layout/edges/diamondHandles.js +108 -0
- package/dist/layout/edges/index.d.ts +10 -0
- package/dist/layout/edges/index.js +8 -0
- package/dist/layout/edges/paths.d.ts +57 -0
- package/dist/layout/edges/paths.js +279 -0
- package/dist/layout/index.d.ts +59 -0
- package/dist/layout/index.js +131 -0
- package/dist/layout/intersect/circle.d.ts +2 -0
- package/dist/layout/intersect/circle.js +14 -0
- package/dist/layout/intersect/diamond.d.ts +9 -0
- package/dist/layout/intersect/diamond.js +21 -0
- package/dist/layout/intersect/index.d.ts +17 -0
- package/dist/layout/intersect/index.js +28 -0
- package/dist/layout/intersect/rect.d.ts +10 -0
- package/dist/layout/intersect/rect.js +31 -0
- package/dist/layout/intersect/rectRounded.d.ts +20 -0
- package/dist/layout/intersect/rectRounded.js +48 -0
- package/dist/layout/mindmapLayout.d.ts +13 -0
- package/dist/layout/mindmapLayout.js +299 -0
- package/dist/layout/sequenceLayout.d.ts +24 -0
- package/dist/layout/sequenceLayout.js +414 -0
- package/dist/layout/subgraph.d.ts +26 -0
- package/dist/layout/subgraph.js +63 -0
- package/dist/layout/types.d.ts +34 -0
- package/dist/layout/types.js +8 -0
- package/dist/parsers/classDiagram.d.ts +2 -0
- package/dist/parsers/classDiagram.js +105 -0
- package/dist/parsers/er.d.ts +2 -0
- package/dist/parsers/er.js +97 -0
- package/dist/parsers/flowchart.d.ts +2 -0
- package/dist/parsers/flowchart.js +191 -0
- package/dist/parsers/helpers.d.ts +4 -0
- package/dist/parsers/helpers.js +8 -0
- package/dist/parsers/index.d.ts +7 -0
- package/dist/parsers/index.js +19 -0
- package/dist/parsers/mindmap.d.ts +2 -0
- package/dist/parsers/mindmap.js +124 -0
- package/dist/parsers/sequence.d.ts +18 -0
- package/dist/parsers/sequence.js +196 -0
- package/dist/parsers/state.d.ts +2 -0
- package/dist/parsers/state.js +68 -0
- package/dist/react.d.ts +7 -0
- package/dist/react.js +9 -0
- package/dist/reactDefaults.d.ts +5 -0
- package/dist/reactDefaults.js +37 -0
- package/dist/renderMarkdown.d.ts +9 -0
- package/dist/renderMarkdown.js +103 -0
- package/dist/swagger.d.ts +113 -0
- package/dist/swagger.js +551 -0
- package/dist/theme/dark.d.ts +8 -0
- package/dist/theme/dark.js +190 -0
- package/dist/theme/index.d.ts +18 -0
- package/dist/theme/index.js +29 -0
- package/dist/theme/light.d.ts +8 -0
- package/dist/theme/light.js +190 -0
- package/dist/theme/types.d.ts +97 -0
- package/dist/theme/types.js +7 -0
- package/dist/types.d.ts +235 -0
- package/dist/types.js +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* RoutedEdge — Renders a pre-computed SVG path from dagre waypoints,
|
|
4
|
+
* with optional ER cardinality markers (crow's foot notation).
|
|
5
|
+
*
|
|
6
|
+
* Instead of using SVG <marker> definitions (which have unreliable
|
|
7
|
+
* orient/refX behavior across browsers), we draw cardinality symbols
|
|
8
|
+
* as explicit SVG elements at the path endpoints using the tangent
|
|
9
|
+
* angle computed during edge routing.
|
|
10
|
+
*/
|
|
11
|
+
import { BaseEdge } from '@xyflow/react';
|
|
12
|
+
import { useTheme } from '../../ThemeContext.js';
|
|
13
|
+
// ─── Cardinality Drawing ────────────────────────────────────────────────────
|
|
14
|
+
//
|
|
15
|
+
// Matches Mermaid's crow's foot notation:
|
|
16
|
+
//
|
|
17
|
+
// ONLY_ONE || Two perpendicular lines near node
|
|
18
|
+
// ZERO_OR_ONE o| Perpendicular line near node, circle farther away
|
|
19
|
+
// ONE_OR_MORE |{ Perpendicular line at node, crow's foot fanning outward
|
|
20
|
+
// ZERO_OR_MORE o{ Crow's foot fanning outward, circle beyond tips
|
|
21
|
+
//
|
|
22
|
+
// Coordinate system:
|
|
23
|
+
// `d` = distance from node boundary (0 = at boundary, positive = away from node)
|
|
24
|
+
// `p` = perpendicular offset (positive = left when facing away from node)
|
|
25
|
+
const SW = 1.5; // stroke width
|
|
26
|
+
const H = 7; // half-height of perpendicular tick marks
|
|
27
|
+
const CR = 5; // circle radius
|
|
28
|
+
const FOOT = 10; // crow's foot length (base to tips)
|
|
29
|
+
const NODE_CLEARANCE = 2; // keep symbols just outside node boundary
|
|
30
|
+
function snapAngleToCardinal(angle) {
|
|
31
|
+
const cx = Math.cos(angle);
|
|
32
|
+
const cy = Math.sin(angle);
|
|
33
|
+
const ax = Math.abs(cx);
|
|
34
|
+
const ay = Math.abs(cy);
|
|
35
|
+
// Near-vertical
|
|
36
|
+
if (ax < 0.12)
|
|
37
|
+
return cy >= 0 ? Math.PI / 2 : -Math.PI / 2;
|
|
38
|
+
// Near-horizontal
|
|
39
|
+
if (ay < 0.12)
|
|
40
|
+
return cx >= 0 ? 0 : Math.PI;
|
|
41
|
+
return angle;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Draws a cardinality symbol at the path endpoint, oriented along the edge.
|
|
45
|
+
*
|
|
46
|
+
* `angle` points FROM the path toward the node (in radians).
|
|
47
|
+
* We draw AWAY from the node, so we negate the angle direction.
|
|
48
|
+
*/
|
|
49
|
+
function CardinalitySymbol({ card, token, isSourceEnd, x, y, angle, color, bgColor, }) {
|
|
50
|
+
const snapped = snapAngleToCardinal(angle);
|
|
51
|
+
// "away" direction: opposite of angle (away from node)
|
|
52
|
+
const awayX = -Math.cos(snapped);
|
|
53
|
+
const awayY = -Math.sin(snapped);
|
|
54
|
+
// perpendicular (90° CCW from away direction)
|
|
55
|
+
const perpX = -awayY;
|
|
56
|
+
const perpY = awayX;
|
|
57
|
+
// Point at (d, p): d = distance away from node, p = perpendicular offset
|
|
58
|
+
function pt(d, p) {
|
|
59
|
+
return [
|
|
60
|
+
x + awayX * (d + NODE_CLEARANCE) + perpX * p,
|
|
61
|
+
y + awayY * (d + NODE_CLEARANCE) + perpY * p,
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
const elements = [];
|
|
65
|
+
function line(d1, p1, d2, p2, key) {
|
|
66
|
+
const [x1, y1] = pt(d1, p1);
|
|
67
|
+
const [x2, y2] = pt(d2, p2);
|
|
68
|
+
elements.push(_jsx("line", { x1: x1, y1: y1, x2: x2, y2: y2, stroke: color, strokeWidth: SW }, key));
|
|
69
|
+
}
|
|
70
|
+
function circle(d, key) {
|
|
71
|
+
const [cx, cy] = pt(d, 0);
|
|
72
|
+
elements.push(_jsx("circle", { cx: cx, cy: cy, r: CR, stroke: color, strokeWidth: SW, fill: bgColor }, key));
|
|
73
|
+
}
|
|
74
|
+
function tick(d, key) {
|
|
75
|
+
line(d, -H, d, H, key);
|
|
76
|
+
}
|
|
77
|
+
// Crow's foot: 3 lines from a point near node fanning to spread tips away
|
|
78
|
+
function crowsFoot(baseD) {
|
|
79
|
+
const tipD = baseD + FOOT;
|
|
80
|
+
line(baseD, 0, tipD, -H, 'f1');
|
|
81
|
+
line(baseD, 0, tipD, 0, 'f2');
|
|
82
|
+
line(baseD, 0, tipD, H, 'f3');
|
|
83
|
+
}
|
|
84
|
+
function glyphKindFromTokenChar(ch) {
|
|
85
|
+
if (!ch)
|
|
86
|
+
return null;
|
|
87
|
+
if (ch === '|')
|
|
88
|
+
return 'tick';
|
|
89
|
+
if (ch === 'o')
|
|
90
|
+
return 'circle';
|
|
91
|
+
if (ch === '{' || ch === '}')
|
|
92
|
+
return 'crow';
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
function resolveNearFar(cardinality) {
|
|
96
|
+
let near;
|
|
97
|
+
let far;
|
|
98
|
+
switch (cardinality) {
|
|
99
|
+
case 'ZERO_OR_ONE':
|
|
100
|
+
near = 'tick';
|
|
101
|
+
far = 'circle';
|
|
102
|
+
break;
|
|
103
|
+
case 'ONE_OR_MORE':
|
|
104
|
+
near = 'tick';
|
|
105
|
+
far = 'crow';
|
|
106
|
+
break;
|
|
107
|
+
case 'ZERO_OR_MORE':
|
|
108
|
+
near = 'crow';
|
|
109
|
+
far = 'circle';
|
|
110
|
+
break;
|
|
111
|
+
default:
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
if (token && token.length >= 2) {
|
|
115
|
+
const nearChar = isSourceEnd ? token[0] : token[token.length - 1];
|
|
116
|
+
const farChar = isSourceEnd ? token[token.length - 1] : token[0];
|
|
117
|
+
const nearFromToken = glyphKindFromTokenChar(nearChar);
|
|
118
|
+
const farFromToken = glyphKindFromTokenChar(farChar);
|
|
119
|
+
if (nearFromToken && farFromToken) {
|
|
120
|
+
const expected = [near, far].sort().join(':');
|
|
121
|
+
const tokenKinds = [nearFromToken, farFromToken].sort().join(':');
|
|
122
|
+
if (expected === tokenKinds) {
|
|
123
|
+
return { near: nearFromToken, far: farFromToken };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { near, far };
|
|
128
|
+
}
|
|
129
|
+
function drawOrdered(near, far) {
|
|
130
|
+
if (near === 'tick' && far === 'circle') {
|
|
131
|
+
tick(0, 't1');
|
|
132
|
+
circle(6 + CR, 'c1');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (near === 'circle' && far === 'tick') {
|
|
136
|
+
circle(CR, 'c1');
|
|
137
|
+
tick(10, 't1');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (near === 'tick' && far === 'crow') {
|
|
141
|
+
tick(0, 't1');
|
|
142
|
+
crowsFoot(4);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (near === 'crow' && far === 'tick') {
|
|
146
|
+
crowsFoot(0);
|
|
147
|
+
tick(FOOT + 2, 't1');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (near === 'crow' && far === 'circle') {
|
|
151
|
+
crowsFoot(0);
|
|
152
|
+
circle(FOOT + CR + 2, 'c1');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (near === 'circle' && far === 'crow') {
|
|
156
|
+
circle(CR, 'c1');
|
|
157
|
+
crowsFoot(CR + 2);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
switch (card) {
|
|
161
|
+
case 'ONLY_ONE': // || — two tick marks
|
|
162
|
+
tick(0, 't1');
|
|
163
|
+
tick(6, 't2');
|
|
164
|
+
break;
|
|
165
|
+
case 'ZERO_OR_ONE':
|
|
166
|
+
case 'ONE_OR_MORE':
|
|
167
|
+
case 'ZERO_OR_MORE': {
|
|
168
|
+
const ordered = resolveNearFar(card);
|
|
169
|
+
if (ordered)
|
|
170
|
+
drawOrdered(ordered.near, ordered.far);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return _jsx("g", { children: elements });
|
|
175
|
+
}
|
|
176
|
+
// ─── Main Component ──────────────────────────────────────────────────────────
|
|
177
|
+
export function RoutedEdge({ data, markerEnd, style, label, labelStyle, labelBgStyle, labelBgPadding, labelBgBorderRadius, }) {
|
|
178
|
+
const theme = useTheme();
|
|
179
|
+
const svgPath = data?.svgPath ?? '';
|
|
180
|
+
const labelX = data?.labelX ?? 0;
|
|
181
|
+
const labelY = data?.labelY ?? 0;
|
|
182
|
+
const cardSource = data?.cardSource;
|
|
183
|
+
const cardTarget = data?.cardTarget;
|
|
184
|
+
const cardSourceToken = data?.cardSourceToken;
|
|
185
|
+
const cardTargetToken = data?.cardTargetToken;
|
|
186
|
+
if (!svgPath)
|
|
187
|
+
return null;
|
|
188
|
+
const edgeColor = style?.stroke ?? String(theme.edgeDefaults.style.stroke ?? theme.color.gray3);
|
|
189
|
+
const bgColor = data?.bgColor ?? theme.color.darkBg2;
|
|
190
|
+
const hasCardinality = cardSource || cardTarget;
|
|
191
|
+
// Endpoint data passed directly from edge routing (no SVG parsing needed)
|
|
192
|
+
const startX = data?.pathStartX;
|
|
193
|
+
const startY = data?.pathStartY;
|
|
194
|
+
const startAngle = data?.pathStartAngle;
|
|
195
|
+
const endX = data?.pathEndX;
|
|
196
|
+
const endY = data?.pathEndY;
|
|
197
|
+
const endAngle = data?.pathEndAngle;
|
|
198
|
+
return (_jsxs(_Fragment, { children: [_jsx(BaseEdge, { path: svgPath, markerEnd: hasCardinality ? undefined : markerEnd, style: style, label: label, labelX: labelX, labelY: labelY, labelStyle: labelStyle, labelBgStyle: labelBgStyle, labelBgPadding: labelBgPadding, labelBgBorderRadius: labelBgBorderRadius }), cardSource && startX != null && startY != null && startAngle != null && (_jsx(CardinalitySymbol, { card: cardSource, token: cardSourceToken, isSourceEnd: true, x: startX, y: startY, angle: startAngle, color: edgeColor, bgColor: bgColor })), cardTarget && endX != null && endY != null && endAngle != null && (_jsx(CardinalitySymbol, { card: cardTarget, token: cardTargetToken, isSourceEnd: false, x: endX, y: endY, angle: endAngle, color: edgeColor, bgColor: bgColor }))] }));
|
|
199
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { Handle, Position } from '@xyflow/react';
|
|
4
|
+
import { useTheme } from '../../ThemeContext.js';
|
|
5
|
+
const invisibleHandle = {
|
|
6
|
+
opacity: 0,
|
|
7
|
+
width: 1,
|
|
8
|
+
height: 1,
|
|
9
|
+
border: 'none',
|
|
10
|
+
background: 'transparent',
|
|
11
|
+
pointerEvents: 'none',
|
|
12
|
+
};
|
|
13
|
+
function spreadPositions(count) {
|
|
14
|
+
const n = Math.max(1, count);
|
|
15
|
+
return Array.from({ length: n }, (_, i) => ((i + 1) / (n + 1)) * 100);
|
|
16
|
+
}
|
|
17
|
+
const SIDE_SLOTS = 5;
|
|
18
|
+
const sideSlotPositions = spreadPositions(SIDE_SLOTS);
|
|
19
|
+
function SideHandles({ side }) {
|
|
20
|
+
const position = side === 'left' ? Position.Left : Position.Right;
|
|
21
|
+
return (_jsxs(_Fragment, { children: [sideSlotPositions.map((pct, i) => (_jsx(Handle, { type: "target", id: `back-${side}-target-${i}`, position: position, style: { ...invisibleHandle, top: `${pct}%` } }, `${side}-t-${i}`))), sideSlotPositions.map((pct, i) => (_jsx(Handle, { type: "source", id: `back-${side}-source-${i}`, position: position, style: { ...invisibleHandle, top: `${pct}%` } }, `${side}-s-${i}`)))] }));
|
|
22
|
+
}
|
|
23
|
+
function ClassHandles({ fwIn, fwOut }) {
|
|
24
|
+
const targetPositions = spreadPositions(fwIn);
|
|
25
|
+
const sourcePositions = spreadPositions(fwOut);
|
|
26
|
+
return (_jsxs(_Fragment, { children: [targetPositions.map((pct, i) => (_jsx(Handle, { type: "target", id: `fw-target-${i}`, position: Position.Top, style: { ...invisibleHandle, left: `${pct}%` } }, `ft-${i}`))), sourcePositions.map((pct, i) => (_jsx(Handle, { type: "source", id: `fw-source-${i}`, position: Position.Bottom, style: { ...invisibleHandle, left: `${pct}%` } }, `fs-${i}`))), _jsx(SideHandles, { side: "left" }), _jsx(SideHandles, { side: "right" }), _jsx(Handle, { type: "target", id: "lateral-target", position: Position.Left, style: invisibleHandle }), _jsx(Handle, { type: "source", id: "lateral-source", position: Position.Right, style: invisibleHandle })] }));
|
|
27
|
+
}
|
|
28
|
+
export const ClassNode = memo(function ClassNode({ data }) {
|
|
29
|
+
const theme = useTheme();
|
|
30
|
+
const label = String(data.label ?? '');
|
|
31
|
+
const props = data.properties ?? [];
|
|
32
|
+
const methods = data.methods ?? [];
|
|
33
|
+
const ns = theme.nodeStyles.class;
|
|
34
|
+
const fwIn = Math.max(1, data.fwIn ?? 1);
|
|
35
|
+
const fwOut = Math.max(1, data.fwOut ?? 1);
|
|
36
|
+
const layoutWidth = data.layoutWidth;
|
|
37
|
+
const layoutHeight = data.layoutHeight;
|
|
38
|
+
return (_jsxs("div", { style: {
|
|
39
|
+
...theme.nodeBase.card,
|
|
40
|
+
fontFamily: theme.font.mono,
|
|
41
|
+
width: layoutWidth,
|
|
42
|
+
}, children: [_jsx("div", { style: {
|
|
43
|
+
...theme.nodeBase.header,
|
|
44
|
+
fontFamily: theme.font.family,
|
|
45
|
+
whiteSpace: 'nowrap',
|
|
46
|
+
overflow: 'hidden',
|
|
47
|
+
textOverflow: 'ellipsis',
|
|
48
|
+
}, title: label, children: label }), props.length > 0 && (_jsx("div", { style: {
|
|
49
|
+
padding: `${theme.space[2]} ${theme.space[3]}`, ...theme.nodeBase.divider,
|
|
50
|
+
}, children: props.map((p, i) => (_jsx("div", { style: {
|
|
51
|
+
fontSize: theme.font.size.sm, color: theme.color.gray4,
|
|
52
|
+
padding: ns.rowPadding, lineHeight: theme.lineHeight.relaxed,
|
|
53
|
+
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
|
|
54
|
+
}, children: _jsx("span", { title: p, children: p.replace(/^([+\-#~])/, '$1 ').trim() }) }, i))) })), methods.length > 0 && (_jsx("div", { style: { padding: `${theme.space[2]} ${theme.space[3]}` }, children: methods.map((m, i) => (_jsx("div", { style: {
|
|
55
|
+
fontSize: theme.font.size.sm, color: theme.color.blue3,
|
|
56
|
+
padding: ns.rowPadding, lineHeight: theme.lineHeight.relaxed,
|
|
57
|
+
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
|
|
58
|
+
}, children: _jsx("span", { title: m, children: m.replace(/^([+\-#~])/, '$1 ').trim() }) }, i))) })), props.length === 0 && methods.length === 0 && (_jsx("div", { style: {
|
|
59
|
+
padding: `${theme.space[2]} ${theme.space[3]}`, fontSize: theme.font.size.sm,
|
|
60
|
+
color: theme.color.gray3, fontStyle: 'italic', textAlign: 'center',
|
|
61
|
+
}, children: "(empty)" })), _jsx(ClassHandles, { fwIn: fwIn, fwOut: fwOut })] }));
|
|
62
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { Handle, Position } from '@xyflow/react';
|
|
4
|
+
import { useTheme } from '../../ThemeContext.js';
|
|
5
|
+
const invisibleHandle = {
|
|
6
|
+
opacity: 0,
|
|
7
|
+
width: 1,
|
|
8
|
+
height: 1,
|
|
9
|
+
border: 'none',
|
|
10
|
+
background: 'transparent',
|
|
11
|
+
pointerEvents: 'none',
|
|
12
|
+
};
|
|
13
|
+
function spreadPositions(count) {
|
|
14
|
+
const n = Math.max(1, count);
|
|
15
|
+
return Array.from({ length: n }, (_, i) => ((i + 1) / (n + 1)) * 100);
|
|
16
|
+
}
|
|
17
|
+
const SIDE_SLOTS = 5;
|
|
18
|
+
const sideSlotPositions = spreadPositions(SIDE_SLOTS);
|
|
19
|
+
function SideHandles({ side }) {
|
|
20
|
+
const position = side === 'left' ? Position.Left : Position.Right;
|
|
21
|
+
return (_jsxs(_Fragment, { children: [sideSlotPositions.map((pct, i) => (_jsx(Handle, { type: "target", id: `back-${side}-target-${i}`, position: position, style: { ...invisibleHandle, top: `${pct}%` } }, `${side}-t-${i}`))), sideSlotPositions.map((pct, i) => (_jsx(Handle, { type: "source", id: `back-${side}-source-${i}`, position: position, style: { ...invisibleHandle, top: `${pct}%` } }, `${side}-s-${i}`)))] }));
|
|
22
|
+
}
|
|
23
|
+
function EntityHandles({ fwIn, fwOut }) {
|
|
24
|
+
const targetPositions = spreadPositions(fwIn);
|
|
25
|
+
const sourcePositions = spreadPositions(fwOut);
|
|
26
|
+
return (_jsxs(_Fragment, { children: [targetPositions.map((pct, i) => (_jsx(Handle, { type: "target", id: `fw-target-${i}`, position: Position.Top, style: { ...invisibleHandle, left: `${pct}%` } }, `ft-${i}`))), sourcePositions.map((pct, i) => (_jsx(Handle, { type: "source", id: `fw-source-${i}`, position: Position.Bottom, style: { ...invisibleHandle, left: `${pct}%` } }, `fs-${i}`))), _jsx(SideHandles, { side: "left" }), _jsx(SideHandles, { side: "right" }), _jsx(Handle, { type: "target", id: "lateral-target", position: Position.Left, style: invisibleHandle }), _jsx(Handle, { type: "source", id: "lateral-source", position: Position.Right, style: invisibleHandle })] }));
|
|
27
|
+
}
|
|
28
|
+
export const EntityNode = memo(function EntityNode({ data }) {
|
|
29
|
+
const theme = useTheme();
|
|
30
|
+
const label = String(data.label ?? '');
|
|
31
|
+
const attrs = data.attributes ?? [];
|
|
32
|
+
const ns = theme.nodeStyles.entity;
|
|
33
|
+
const fwIn = Math.max(1, data.fwIn ?? 1);
|
|
34
|
+
const fwOut = Math.max(1, data.fwOut ?? 1);
|
|
35
|
+
const layoutWidth = data.layoutWidth;
|
|
36
|
+
const layoutHeight = data.layoutHeight;
|
|
37
|
+
return (_jsxs("div", { style: {
|
|
38
|
+
...theme.nodeBase.card,
|
|
39
|
+
width: layoutWidth,
|
|
40
|
+
}, children: [_jsx("div", { style: { ...theme.nodeBase.header, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, title: label, children: label }), attrs.length > 0 && (_jsx("div", { style: { padding: `${theme.space[2]} ${theme.space[3]}` }, children: attrs.map((attr, i) => {
|
|
41
|
+
const parts = attr.split(' ');
|
|
42
|
+
const type = parts[0] ?? '';
|
|
43
|
+
const name = parts[1] ?? '';
|
|
44
|
+
const badge = parts[2];
|
|
45
|
+
const badgeVariant = badge === 'PK' ? 'primary' : badge === 'FK' ? 'secondary' : 'muted';
|
|
46
|
+
return (_jsxs("div", { style: {
|
|
47
|
+
display: 'flex', alignItems: 'center', gap: theme.space[2],
|
|
48
|
+
padding: `${theme.space[1]} 0`,
|
|
49
|
+
borderBottom: i < attrs.length - 1 ? `${theme.borderWidth.sm} solid ${theme.color.gray2}` : 'none',
|
|
50
|
+
fontSize: theme.font.size.md,
|
|
51
|
+
}, children: [_jsx("span", { style: {
|
|
52
|
+
color: theme.color.gray3, minWidth: ns.typeColumnWidth,
|
|
53
|
+
fontFamily: theme.font.mono, fontSize: theme.font.size.sm,
|
|
54
|
+
flexShrink: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
|
|
55
|
+
}, children: _jsx("span", { title: type, children: type }) }), _jsx("span", { style: { color: theme.color.gray4, flex: 1, minWidth: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, title: name, children: name }), badge && _jsx("span", { style: theme.nodeBase.badge(badgeVariant), children: badge })] }, i));
|
|
56
|
+
}) })), _jsx(EntityHandles, { fwIn: fwIn, fwOut: fwOut })] }));
|
|
57
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { Handle, Position } from '@xyflow/react';
|
|
4
|
+
import ReactMarkdown from 'react-markdown';
|
|
5
|
+
import { useTheme } from '../../ThemeContext.js';
|
|
6
|
+
import { getNodeChild } from '../../config.js';
|
|
7
|
+
const invisibleHandle = {
|
|
8
|
+
opacity: 0,
|
|
9
|
+
width: 1,
|
|
10
|
+
height: 1,
|
|
11
|
+
border: 'none',
|
|
12
|
+
background: 'transparent',
|
|
13
|
+
pointerEvents: 'none',
|
|
14
|
+
};
|
|
15
|
+
function normalizeLabelMarkdown(label) {
|
|
16
|
+
return label.replace(/\\n/g, '\n').trim();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generates evenly-spaced handle positions across a face.
|
|
20
|
+
* For 1 handle: [50%], for 2: [33%, 67%], for 3: [25%, 50%, 75%], etc.
|
|
21
|
+
*/
|
|
22
|
+
function spreadPositions(count) {
|
|
23
|
+
const n = Math.max(1, count);
|
|
24
|
+
return Array.from({ length: n }, (_, i) => ((i + 1) / (n + 1)) * 100);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Forward-edge handles spread across a face so multiple edges
|
|
28
|
+
* don't converge at the same point. Back-edge + lateral handles
|
|
29
|
+
* remain at fixed side positions.
|
|
30
|
+
*/
|
|
31
|
+
const SIDE_SLOTS = 5;
|
|
32
|
+
const sideSlotPositions = spreadPositions(SIDE_SLOTS); // [~17%, 33%, 50%, 67%, 83%]
|
|
33
|
+
function SideHandles({ side }) {
|
|
34
|
+
const position = side === 'left' ? Position.Left : Position.Right;
|
|
35
|
+
return (_jsxs(_Fragment, { children: [sideSlotPositions.map((pct, i) => (_jsx(Handle, { type: "target", id: `back-${side}-target-${i}`, position: position, style: { ...invisibleHandle, top: `${pct}%` } }, `${side}-t-${i}`))), sideSlotPositions.map((pct, i) => (_jsx(Handle, { type: "source", id: `back-${side}-source-${i}`, position: position, style: { ...invisibleHandle, top: `${pct}%` } }, `${side}-s-${i}`)))] }));
|
|
36
|
+
}
|
|
37
|
+
function NodeHandles({ fwIn, fwOut }) {
|
|
38
|
+
const targetPositions = spreadPositions(fwIn);
|
|
39
|
+
const sourcePositions = spreadPositions(fwOut);
|
|
40
|
+
return (_jsxs(_Fragment, { children: [targetPositions.map((pct, i) => (_jsx(Handle, { type: "target", id: `fw-target-${i}`, position: Position.Top, style: { ...invisibleHandle, left: `${pct}%` } }, `ft-${i}`))), sourcePositions.map((pct, i) => (_jsx(Handle, { type: "source", id: `fw-source-${i}`, position: Position.Bottom, style: { ...invisibleHandle, left: `${pct}%` } }, `fs-${i}`))), _jsx(SideHandles, { side: "left" }), _jsx(SideHandles, { side: "right" }), _jsx(Handle, { type: "target", id: "lateral-target", position: Position.Left, style: invisibleHandle }), _jsx(Handle, { type: "source", id: "lateral-source", position: Position.Right, style: invisibleHandle })] }));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Diamond handles positioned based on target locations.
|
|
44
|
+
*
|
|
45
|
+
* The layout engine computes a `diamondHandleConfig` that specifies how many
|
|
46
|
+
* source handles are needed at each position (bottom, left, right) based on
|
|
47
|
+
* where each outgoing edge's target is located relative to the diamond center.
|
|
48
|
+
*
|
|
49
|
+
* Fallback: if no config is provided (single outgoing), uses bottom handle.
|
|
50
|
+
*/
|
|
51
|
+
function DiamondHandles({ handleConfig }) {
|
|
52
|
+
const bottom = handleConfig?.bottom ?? 1;
|
|
53
|
+
const left = handleConfig?.left ?? 0;
|
|
54
|
+
const right = handleConfig?.right ?? 0;
|
|
55
|
+
return (_jsxs(_Fragment, { children: [_jsx(Handle, { type: "target", id: "fw-target-0", position: Position.Top, style: invisibleHandle }), Array.from({ length: bottom }, (_, i) => (_jsx(Handle, { type: "source", id: `fw-source-bottom-${i}`, position: Position.Bottom, style: invisibleHandle }, `db-${i}`))), Array.from({ length: left }, (_, i) => (_jsx(Handle, { type: "source", id: `fw-source-left-${i}`, position: Position.Left, style: invisibleHandle }, `dl-${i}`))), Array.from({ length: right }, (_, i) => (_jsx(Handle, { type: "source", id: `fw-source-right-${i}`, position: Position.Right, style: invisibleHandle }, `dr-${i}`))), _jsx(Handle, { type: "source", id: "fw-source-0", position: Position.Bottom, style: invisibleHandle }), _jsx(SideHandles, { side: "left" }), _jsx(SideHandles, { side: "right" }), _jsx(Handle, { type: "target", id: "lateral-target", position: Position.Left, style: invisibleHandle }), _jsx(Handle, { type: "source", id: "lateral-source", position: Position.Right, style: invisibleHandle })] }));
|
|
56
|
+
}
|
|
57
|
+
export const FlowNode = memo(function FlowNode({ data }) {
|
|
58
|
+
const theme = useTheme();
|
|
59
|
+
const label = String(data.label ?? '');
|
|
60
|
+
const labelMarkdown = normalizeLabelMarkdown(label);
|
|
61
|
+
const shape = String(data.shape ?? 'rect');
|
|
62
|
+
const meta = data.meta;
|
|
63
|
+
const ns = theme.nodeStyles.flow;
|
|
64
|
+
const fwIn = Math.max(1, data.fwIn ?? 1);
|
|
65
|
+
const fwOut = Math.max(1, data.fwOut ?? 1);
|
|
66
|
+
const layoutWidth = data.layoutWidth;
|
|
67
|
+
const layoutHeight = data.layoutHeight;
|
|
68
|
+
const nodeBgColor = data.nodeBgColor ?? theme.color.orange1;
|
|
69
|
+
const nodeTextColor = data.nodeTextColor ?? theme.color.white;
|
|
70
|
+
const markdownComponents = {
|
|
71
|
+
p: ({ children }) => (_jsx("p", { style: { margin: 0, whiteSpace: 'pre-wrap', lineHeight: theme.lineHeight.normal }, children: children })),
|
|
72
|
+
strong: ({ children }) => (_jsx("strong", { style: { fontWeight: theme.font.weight.bold }, children: children })),
|
|
73
|
+
em: ({ children }) => (_jsx("em", { style: { fontStyle: 'italic' }, children: children })),
|
|
74
|
+
code: ({ children }) => (_jsx("code", { style: {
|
|
75
|
+
fontFamily: theme.font.mono,
|
|
76
|
+
fontSize: theme.font.size.sm,
|
|
77
|
+
backgroundColor: theme.nodeStyles.flow.markdownCode.backgroundColor,
|
|
78
|
+
borderRadius: theme.radius.xs,
|
|
79
|
+
padding: theme.nodeStyles.flow.markdownCode.padding,
|
|
80
|
+
}, children: children })),
|
|
81
|
+
ul: ({ children }) => (_jsx("ul", { style: { margin: 0, paddingLeft: theme.space[4], lineHeight: theme.lineHeight.normal }, children: children })),
|
|
82
|
+
ol: ({ children }) => (_jsx("ol", { style: { margin: 0, paddingLeft: theme.space[4], lineHeight: theme.lineHeight.normal }, children: children })),
|
|
83
|
+
li: ({ children }) => (_jsx("li", { style: { margin: 0 }, children: children })),
|
|
84
|
+
};
|
|
85
|
+
const ChildComponent = meta?.component ? getNodeChild(meta.component) : undefined;
|
|
86
|
+
if (shape === 'diamond') {
|
|
87
|
+
// Outer container = dagre's bounding box (the diagonal of the rotated square).
|
|
88
|
+
// Inner square = diagonal / sqrt(2), then rotated 45° to form the diamond shape.
|
|
89
|
+
const diagSize = layoutWidth;
|
|
90
|
+
const innerSize = diagSize / Math.SQRT2;
|
|
91
|
+
return (_jsxs("div", { style: {
|
|
92
|
+
width: diagSize, height: diagSize, position: 'relative',
|
|
93
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
94
|
+
}, children: [_jsx("div", { style: {
|
|
95
|
+
width: innerSize, height: innerSize, backgroundColor: theme.color.orange1,
|
|
96
|
+
transform: 'rotate(45deg)', borderRadius: theme.radius.md,
|
|
97
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
98
|
+
}, children: _jsx("span", { style: {
|
|
99
|
+
transform: 'rotate(-45deg)', color: theme.color.white, fontSize: theme.font.size.base,
|
|
100
|
+
fontWeight: theme.font.weight.bold, fontFamily: theme.font.family,
|
|
101
|
+
textAlign: 'center', lineHeight: theme.lineHeight.tight,
|
|
102
|
+
padding: theme.space[2], maxWidth: ns.diamond.labelMaxWidth, overflowWrap: 'break-word',
|
|
103
|
+
}, children: _jsx(ReactMarkdown, { components: markdownComponents, children: labelMarkdown }) }) }), _jsx(DiamondHandles, { handleConfig: data.diamondHandleConfig })] }));
|
|
104
|
+
}
|
|
105
|
+
if (shape === 'circle') {
|
|
106
|
+
return (_jsxs("div", { style: {
|
|
107
|
+
width: layoutWidth, height: layoutHeight,
|
|
108
|
+
backgroundColor: nodeBgColor, borderRadius: theme.radius.full,
|
|
109
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
110
|
+
fontFamily: theme.font.family,
|
|
111
|
+
}, children: [_jsx(Handle, { type: "target", id: "fw-target-0", position: Position.Top, style: invisibleHandle }), _jsx("span", { style: {
|
|
112
|
+
color: nodeTextColor, fontSize: theme.font.size.md, fontWeight: theme.font.weight.bold,
|
|
113
|
+
textAlign: 'center', padding: theme.space[1], maxWidth: '100%', overflowWrap: 'break-word',
|
|
114
|
+
}, children: _jsx(ReactMarkdown, { components: markdownComponents, children: labelMarkdown }) }), _jsx(Handle, { type: "source", id: "fw-source-0", position: Position.Bottom, style: invisibleHandle }), _jsx(NodeHandles, { fwIn: fwIn, fwOut: fwOut })] }));
|
|
115
|
+
}
|
|
116
|
+
if (shape === 'cylinder') {
|
|
117
|
+
const w = layoutWidth;
|
|
118
|
+
const h = layoutHeight - 10; // subtract ry for inner body height
|
|
119
|
+
const ry = 10; // ellipse vertical radius for the cap
|
|
120
|
+
return (_jsxs("div", { style: {
|
|
121
|
+
width: w, height: h + ry, position: 'relative',
|
|
122
|
+
fontFamily: theme.font.family, cursor: 'pointer',
|
|
123
|
+
}, children: [_jsxs("svg", { width: w, height: h + ry * 2, viewBox: `0 0 ${w} ${h + ry * 2}`, style: { position: 'absolute', top: -ry, left: 0 }, children: [_jsx("path", { d: `M0,${ry} L0,${h + ry} Q0,${h + ry * 2} ${w / 2},${h + ry * 2} Q${w},${h + ry * 2} ${w},${h + ry} L${w},${ry}`, fill: theme.color.orange1 }), _jsx("ellipse", { cx: w / 2, cy: ry, rx: w / 2, ry: ry, fill: theme.color.orange2 })] }), _jsx("div", { style: {
|
|
124
|
+
position: 'relative', width: '100%', height: '100%',
|
|
125
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
126
|
+
color: theme.color.white, fontSize: theme.font.size.md,
|
|
127
|
+
fontWeight: theme.font.weight.bold, textAlign: 'center',
|
|
128
|
+
paddingTop: ry / 2, overflowWrap: 'break-word',
|
|
129
|
+
}, children: _jsx(ReactMarkdown, { components: markdownComponents, children: labelMarkdown }) }), _jsx(NodeHandles, { fwIn: fwIn, fwOut: fwOut })] }));
|
|
130
|
+
}
|
|
131
|
+
const br = shape === 'stadium' ? theme.radius.stadium : shape === 'round' ? theme.radius.xl : theme.radius.md;
|
|
132
|
+
return (_jsxs("div", { style: {
|
|
133
|
+
backgroundColor: nodeBgColor, borderRadius: br,
|
|
134
|
+
padding: `${theme.space[3]} ${theme.space[5]}`,
|
|
135
|
+
width: layoutWidth, height: layoutHeight,
|
|
136
|
+
boxSizing: 'border-box',
|
|
137
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
138
|
+
fontFamily: theme.font.family, cursor: 'pointer',
|
|
139
|
+
}, children: [_jsx("div", { style: {
|
|
140
|
+
color: nodeTextColor, fontSize: theme.font.size.lg, fontWeight: theme.font.weight.bold,
|
|
141
|
+
textAlign: 'center', lineHeight: theme.lineHeight.normal,
|
|
142
|
+
whiteSpace: 'normal', overflowWrap: 'break-word', width: '100%',
|
|
143
|
+
}, children: _jsx(ReactMarkdown, { components: markdownComponents, children: labelMarkdown }) }), ChildComponent && (_jsx(ChildComponent, { nodeId: String(data.id ?? ''), label: label, componentProps: meta?.componentProps })), _jsx(NodeHandles, { fwIn: fwIn, fwOut: fwOut })] }));
|
|
144
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SequenceNodes.tsx — React Flow node components for sequence diagrams.
|
|
3
|
+
*
|
|
4
|
+
* Node types:
|
|
5
|
+
* - sequenceParticipant: Clickable participant boxes at top/bottom
|
|
6
|
+
* - sequenceLifeline: Dashed vertical line behind messages
|
|
7
|
+
* - sequenceAnchor: Invisible 2px nodes at message endpoints (edges attach here)
|
|
8
|
+
* - sequenceNote: Styled note boxes spanning attached participants
|
|
9
|
+
* - sequenceBlock: Alt/loop/opt overlay rectangles
|
|
10
|
+
* - sequenceMessageLabel: Text label positioned above message edges
|
|
11
|
+
* - sequenceNumber: Numbered circle on the lifeline for autonumbering
|
|
12
|
+
*/
|
|
13
|
+
interface ParticipantProps {
|
|
14
|
+
data: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
export declare const SequenceParticipantNode: import("react").NamedExoticComponent<ParticipantProps>;
|
|
17
|
+
export declare const SequenceLifelineNode: import("react").NamedExoticComponent<{
|
|
18
|
+
data: Record<string, unknown>;
|
|
19
|
+
}>;
|
|
20
|
+
export declare const SequenceAnchorNode: import("react").NamedExoticComponent<object>;
|
|
21
|
+
export declare const SequenceNoteNode: import("react").NamedExoticComponent<{
|
|
22
|
+
data: Record<string, unknown>;
|
|
23
|
+
}>;
|
|
24
|
+
export declare const SequenceMessageLabelNode: import("react").NamedExoticComponent<{
|
|
25
|
+
data: Record<string, unknown>;
|
|
26
|
+
}>;
|
|
27
|
+
export declare const SequenceNumberNode: import("react").NamedExoticComponent<{
|
|
28
|
+
data: Record<string, unknown>;
|
|
29
|
+
}>;
|
|
30
|
+
export declare const SequenceBlockNode: import("react").NamedExoticComponent<{
|
|
31
|
+
data: Record<string, unknown>;
|
|
32
|
+
}>;
|
|
33
|
+
export {};
|