@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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/dist/ThemeContext.d.ts +47 -0
  4. package/dist/ThemeContext.js +81 -0
  5. package/dist/components/DiagramCanvas.d.ts +8 -0
  6. package/dist/components/DiagramCanvas.js +19 -0
  7. package/dist/components/GraphCanvas.d.ts +9 -0
  8. package/dist/components/GraphCanvas.js +104 -0
  9. package/dist/components/NodeDetail.d.ts +9 -0
  10. package/dist/components/NodeDetail.js +127 -0
  11. package/dist/components/edges/RoutedEdge.d.ts +11 -0
  12. package/dist/components/edges/RoutedEdge.js +199 -0
  13. package/dist/components/nodes/ClassNode.d.ts +5 -0
  14. package/dist/components/nodes/ClassNode.js +62 -0
  15. package/dist/components/nodes/EntityNode.d.ts +5 -0
  16. package/dist/components/nodes/EntityNode.js +57 -0
  17. package/dist/components/nodes/FlowNode.d.ts +5 -0
  18. package/dist/components/nodes/FlowNode.js +144 -0
  19. package/dist/components/nodes/SequenceNodes.d.ts +33 -0
  20. package/dist/components/nodes/SequenceNodes.js +205 -0
  21. package/dist/components/nodes/StateNode.d.ts +5 -0
  22. package/dist/components/nodes/StateNode.js +71 -0
  23. package/dist/components/nodes/SubgraphNode.d.ts +5 -0
  24. package/dist/components/nodes/SubgraphNode.js +16 -0
  25. package/dist/config.d.ts +138 -0
  26. package/dist/config.js +165 -0
  27. package/dist/core.d.ts +12 -0
  28. package/dist/core.js +7 -0
  29. package/dist/diagrams/detect.d.ts +2 -0
  30. package/dist/diagrams/detect.js +8 -0
  31. package/dist/diagrams/plugins.d.ts +2 -0
  32. package/dist/diagrams/plugins.js +45 -0
  33. package/dist/diagrams/registry.d.ts +3 -0
  34. package/dist/diagrams/registry.js +13 -0
  35. package/dist/diagrams/types.d.ts +7 -0
  36. package/dist/diagrams/types.js +1 -0
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.js +3 -0
  39. package/dist/layout/dagre/index.d.ts +31 -0
  40. package/dist/layout/dagre/index.js +224 -0
  41. package/dist/layout/dagre/nodeSizing.d.ts +32 -0
  42. package/dist/layout/dagre/nodeSizing.js +202 -0
  43. package/dist/layout/edges/buildEdges.d.ts +18 -0
  44. package/dist/layout/edges/buildEdges.js +405 -0
  45. package/dist/layout/edges/classify.d.ts +13 -0
  46. package/dist/layout/edges/classify.js +36 -0
  47. package/dist/layout/edges/diamondHandles.d.ts +23 -0
  48. package/dist/layout/edges/diamondHandles.js +108 -0
  49. package/dist/layout/edges/index.d.ts +10 -0
  50. package/dist/layout/edges/index.js +8 -0
  51. package/dist/layout/edges/paths.d.ts +57 -0
  52. package/dist/layout/edges/paths.js +279 -0
  53. package/dist/layout/index.d.ts +59 -0
  54. package/dist/layout/index.js +131 -0
  55. package/dist/layout/intersect/circle.d.ts +2 -0
  56. package/dist/layout/intersect/circle.js +14 -0
  57. package/dist/layout/intersect/diamond.d.ts +9 -0
  58. package/dist/layout/intersect/diamond.js +21 -0
  59. package/dist/layout/intersect/index.d.ts +17 -0
  60. package/dist/layout/intersect/index.js +28 -0
  61. package/dist/layout/intersect/rect.d.ts +10 -0
  62. package/dist/layout/intersect/rect.js +31 -0
  63. package/dist/layout/intersect/rectRounded.d.ts +20 -0
  64. package/dist/layout/intersect/rectRounded.js +48 -0
  65. package/dist/layout/mindmapLayout.d.ts +13 -0
  66. package/dist/layout/mindmapLayout.js +299 -0
  67. package/dist/layout/sequenceLayout.d.ts +24 -0
  68. package/dist/layout/sequenceLayout.js +414 -0
  69. package/dist/layout/subgraph.d.ts +26 -0
  70. package/dist/layout/subgraph.js +63 -0
  71. package/dist/layout/types.d.ts +34 -0
  72. package/dist/layout/types.js +8 -0
  73. package/dist/parsers/classDiagram.d.ts +2 -0
  74. package/dist/parsers/classDiagram.js +105 -0
  75. package/dist/parsers/er.d.ts +2 -0
  76. package/dist/parsers/er.js +97 -0
  77. package/dist/parsers/flowchart.d.ts +2 -0
  78. package/dist/parsers/flowchart.js +191 -0
  79. package/dist/parsers/helpers.d.ts +4 -0
  80. package/dist/parsers/helpers.js +8 -0
  81. package/dist/parsers/index.d.ts +7 -0
  82. package/dist/parsers/index.js +19 -0
  83. package/dist/parsers/mindmap.d.ts +2 -0
  84. package/dist/parsers/mindmap.js +124 -0
  85. package/dist/parsers/sequence.d.ts +18 -0
  86. package/dist/parsers/sequence.js +196 -0
  87. package/dist/parsers/state.d.ts +2 -0
  88. package/dist/parsers/state.js +68 -0
  89. package/dist/react.d.ts +7 -0
  90. package/dist/react.js +9 -0
  91. package/dist/reactDefaults.d.ts +5 -0
  92. package/dist/reactDefaults.js +37 -0
  93. package/dist/renderMarkdown.d.ts +9 -0
  94. package/dist/renderMarkdown.js +103 -0
  95. package/dist/swagger.d.ts +113 -0
  96. package/dist/swagger.js +551 -0
  97. package/dist/theme/dark.d.ts +8 -0
  98. package/dist/theme/dark.js +190 -0
  99. package/dist/theme/index.d.ts +18 -0
  100. package/dist/theme/index.js +29 -0
  101. package/dist/theme/light.d.ts +8 -0
  102. package/dist/theme/light.js +190 -0
  103. package/dist/theme/types.d.ts +97 -0
  104. package/dist/theme/types.js +7 -0
  105. package/dist/types.d.ts +235 -0
  106. package/dist/types.js +1 -0
  107. 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,5 @@
1
+ interface Props {
2
+ data: Record<string, unknown>;
3
+ }
4
+ export declare const ClassNode: import("react").NamedExoticComponent<Props>;
5
+ export {};
@@ -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,5 @@
1
+ interface Props {
2
+ data: Record<string, unknown>;
3
+ }
4
+ export declare const EntityNode: import("react").NamedExoticComponent<Props>;
5
+ export {};
@@ -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,5 @@
1
+ interface Props {
2
+ data: Record<string, unknown>;
3
+ }
4
+ export declare const FlowNode: import("react").NamedExoticComponent<Props>;
5
+ export {};
@@ -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 {};