@particle-academy/fancy-echarts 2.0.3 → 3.0.2

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/dist/index.cjs CHANGED
@@ -7,7 +7,300 @@ var charts = require('echarts/charts');
7
7
  var components = require('echarts/components');
8
8
  var renderers = require('echarts/renderers');
9
9
 
10
- // src/components/EChart.tsx
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __esm = (fn, res) => function __init() {
13
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
14
+ };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+
20
+ // src/components/Diagram/diagram.serializers.ts
21
+ var diagram_serializers_exports = {};
22
+ __export(diagram_serializers_exports, {
23
+ deserializeSchema: () => deserializeSchema,
24
+ serializeToDFD: () => serializeToDFD,
25
+ serializeToERD: () => serializeToERD,
26
+ serializeToUML: () => serializeToUML
27
+ });
28
+ function serializeToERD(schema) {
29
+ const lines = [];
30
+ for (const entity of schema.entities) {
31
+ lines.push(`[${entity.name}]`);
32
+ if (entity.fields) {
33
+ for (const field of entity.fields) {
34
+ const parts = [` ${field.name}`];
35
+ if (field.type) parts.push(field.type);
36
+ if (field.primary) parts.push("PK");
37
+ if (field.foreign) parts.push("FK");
38
+ if (field.nullable) parts.push("?");
39
+ lines.push(parts.join(" "));
40
+ }
41
+ }
42
+ lines.push("");
43
+ }
44
+ for (const rel of schema.relations) {
45
+ const fromEntity = schema.entities.find((e) => e.id === rel.from);
46
+ const toEntity = schema.entities.find((e) => e.id === rel.to);
47
+ if (!fromEntity || !toEntity) continue;
48
+ const marker = getERDMarker(rel.type);
49
+ const parts = [fromEntity.name, marker, toEntity.name];
50
+ if (rel.label) parts.push(`: ${rel.label}`);
51
+ lines.push(parts.join(" "));
52
+ }
53
+ return lines.join("\n").trim();
54
+ }
55
+ function getERDMarker(type) {
56
+ switch (type) {
57
+ case "one-to-one":
58
+ return "1--1";
59
+ case "one-to-many":
60
+ return "1--*";
61
+ case "many-to-many":
62
+ return "*--*";
63
+ default:
64
+ return "--";
65
+ }
66
+ }
67
+ function serializeToUML(schema) {
68
+ const lines = ["@startuml"];
69
+ for (const entity of schema.entities) {
70
+ lines.push(`class ${entity.name} {`);
71
+ if (entity.fields) {
72
+ for (const field of entity.fields) {
73
+ const typeStr = field.type ?? "any";
74
+ const nullable = field.nullable ? "?" : "";
75
+ const stereotype = field.primary ? " <<PK>>" : field.foreign ? " <<FK>>" : "";
76
+ lines.push(` ${field.name} : ${typeStr}${nullable}${stereotype}`);
77
+ }
78
+ }
79
+ lines.push("}");
80
+ lines.push("");
81
+ }
82
+ for (const rel of schema.relations) {
83
+ const fromEntity = schema.entities.find((e) => e.id === rel.from);
84
+ const toEntity = schema.entities.find((e) => e.id === rel.to);
85
+ if (!fromEntity || !toEntity) continue;
86
+ const arrow = getUMLArrow(rel.type);
87
+ const label = rel.label ? ` : ${rel.label}` : "";
88
+ lines.push(`${fromEntity.name} ${arrow} ${toEntity.name}${label}`);
89
+ }
90
+ lines.push("@enduml");
91
+ return lines.join("\n");
92
+ }
93
+ function getUMLArrow(type) {
94
+ switch (type) {
95
+ case "one-to-one":
96
+ return '"1" -- "1"';
97
+ case "one-to-many":
98
+ return '"1" -- "*"';
99
+ case "many-to-many":
100
+ return '"*" -- "*"';
101
+ default:
102
+ return "--";
103
+ }
104
+ }
105
+ function serializeToDFD(schema) {
106
+ const lines = [];
107
+ for (const entity of schema.entities) {
108
+ lines.push(`entity ${entity.name}`);
109
+ }
110
+ lines.push("");
111
+ for (const rel of schema.relations) {
112
+ const fromEntity = schema.entities.find((e) => e.id === rel.from);
113
+ const toEntity = schema.entities.find((e) => e.id === rel.to);
114
+ if (!fromEntity || !toEntity) continue;
115
+ const label = rel.label ? ` "${rel.label}"` : "";
116
+ lines.push(`${fromEntity.name} -> ${toEntity.name}${label}`);
117
+ }
118
+ return lines.join("\n").trim();
119
+ }
120
+ function deserializeSchema(input, format) {
121
+ switch (format) {
122
+ case "erd":
123
+ return deserializeERD(input);
124
+ case "uml":
125
+ return deserializeUML(input);
126
+ case "dfd":
127
+ return deserializeDFD(input);
128
+ }
129
+ }
130
+ function deserializeERD(input) {
131
+ const entities = [];
132
+ const relations = [];
133
+ const lines = input.split("\n");
134
+ let currentEntity = null;
135
+ for (const rawLine of lines) {
136
+ const line = rawLine.trim();
137
+ const entityMatch = line.match(/^\[(.+)\]$/);
138
+ if (entityMatch) {
139
+ currentEntity = {
140
+ id: entityMatch[1].toLowerCase().replace(/\s+/g, "_"),
141
+ name: entityMatch[1],
142
+ fields: []
143
+ };
144
+ entities.push(currentEntity);
145
+ continue;
146
+ }
147
+ if (currentEntity && rawLine.startsWith(" ") && line.length > 0) {
148
+ const parts = line.split(/\s+/);
149
+ const field = { name: parts[0] };
150
+ if (parts.length > 1 && !["PK", "FK", "?"].includes(parts[1])) {
151
+ field.type = parts[1];
152
+ }
153
+ if (parts.includes("PK")) field.primary = true;
154
+ if (parts.includes("FK")) field.foreign = true;
155
+ if (parts.includes("?")) field.nullable = true;
156
+ currentEntity.fields.push(field);
157
+ continue;
158
+ }
159
+ const relMatch = line.match(
160
+ /^(\S+)\s+(1--1|1--\*|\*--\*)\s+(\S+)(?:\s*:\s*(.+))?$/
161
+ );
162
+ if (relMatch) {
163
+ currentEntity = null;
164
+ const fromName = relMatch[1];
165
+ const marker = relMatch[2];
166
+ const toName = relMatch[3];
167
+ const label = relMatch[4];
168
+ const fromEntity = entities.find((e) => e.name === fromName);
169
+ const toEntity = entities.find((e) => e.name === toName);
170
+ if (fromEntity && toEntity) {
171
+ relations.push({
172
+ id: `${fromEntity.id ?? fromEntity.name}_${toEntity.id ?? toEntity.name}`,
173
+ from: fromEntity.id ?? fromEntity.name,
174
+ to: toEntity.id ?? toEntity.name,
175
+ type: parseERDMarker(marker),
176
+ label
177
+ });
178
+ }
179
+ continue;
180
+ }
181
+ if (line === "") {
182
+ currentEntity = null;
183
+ }
184
+ }
185
+ return { entities, relations };
186
+ }
187
+ function parseERDMarker(marker) {
188
+ switch (marker) {
189
+ case "1--1":
190
+ return "one-to-one";
191
+ case "1--*":
192
+ return "one-to-many";
193
+ case "*--*":
194
+ return "many-to-many";
195
+ default:
196
+ return "one-to-many";
197
+ }
198
+ }
199
+ function deserializeUML(input) {
200
+ const entities = [];
201
+ const relations = [];
202
+ const lines = input.split("\n");
203
+ let currentEntity = null;
204
+ for (const rawLine of lines) {
205
+ const line = rawLine.trim();
206
+ if (line === "@startuml" || line === "@enduml" || line === "") continue;
207
+ const classMatch = line.match(/^class\s+(\S+)\s*\{$/);
208
+ if (classMatch) {
209
+ currentEntity = {
210
+ id: classMatch[1].toLowerCase().replace(/\s+/g, "_"),
211
+ name: classMatch[1],
212
+ fields: []
213
+ };
214
+ entities.push(currentEntity);
215
+ continue;
216
+ }
217
+ if (line === "}") {
218
+ currentEntity = null;
219
+ continue;
220
+ }
221
+ if (currentEntity) {
222
+ const fieldMatch = line.match(
223
+ /^(\S+)\s*:\s*(\S+?)(\?)?(?:\s*<<(PK|FK)>>)?$/
224
+ );
225
+ if (fieldMatch) {
226
+ const field = {
227
+ name: fieldMatch[1],
228
+ type: fieldMatch[2]
229
+ };
230
+ if (fieldMatch[3]) field.nullable = true;
231
+ if (fieldMatch[4] === "PK") field.primary = true;
232
+ if (fieldMatch[4] === "FK") field.foreign = true;
233
+ currentEntity.fields.push(field);
234
+ }
235
+ continue;
236
+ }
237
+ const relMatch = line.match(
238
+ /^(\S+)\s+"([1*])"\s+--\s+"([1*])"\s+(\S+)(?:\s*:\s*(.+))?$/
239
+ );
240
+ if (relMatch) {
241
+ const fromName = relMatch[1];
242
+ const fromCard = relMatch[2];
243
+ const toCard = relMatch[3];
244
+ const toName = relMatch[4];
245
+ const label = relMatch[5];
246
+ const fromEntity = entities.find((e) => e.name === fromName);
247
+ const toEntity = entities.find((e) => e.name === toName);
248
+ if (fromEntity && toEntity) {
249
+ const type = fromCard === "1" && toCard === "1" ? "one-to-one" : fromCard === "1" && toCard === "*" ? "one-to-many" : "many-to-many";
250
+ relations.push({
251
+ id: `${fromEntity.id ?? fromEntity.name}_${toEntity.id ?? toEntity.name}`,
252
+ from: fromEntity.id ?? fromEntity.name,
253
+ to: toEntity.id ?? toEntity.name,
254
+ type,
255
+ label
256
+ });
257
+ }
258
+ }
259
+ }
260
+ return { entities, relations };
261
+ }
262
+ function deserializeDFD(input) {
263
+ const entities = [];
264
+ const relations = [];
265
+ const lines = input.split("\n");
266
+ for (const rawLine of lines) {
267
+ const line = rawLine.trim();
268
+ if (line === "") continue;
269
+ const entityMatch = line.match(/^entity\s+(\S+)$/);
270
+ if (entityMatch) {
271
+ entities.push({
272
+ id: entityMatch[1].toLowerCase().replace(/\s+/g, "_"),
273
+ name: entityMatch[1],
274
+ fields: []
275
+ });
276
+ continue;
277
+ }
278
+ const flowMatch = line.match(
279
+ /^(\S+)\s+->\s+(\S+)(?:\s+"(.+)")?$/
280
+ );
281
+ if (flowMatch) {
282
+ const fromName = flowMatch[1];
283
+ const toName = flowMatch[2];
284
+ const label = flowMatch[3];
285
+ const fromEntity = entities.find((e) => e.name === fromName);
286
+ const toEntity = entities.find((e) => e.name === toName);
287
+ if (fromEntity && toEntity) {
288
+ relations.push({
289
+ id: `${fromEntity.id ?? fromEntity.name}_${toEntity.id ?? toEntity.name}`,
290
+ from: fromEntity.id ?? fromEntity.name,
291
+ to: toEntity.id ?? toEntity.name,
292
+ type: "one-to-many",
293
+ label
294
+ });
295
+ }
296
+ }
297
+ }
298
+ return { entities, relations };
299
+ }
300
+ var init_diagram_serializers = __esm({
301
+ "src/components/Diagram/diagram.serializers.ts"() {
302
+ }
303
+ });
11
304
  function useResizeObserver(ref, callback) {
12
305
  const callbackRef = react.useRef(callback);
13
306
  callbackRef.current = callback;
@@ -600,6 +893,2045 @@ function registerBuiltinThemes() {
600
893
  registerTheme("pastel", pastelTheme);
601
894
  }
602
895
 
896
+ // src/utils/cn.ts
897
+ function flatten(value) {
898
+ if (!value) return "";
899
+ if (typeof value === "string" || typeof value === "number") return String(value);
900
+ if (Array.isArray(value)) return value.map(flatten).filter(Boolean).join(" ");
901
+ if (typeof value === "object") {
902
+ return Object.entries(value).filter(([, v]) => Boolean(v)).map(([k]) => k).join(" ");
903
+ }
904
+ return "";
905
+ }
906
+ function cn(...inputs) {
907
+ return inputs.map(flatten).filter(Boolean).join(" ");
908
+ }
909
+ function useControllableState(controlledValue, defaultValue, onChange) {
910
+ const [uncontrolledValue, setUncontrolledValue] = react.useState(defaultValue);
911
+ const isControlled = controlledValue !== void 0;
912
+ const value = isControlled ? controlledValue : uncontrolledValue;
913
+ const onChangeRef = react.useRef(onChange);
914
+ onChangeRef.current = onChange;
915
+ const setValue = react.useCallback(
916
+ (next) => {
917
+ const nextValue = typeof next === "function" ? next(value) : next;
918
+ if (!isControlled) {
919
+ setUncontrolledValue(nextValue);
920
+ }
921
+ onChangeRef.current?.(nextValue);
922
+ },
923
+ [isControlled, value]
924
+ );
925
+ return [value, setValue];
926
+ }
927
+ function usePanZoom({
928
+ viewport,
929
+ setViewport,
930
+ minZoom,
931
+ maxZoom,
932
+ pannable,
933
+ zoomable,
934
+ containerRef
935
+ }) {
936
+ const isPanningRef = react.useRef(false);
937
+ const startRef = react.useRef({ x: 0, y: 0, panX: 0, panY: 0 });
938
+ const viewportRef = react.useRef(viewport);
939
+ viewportRef.current = viewport;
940
+ const setViewportRef = react.useRef(setViewport);
941
+ setViewportRef.current = setViewport;
942
+ const zoomableRef = react.useRef(zoomable);
943
+ zoomableRef.current = zoomable;
944
+ const minZoomRef = react.useRef(minZoom);
945
+ minZoomRef.current = minZoom;
946
+ const maxZoomRef = react.useRef(maxZoom);
947
+ maxZoomRef.current = maxZoom;
948
+ react.useEffect(() => {
949
+ const container = containerRef.current;
950
+ if (!container) return;
951
+ function handleWheel(e) {
952
+ if (!zoomableRef.current) return;
953
+ if (!e.ctrlKey && !e.metaKey) return;
954
+ e.preventDefault();
955
+ const rect = container.getBoundingClientRect();
956
+ const mouseX = e.clientX - rect.left;
957
+ const mouseY = e.clientY - rect.top;
958
+ setViewportRef.current((prev) => {
959
+ const factor = 1 - e.deltaY * 1e-3;
960
+ const newZoom = Math.min(maxZoomRef.current, Math.max(minZoomRef.current, prev.zoom * factor));
961
+ const ratio = newZoom / prev.zoom;
962
+ return {
963
+ zoom: newZoom,
964
+ panX: mouseX - (mouseX - prev.panX) * ratio,
965
+ panY: mouseY - (mouseY - prev.panY) * ratio
966
+ };
967
+ });
968
+ }
969
+ container.addEventListener("wheel", handleWheel, { passive: false });
970
+ return () => {
971
+ container.removeEventListener("wheel", handleWheel);
972
+ };
973
+ }, [containerRef]);
974
+ const onPointerDown = react.useCallback(
975
+ (e) => {
976
+ if (!pannable) return;
977
+ if (e.button !== 0) return;
978
+ const target = e.target;
979
+ if (target !== containerRef.current && !target.hasAttribute("data-canvas-bg")) return;
980
+ isPanningRef.current = true;
981
+ startRef.current = { x: e.clientX, y: e.clientY, panX: viewport.panX, panY: viewport.panY };
982
+ e.target.setPointerCapture(e.pointerId);
983
+ e.preventDefault();
984
+ },
985
+ [pannable, viewport.panX, viewport.panY, containerRef]
986
+ );
987
+ const onPointerMove = react.useCallback(
988
+ (e) => {
989
+ if (!isPanningRef.current) return;
990
+ const dx = e.clientX - startRef.current.x;
991
+ const dy = e.clientY - startRef.current.y;
992
+ setViewport((prev) => ({
993
+ ...prev,
994
+ panX: startRef.current.panX + dx,
995
+ panY: startRef.current.panY + dy
996
+ }));
997
+ },
998
+ [setViewport]
999
+ );
1000
+ const onPointerUp = react.useCallback(() => {
1001
+ isPanningRef.current = false;
1002
+ }, []);
1003
+ return {
1004
+ containerProps: { onPointerDown, onPointerMove, onPointerUp },
1005
+ isPanning: isPanningRef.current
1006
+ };
1007
+ }
1008
+ function useNodeRegistry() {
1009
+ const rectsRef = react.useRef(/* @__PURE__ */ new Map());
1010
+ const [version, setVersion] = react.useState(0);
1011
+ const registerNode = react.useCallback((id, rect) => {
1012
+ rectsRef.current.set(id, rect);
1013
+ setVersion((v) => v + 1);
1014
+ }, []);
1015
+ const unregisterNode = react.useCallback((id) => {
1016
+ rectsRef.current.delete(id);
1017
+ setVersion((v) => v + 1);
1018
+ }, []);
1019
+ return { registerNode, unregisterNode, nodeRects: rectsRef.current, version };
1020
+ }
1021
+ var CanvasContext = react.createContext(null);
1022
+ function useCanvas() {
1023
+ const ctx = react.useContext(CanvasContext);
1024
+ if (!ctx) throw new Error("useCanvas must be used within a Canvas component");
1025
+ return ctx;
1026
+ }
1027
+ function CanvasNode({ children, id, x, y, draggable, onPositionChange, className, style }) {
1028
+ const { registerNode, unregisterNode, viewport, gridSize, snapToGrid } = useCanvas();
1029
+ const nodeRef = react.useRef(null);
1030
+ const isDragging = react.useRef(false);
1031
+ const dragStart = react.useRef({ mouseX: 0, mouseY: 0, nodeX: 0, nodeY: 0 });
1032
+ react.useEffect(() => {
1033
+ const el = nodeRef.current;
1034
+ if (!el) return;
1035
+ const updateRect = () => {
1036
+ registerNode(id, { x, y, width: el.offsetWidth, height: el.offsetHeight });
1037
+ };
1038
+ updateRect();
1039
+ const observer = new ResizeObserver(updateRect);
1040
+ observer.observe(el);
1041
+ return () => {
1042
+ observer.disconnect();
1043
+ unregisterNode(id);
1044
+ };
1045
+ }, [id, x, y, registerNode, unregisterNode]);
1046
+ const handlePointerDown = react.useCallback(
1047
+ (e) => {
1048
+ if (!draggable || e.button !== 0) return;
1049
+ e.stopPropagation();
1050
+ isDragging.current = true;
1051
+ dragStart.current = { mouseX: e.clientX, mouseY: e.clientY, nodeX: x, nodeY: y };
1052
+ e.target.setPointerCapture(e.pointerId);
1053
+ },
1054
+ [draggable, x, y]
1055
+ );
1056
+ const handlePointerMove = react.useCallback(
1057
+ (e) => {
1058
+ if (!isDragging.current) return;
1059
+ const dx = (e.clientX - dragStart.current.mouseX) / viewport.zoom;
1060
+ const dy = (e.clientY - dragStart.current.mouseY) / viewport.zoom;
1061
+ let nx = dragStart.current.nodeX + dx;
1062
+ let ny = dragStart.current.nodeY + dy;
1063
+ if (snapToGrid && gridSize > 0) {
1064
+ nx = Math.round(nx / gridSize) * gridSize;
1065
+ ny = Math.round(ny / gridSize) * gridSize;
1066
+ }
1067
+ onPositionChange?.(nx, ny);
1068
+ },
1069
+ [viewport.zoom, onPositionChange, snapToGrid, gridSize]
1070
+ );
1071
+ const handlePointerUp = react.useCallback(() => {
1072
+ isDragging.current = false;
1073
+ }, []);
1074
+ return /* @__PURE__ */ jsxRuntime.jsx(
1075
+ "div",
1076
+ {
1077
+ ref: nodeRef,
1078
+ "data-react-fancy-canvas-node": "",
1079
+ "data-node-id": id,
1080
+ className: cn("absolute", draggable && "cursor-grab active:cursor-grabbing", className),
1081
+ style: { left: x, top: y, ...style },
1082
+ onPointerDown: handlePointerDown,
1083
+ onPointerMove: handlePointerMove,
1084
+ onPointerUp: handlePointerUp,
1085
+ children
1086
+ }
1087
+ );
1088
+ }
1089
+ CanvasNode.displayName = "CanvasNode";
1090
+
1091
+ // src/components/Diagram/_canvas/canvas.utils.ts
1092
+ function getAnchorPoint(rect, anchor, otherRect) {
1093
+ const cx = rect.x + rect.width / 2;
1094
+ const cy = rect.y + rect.height / 2;
1095
+ if (anchor === "auto" && otherRect) {
1096
+ const ocx = otherRect.x + otherRect.width / 2;
1097
+ const ocy = otherRect.y + otherRect.height / 2;
1098
+ const dx = ocx - cx;
1099
+ const dy = ocy - cy;
1100
+ if (Math.abs(dx) > Math.abs(dy)) {
1101
+ return dx > 0 ? { x: rect.x + rect.width, y: cy } : { x: rect.x, y: cy };
1102
+ }
1103
+ return dy > 0 ? { x: cx, y: rect.y + rect.height } : { x: cx, y: rect.y };
1104
+ }
1105
+ switch (anchor) {
1106
+ case "top":
1107
+ return { x: cx, y: rect.y };
1108
+ case "bottom":
1109
+ return { x: cx, y: rect.y + rect.height };
1110
+ case "left":
1111
+ return { x: rect.x, y: cy };
1112
+ case "right":
1113
+ return { x: rect.x + rect.width, y: cy };
1114
+ case "center":
1115
+ return { x: cx, y: cy };
1116
+ default:
1117
+ return { x: cx, y: cy };
1118
+ }
1119
+ }
1120
+ function bezierPath(from, to) {
1121
+ const dx = Math.abs(to.x - from.x);
1122
+ const dy = Math.abs(to.y - from.y);
1123
+ if (dx > dy) {
1124
+ const offset2 = dx * 0.5;
1125
+ const cp1x = from.x + (to.x > from.x ? offset2 : -offset2);
1126
+ const cp2x = to.x + (to.x > from.x ? -offset2 : offset2);
1127
+ return `M${from.x},${from.y} C${cp1x},${from.y} ${cp2x},${to.y} ${to.x},${to.y}`;
1128
+ }
1129
+ const offset = Math.max(dy * 0.5, 30);
1130
+ const cp1y = from.y + (to.y > from.y ? offset : -offset);
1131
+ const cp2y = to.y + (to.y > from.y ? -offset : offset);
1132
+ return `M${from.x},${from.y} C${from.x},${cp1y} ${to.x},${cp2y} ${to.x},${to.y}`;
1133
+ }
1134
+ function stepPath(from, to) {
1135
+ const midX = (from.x + to.x) / 2;
1136
+ return `M${from.x},${from.y} H${midX} V${to.y} H${to.x}`;
1137
+ }
1138
+ function straightPath(from, to) {
1139
+ return `M${from.x},${from.y} L${to.x},${to.y}`;
1140
+ }
1141
+ function getEdgePath(from, to, curve = "bezier") {
1142
+ switch (curve) {
1143
+ case "bezier":
1144
+ return bezierPath(from, to);
1145
+ case "step":
1146
+ return stepPath(from, to);
1147
+ case "straight":
1148
+ return straightPath(from, to);
1149
+ }
1150
+ }
1151
+ function CanvasEdge({
1152
+ from,
1153
+ to,
1154
+ fromAnchor = "auto",
1155
+ toAnchor = "auto",
1156
+ curve = "bezier",
1157
+ color = "currentColor",
1158
+ strokeWidth = 2,
1159
+ dashed = false,
1160
+ animated = false,
1161
+ label,
1162
+ className,
1163
+ markerStart,
1164
+ markerEnd
1165
+ }) {
1166
+ const { nodeRects, registryVersion } = useCanvas();
1167
+ const path = react.useMemo(() => {
1168
+ const fromRect = nodeRects.get(from);
1169
+ const toRect = nodeRects.get(to);
1170
+ if (!fromRect || !toRect) return null;
1171
+ const fromPt = getAnchorPoint(fromRect, fromAnchor, toRect);
1172
+ const toPt = getAnchorPoint(toRect, toAnchor, fromRect);
1173
+ return {
1174
+ d: getEdgePath(fromPt, toPt, curve),
1175
+ midX: (fromPt.x + toPt.x) / 2,
1176
+ midY: (fromPt.y + toPt.y) / 2
1177
+ };
1178
+ }, [from, to, fromAnchor, toAnchor, curve, nodeRects, registryVersion]);
1179
+ if (!path) return null;
1180
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { "data-react-fancy-canvas-edge": "", className: cn("text-zinc-300 dark:text-zinc-600", className), children: [
1181
+ /* @__PURE__ */ jsxRuntime.jsx(
1182
+ "path",
1183
+ {
1184
+ d: path.d,
1185
+ fill: "none",
1186
+ stroke: color,
1187
+ strokeWidth,
1188
+ strokeDasharray: dashed ? "6 4" : void 0,
1189
+ markerStart: markerStart ? `url(#${markerStart})` : void 0,
1190
+ markerEnd: markerEnd ? `url(#${markerEnd})` : void 0,
1191
+ className: animated ? "animate-[dash_1s_linear_infinite]" : "",
1192
+ style: animated ? { strokeDasharray: "8 4" } : void 0
1193
+ }
1194
+ ),
1195
+ label && /* @__PURE__ */ jsxRuntime.jsx("foreignObject", { x: path.midX - 40, y: path.midY - 12, width: 80, height: 24, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center text-xs text-zinc-500", children: label }) })
1196
+ ] });
1197
+ }
1198
+ CanvasEdge.displayName = "CanvasEdge";
1199
+ function CanvasMinimap({ width = 150, height = 100, className }) {
1200
+ const { nodeRects, registryVersion, viewport } = useCanvas();
1201
+ const bounds = react.useMemo(() => {
1202
+ if (nodeRects.size === 0) return { minX: 0, minY: 0, maxX: 500, maxY: 300 };
1203
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1204
+ nodeRects.forEach((r) => {
1205
+ minX = Math.min(minX, r.x);
1206
+ minY = Math.min(minY, r.y);
1207
+ maxX = Math.max(maxX, r.x + r.width);
1208
+ maxY = Math.max(maxY, r.y + r.height);
1209
+ });
1210
+ const padding = 50;
1211
+ return { minX: minX - padding, minY: minY - padding, maxX: maxX + padding, maxY: maxY + padding };
1212
+ }, [nodeRects, registryVersion]);
1213
+ const scaleX = width / (bounds.maxX - bounds.minX || 1);
1214
+ const scaleY = height / (bounds.maxY - bounds.minY || 1);
1215
+ const scale = Math.min(scaleX, scaleY);
1216
+ return /* @__PURE__ */ jsxRuntime.jsx(
1217
+ "div",
1218
+ {
1219
+ "data-react-fancy-canvas-minimap": "",
1220
+ className: cn(
1221
+ "absolute right-3 bottom-3 overflow-hidden rounded-lg border border-zinc-200 bg-white/90 dark:border-zinc-700 dark:bg-zinc-900/90",
1222
+ className
1223
+ ),
1224
+ style: { width, height },
1225
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width, height, children: [
1226
+ Array.from(nodeRects.entries()).map(([id, rect]) => /* @__PURE__ */ jsxRuntime.jsx(
1227
+ "rect",
1228
+ {
1229
+ x: (rect.x - bounds.minX) * scale,
1230
+ y: (rect.y - bounds.minY) * scale,
1231
+ width: Math.max(rect.width * scale, 4),
1232
+ height: Math.max(rect.height * scale, 3),
1233
+ rx: 1,
1234
+ className: "fill-blue-400/60"
1235
+ },
1236
+ id
1237
+ )),
1238
+ /* @__PURE__ */ jsxRuntime.jsx(
1239
+ "rect",
1240
+ {
1241
+ x: (-viewport.panX / viewport.zoom - bounds.minX) * scale,
1242
+ y: (-viewport.panY / viewport.zoom - bounds.minY) * scale,
1243
+ width: (width / viewport.zoom / scale > 0 ? width / viewport.zoom : width) * scale / (bounds.maxX - bounds.minX || 1) * (bounds.maxX - bounds.minX),
1244
+ height: (height / viewport.zoom / scale > 0 ? height / viewport.zoom : height) * scale / (bounds.maxY - bounds.minY || 1) * (bounds.maxY - bounds.minY),
1245
+ fill: "none",
1246
+ stroke: "currentColor",
1247
+ strokeWidth: 1,
1248
+ className: "text-blue-500"
1249
+ }
1250
+ )
1251
+ ] })
1252
+ }
1253
+ );
1254
+ }
1255
+ CanvasMinimap.displayName = "CanvasMinimap";
1256
+ var iconProps = { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" };
1257
+ var ZoomIn = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...iconProps, children: [
1258
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "8" }),
1259
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" }),
1260
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "11", y1: "8", x2: "11", y2: "14" }),
1261
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "8", y1: "11", x2: "14", y2: "11" })
1262
+ ] });
1263
+ var ZoomOut = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...iconProps, children: [
1264
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "8" }),
1265
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" }),
1266
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "8", y1: "11", x2: "14", y2: "11" })
1267
+ ] });
1268
+ var Maximize = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...iconProps, children: [
1269
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }),
1270
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }),
1271
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }),
1272
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" })
1273
+ ] });
1274
+ var RotateCcw = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...iconProps, children: [
1275
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
1276
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 3v5h5" })
1277
+ ] });
1278
+ function CanvasControls({
1279
+ className,
1280
+ showZoomIn = true,
1281
+ showZoomOut = true,
1282
+ showReset = true,
1283
+ showFitAll = true
1284
+ }) {
1285
+ const { setViewport, nodeRects, containerRef } = useCanvas();
1286
+ const zoomIn = () => setViewport((v) => ({ ...v, zoom: Math.min(3, v.zoom * 1.25) }));
1287
+ const zoomOut = () => setViewport((v) => ({ ...v, zoom: Math.max(0.1, v.zoom / 1.25) }));
1288
+ const reset = () => setViewport({ panX: 0, panY: 0, zoom: 1 });
1289
+ const fitAll = () => {
1290
+ const container = containerRef.current;
1291
+ if (!container || nodeRects.size === 0) return reset();
1292
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1293
+ nodeRects.forEach((r) => {
1294
+ minX = Math.min(minX, r.x);
1295
+ minY = Math.min(minY, r.y);
1296
+ maxX = Math.max(maxX, r.x + r.width);
1297
+ maxY = Math.max(maxY, r.y + r.height);
1298
+ });
1299
+ const padding = 40;
1300
+ const contentW = maxX - minX + padding * 2;
1301
+ const contentH = maxY - minY + padding * 2;
1302
+ const cw = container.clientWidth;
1303
+ const ch = container.clientHeight;
1304
+ const zoom = Math.min(cw / contentW, ch / contentH, 1.5);
1305
+ const panX = (cw - contentW * zoom) / 2 - minX * zoom + padding * zoom;
1306
+ const panY = (ch - contentH * zoom) / 2 - minY * zoom + padding * zoom;
1307
+ setViewport({ panX, panY, zoom });
1308
+ };
1309
+ const btnClass = "flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 hover:bg-zinc-100 hover:text-zinc-700 dark:hover:bg-zinc-800 dark:hover:text-zinc-300 transition-colors";
1310
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1311
+ "div",
1312
+ {
1313
+ "data-react-fancy-canvas-controls": "",
1314
+ className: cn(
1315
+ "absolute bottom-3 left-3 flex gap-1 rounded-lg border border-zinc-200 bg-white/90 p-1 shadow-sm dark:border-zinc-700 dark:bg-zinc-900/90",
1316
+ className
1317
+ ),
1318
+ children: [
1319
+ showZoomIn && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: zoomIn, className: btnClass, "aria-label": "Zoom in", children: /* @__PURE__ */ jsxRuntime.jsx(ZoomIn, {}) }),
1320
+ showZoomOut && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: zoomOut, className: btnClass, "aria-label": "Zoom out", children: /* @__PURE__ */ jsxRuntime.jsx(ZoomOut, {}) }),
1321
+ showReset && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: reset, className: btnClass, "aria-label": "Reset view", children: /* @__PURE__ */ jsxRuntime.jsx(RotateCcw, {}) }),
1322
+ showFitAll && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: fitAll, className: btnClass, "aria-label": "Fit all", children: /* @__PURE__ */ jsxRuntime.jsx(Maximize, {}) })
1323
+ ]
1324
+ }
1325
+ );
1326
+ }
1327
+ CanvasControls.displayName = "CanvasControls";
1328
+ var DEFAULT_VIEWPORT = { panX: 0, panY: 0, zoom: 1 };
1329
+ function CanvasRoot({
1330
+ children,
1331
+ viewport: controlledViewport,
1332
+ defaultViewport = DEFAULT_VIEWPORT,
1333
+ onViewportChange,
1334
+ minZoom = 0.1,
1335
+ maxZoom = 3,
1336
+ pannable = true,
1337
+ zoomable = true,
1338
+ showGrid = false,
1339
+ gridStyle = "dots",
1340
+ gridSize = 20,
1341
+ gridColor = "rgb(161 161 170 / 0.3)",
1342
+ snapToGrid = false,
1343
+ fitOnMount = false,
1344
+ className,
1345
+ style
1346
+ }) {
1347
+ const containerRef = react.useRef(null);
1348
+ const [viewport, setViewport] = useControllableState(controlledViewport, defaultViewport, onViewportChange);
1349
+ const { registerNode, unregisterNode, nodeRects, version: registryVersion } = useNodeRegistry();
1350
+ const { containerProps } = usePanZoom({
1351
+ viewport,
1352
+ setViewport,
1353
+ minZoom,
1354
+ maxZoom,
1355
+ pannable,
1356
+ zoomable,
1357
+ containerRef
1358
+ });
1359
+ const ctx = react.useMemo(
1360
+ () => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef, gridSize, snapToGrid }),
1361
+ [viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, gridSize, snapToGrid]
1362
+ );
1363
+ const hasFitted = react.useRef(false);
1364
+ react.useEffect(() => {
1365
+ if (!fitOnMount || hasFitted.current || nodeRects.size === 0) return;
1366
+ const container = containerRef.current;
1367
+ if (!container || container.clientWidth === 0) return;
1368
+ hasFitted.current = true;
1369
+ requestAnimationFrame(() => {
1370
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1371
+ nodeRects.forEach((r) => {
1372
+ minX = Math.min(minX, r.x);
1373
+ minY = Math.min(minY, r.y);
1374
+ maxX = Math.max(maxX, r.x + r.width);
1375
+ maxY = Math.max(maxY, r.y + r.height);
1376
+ });
1377
+ const padding = 40;
1378
+ const contentW = maxX - minX + padding * 2;
1379
+ const contentH = maxY - minY + padding * 2;
1380
+ const cw = container.clientWidth;
1381
+ const ch = container.clientHeight;
1382
+ const zoom = Math.min(cw / contentW, ch / contentH, 1.5);
1383
+ const panX = (cw - contentW * zoom) / 2 - minX * zoom + padding * zoom;
1384
+ const panY = (ch - contentH * zoom) / 2 - minY * zoom + padding * zoom;
1385
+ setViewport({ panX, panY, zoom });
1386
+ });
1387
+ }, [fitOnMount, nodeRects, registryVersion, setViewport]);
1388
+ const edges = [];
1389
+ const others = [];
1390
+ const overlays = [];
1391
+ react.Children.forEach(children, (child) => {
1392
+ const el = child;
1393
+ if (!el || !el.type) return;
1394
+ const elType = el.type;
1395
+ if (elType === CanvasEdge || elType?._isCanvasEdge) {
1396
+ edges.push(el);
1397
+ } else if (elType === CanvasMinimap || elType === CanvasControls) {
1398
+ overlays.push(el);
1399
+ } else {
1400
+ others.push(el);
1401
+ }
1402
+ });
1403
+ return /* @__PURE__ */ jsxRuntime.jsx(CanvasContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsxs(
1404
+ "div",
1405
+ {
1406
+ ref: containerRef,
1407
+ "data-react-fancy-canvas": "",
1408
+ className: cn("relative overflow-hidden", className),
1409
+ style: { touchAction: "none", ...style },
1410
+ ...containerProps,
1411
+ children: [
1412
+ /* @__PURE__ */ jsxRuntime.jsx(
1413
+ "div",
1414
+ {
1415
+ "data-canvas-bg": "",
1416
+ className: "absolute inset-0",
1417
+ style: showGrid && gridStyle !== "none" ? gridStyle === "lines" ? {
1418
+ backgroundImage: `linear-gradient(to right, ${gridColor} 1px, transparent 1px), linear-gradient(to bottom, ${gridColor} 1px, transparent 1px)`,
1419
+ backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
1420
+ backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
1421
+ } : {
1422
+ backgroundImage: `radial-gradient(circle, ${gridColor} 1px, transparent 1px)`,
1423
+ backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
1424
+ backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
1425
+ } : void 0
1426
+ }
1427
+ ),
1428
+ /* @__PURE__ */ jsxRuntime.jsx(
1429
+ "div",
1430
+ {
1431
+ className: "absolute origin-top-left",
1432
+ style: {
1433
+ transform: `translate(${viewport.panX}px, ${viewport.panY}px) scale(${viewport.zoom})`
1434
+ },
1435
+ children: others
1436
+ }
1437
+ ),
1438
+ /* @__PURE__ */ jsxRuntime.jsxs(
1439
+ "svg",
1440
+ {
1441
+ className: "pointer-events-none absolute inset-0 h-full w-full",
1442
+ style: {
1443
+ transform: `translate(${viewport.panX}px, ${viewport.panY}px) scale(${viewport.zoom})`,
1444
+ transformOrigin: "0 0"
1445
+ },
1446
+ children: [
1447
+ /* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
1448
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-arrow", viewBox: "0 0 10 10", refX: "10", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M0,0 L10,5 L0,10 Z", fill: "#71717a" }) }),
1449
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-circle", viewBox: "0 0 10 10", refX: "5", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "5", cy: "5", r: "3.5", fill: "#71717a" }) }),
1450
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-diamond", viewBox: "0 0 12 12", refX: "6", refY: "6", markerWidth: "10", markerHeight: "10", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "6,0 12,6 6,12 0,6", fill: "none", stroke: "#71717a", strokeWidth: "1.5" }) }),
1451
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-one", viewBox: "0 0 2 16", refX: "1", refY: "8", markerWidth: "2", markerHeight: "14", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "1", y1: "0", x2: "1", y2: "16", stroke: "#71717a", strokeWidth: "2" }) }),
1452
+ /* @__PURE__ */ jsxRuntime.jsxs("marker", { id: "canvas-crow-foot", viewBox: "0 0 16 16", refX: "16", refY: "8", markerWidth: "14", markerHeight: "14", orient: "auto", children: [
1453
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", y1: "8", x2: "0", y2: "0", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
1454
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", y1: "8", x2: "0", y2: "8", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
1455
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", y1: "8", x2: "0", y2: "16", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
1456
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", y1: "0", x2: "16", y2: "16", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" })
1457
+ ] })
1458
+ ] }),
1459
+ edges
1460
+ ]
1461
+ }
1462
+ ),
1463
+ overlays
1464
+ ]
1465
+ }
1466
+ ) });
1467
+ }
1468
+ var Canvas = Object.assign(CanvasRoot, {
1469
+ Node: CanvasNode,
1470
+ Edge: CanvasEdge,
1471
+ Minimap: CanvasMinimap,
1472
+ Controls: CanvasControls
1473
+ });
1474
+ var DiagramContext = react.createContext(null);
1475
+ function useDiagram() {
1476
+ const ctx = react.useContext(DiagramContext);
1477
+ if (!ctx) {
1478
+ throw new Error("useDiagram must be used within a <Diagram> component");
1479
+ }
1480
+ return ctx;
1481
+ }
1482
+ function DiagramField({
1483
+ name,
1484
+ type,
1485
+ primary = false,
1486
+ foreign = false,
1487
+ nullable = false,
1488
+ className
1489
+ }) {
1490
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1491
+ "div",
1492
+ {
1493
+ "data-react-fancy-diagram-field": "",
1494
+ className: cn(
1495
+ "flex items-center justify-between gap-2 border-t border-zinc-200 px-3 py-1 text-sm dark:border-zinc-700",
1496
+ className
1497
+ ),
1498
+ children: [
1499
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
1500
+ primary && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex items-center rounded bg-blue-100 px-1 py-0.5 text-[10px] font-semibold leading-none text-blue-700 dark:bg-blue-900/50 dark:text-blue-300", children: "PK" }),
1501
+ foreign && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex items-center rounded bg-amber-100 px-1 py-0.5 text-[10px] font-semibold leading-none text-amber-700 dark:bg-amber-900/50 dark:text-amber-300", children: "FK" }),
1502
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-zinc-800 dark:text-zinc-200", children: name })
1503
+ ] }),
1504
+ type && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "shrink-0 text-xs text-zinc-400 dark:text-zinc-500", children: [
1505
+ type,
1506
+ nullable && "?"
1507
+ ] })
1508
+ ]
1509
+ }
1510
+ );
1511
+ }
1512
+ DiagramField.displayName = "DiagramField";
1513
+ function DiagramEntity({
1514
+ children,
1515
+ id: idProp,
1516
+ name,
1517
+ x = 0,
1518
+ y = 0,
1519
+ color = "bg-blue-600 dark:bg-blue-500",
1520
+ draggable,
1521
+ onPositionChange,
1522
+ className
1523
+ }) {
1524
+ const id = idProp ?? name;
1525
+ const fields = [];
1526
+ const other = [];
1527
+ react.Children.forEach(children, (child) => {
1528
+ const el = child;
1529
+ if (!el || !el.type) return;
1530
+ if (el.type === DiagramField) {
1531
+ fields.push(el);
1532
+ } else {
1533
+ other.push(el);
1534
+ }
1535
+ });
1536
+ return /* @__PURE__ */ jsxRuntime.jsx(Canvas.Node, { id, x, y, draggable, onPositionChange, children: /* @__PURE__ */ jsxRuntime.jsxs(
1537
+ "div",
1538
+ {
1539
+ "data-react-fancy-diagram-entity": "",
1540
+ "data-entity-id": id,
1541
+ className: cn(
1542
+ "w-[220px] overflow-hidden rounded-lg border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-800",
1543
+ className
1544
+ ),
1545
+ children: [
1546
+ /* @__PURE__ */ jsxRuntime.jsx(
1547
+ "div",
1548
+ {
1549
+ className: cn(
1550
+ "px-3 py-2 text-sm font-semibold text-white",
1551
+ color
1552
+ ),
1553
+ children: name
1554
+ }
1555
+ ),
1556
+ fields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { children: fields }),
1557
+ other
1558
+ ]
1559
+ }
1560
+ ) });
1561
+ }
1562
+ DiagramEntity.displayName = "DiagramEntity";
1563
+
1564
+ // src/components/Diagram/diagram.markers.ts
1565
+ function defaultMarkersForType(type) {
1566
+ switch (type) {
1567
+ case "one-to-one":
1568
+ return { fromMarker: "one", toMarker: "one", lineStyle: "solid" };
1569
+ case "one-to-many":
1570
+ return { fromMarker: "one", toMarker: "many", lineStyle: "solid" };
1571
+ case "many-to-one":
1572
+ return { fromMarker: "many", toMarker: "one", lineStyle: "solid" };
1573
+ case "many-to-many":
1574
+ return { fromMarker: "many", toMarker: "many", lineStyle: "solid" };
1575
+ case "association":
1576
+ return { fromMarker: "none", toMarker: "arrow", lineStyle: "solid" };
1577
+ case "aggregation":
1578
+ return { fromMarker: "diamond-open", toMarker: "none", lineStyle: "solid" };
1579
+ case "composition":
1580
+ return { fromMarker: "diamond", toMarker: "none", lineStyle: "solid" };
1581
+ case "inheritance":
1582
+ return { fromMarker: "none", toMarker: "triangle-open", lineStyle: "solid" };
1583
+ case "implementation":
1584
+ return { fromMarker: "none", toMarker: "triangle-open", lineStyle: "dashed" };
1585
+ case "dependency":
1586
+ return { fromMarker: "none", toMarker: "arrow", lineStyle: "dashed" };
1587
+ default:
1588
+ return { fromMarker: "none", toMarker: "none", lineStyle: "solid" };
1589
+ }
1590
+ }
1591
+ var SIZE = 12;
1592
+ function renderMarker(marker, pt, direction) {
1593
+ if (typeof marker === "string" && marker.startsWith("emoji:")) {
1594
+ return { paths: [], text: marker.slice(6) };
1595
+ }
1596
+ switch (marker) {
1597
+ case "none":
1598
+ return null;
1599
+ case "arrow":
1600
+ return { paths: [{ d: arrowPath(pt, direction), fill: "stroke" }] };
1601
+ case "arrow-open":
1602
+ return { paths: [{ d: arrowPath(pt, direction), fill: "none" }] };
1603
+ case "circle":
1604
+ return { paths: [{ d: circlePath(pt), fill: "stroke" }] };
1605
+ case "circle-open":
1606
+ return { paths: [{ d: circlePath(pt), fill: "background" }] };
1607
+ case "square":
1608
+ return { paths: [{ d: squarePath(pt, direction), fill: "stroke" }] };
1609
+ case "square-open":
1610
+ return { paths: [{ d: squarePath(pt, direction), fill: "background" }] };
1611
+ case "diamond":
1612
+ return { paths: [{ d: diamondPath(pt, direction), fill: "stroke" }] };
1613
+ case "diamond-open":
1614
+ return { paths: [{ d: diamondPath(pt, direction), fill: "background" }] };
1615
+ case "triangle":
1616
+ return { paths: [{ d: trianglePath(pt, direction), fill: "stroke" }] };
1617
+ case "triangle-open":
1618
+ return { paths: [{ d: trianglePath(pt, direction), fill: "background" }] };
1619
+ case "cross":
1620
+ return { paths: [{ d: crossPath(pt, direction), fill: "none" }] };
1621
+ case "one":
1622
+ return { paths: [{ d: oneSymbol(pt, direction), fill: "none" }] };
1623
+ case "many":
1624
+ return { paths: [{ d: crowFootSymbol(pt, direction), fill: "none" }] };
1625
+ case "optional-one":
1626
+ return {
1627
+ paths: [
1628
+ { d: circleOuter(pt, direction), fill: "background" },
1629
+ { d: oneSymbolOffset(pt, direction), fill: "none" }
1630
+ ]
1631
+ };
1632
+ case "optional-many":
1633
+ return {
1634
+ paths: [
1635
+ { d: circleOuter(pt, direction), fill: "background" },
1636
+ { d: crowFootSymbolOffset(pt, direction), fill: "none" }
1637
+ ]
1638
+ };
1639
+ default:
1640
+ if (typeof marker === "string" && marker !== "") {
1641
+ return { paths: [], text: marker };
1642
+ }
1643
+ return null;
1644
+ }
1645
+ }
1646
+ function markerInset(marker) {
1647
+ if (marker === "none" || marker === void 0) return 0;
1648
+ if (typeof marker === "string" && (marker.startsWith("emoji:") || !KNOWN_MARKERS.has(marker))) {
1649
+ return SIZE;
1650
+ }
1651
+ switch (marker) {
1652
+ case "circle":
1653
+ case "circle-open":
1654
+ return SIZE * 0.6;
1655
+ case "one":
1656
+ return 0;
1657
+ // bar sits AT endpoint
1658
+ case "many":
1659
+ return SIZE;
1660
+ case "optional-one":
1661
+ return SIZE * 1.2;
1662
+ case "optional-many":
1663
+ return SIZE * 1.8;
1664
+ default:
1665
+ return SIZE;
1666
+ }
1667
+ }
1668
+ var KNOWN_MARKERS = /* @__PURE__ */ new Set([
1669
+ "none",
1670
+ "arrow",
1671
+ "arrow-open",
1672
+ "circle",
1673
+ "circle-open",
1674
+ "square",
1675
+ "square-open",
1676
+ "diamond",
1677
+ "diamond-open",
1678
+ "triangle",
1679
+ "triangle-open",
1680
+ "one",
1681
+ "many",
1682
+ "optional-one",
1683
+ "optional-many",
1684
+ "cross"
1685
+ ]);
1686
+ function dirVec(direction) {
1687
+ switch (direction) {
1688
+ case "left":
1689
+ return [-1, 0];
1690
+ case "right":
1691
+ return [1, 0];
1692
+ case "up":
1693
+ return [0, -1];
1694
+ case "down":
1695
+ return [0, 1];
1696
+ }
1697
+ }
1698
+ function perpVec(direction) {
1699
+ switch (direction) {
1700
+ case "left":
1701
+ case "right":
1702
+ return [0, 1];
1703
+ case "up":
1704
+ case "down":
1705
+ return [1, 0];
1706
+ }
1707
+ }
1708
+ function arrowPath(pt, direction, _filled) {
1709
+ const [dx, dy] = dirVec(direction);
1710
+ const [px, py] = perpVec(direction);
1711
+ const tipX = pt.x + dx * SIZE;
1712
+ const tipY = pt.y + dy * SIZE;
1713
+ const baseAX = pt.x + px * (SIZE * 0.55);
1714
+ const baseAY = pt.y + py * (SIZE * 0.55);
1715
+ const baseBX = pt.x - px * (SIZE * 0.55);
1716
+ const baseBY = pt.y - py * (SIZE * 0.55);
1717
+ return `M${baseAX},${baseAY} L${tipX},${tipY} L${baseBX},${baseBY} Z`;
1718
+ }
1719
+ function circlePath(pt, _filled) {
1720
+ const r = SIZE * 0.45;
1721
+ return `M${pt.x - r},${pt.y} a${r},${r} 0 1 0 ${r * 2},0 a${r},${r} 0 1 0 ${-r * 2},0 Z`;
1722
+ }
1723
+ function squarePath(pt, direction, _filled) {
1724
+ const [dx, dy] = dirVec(direction);
1725
+ const [px, py] = perpVec(direction);
1726
+ const half = SIZE * 0.5;
1727
+ const cx = pt.x + dx * half;
1728
+ const cy = pt.y + dy * half;
1729
+ const tlX = cx - px * half - dx * half;
1730
+ const tlY = cy - py * half - dy * half;
1731
+ const trX = cx + px * half - dx * half;
1732
+ const trY = cy + py * half - dy * half;
1733
+ const brX = cx + px * half + dx * half;
1734
+ const brY = cy + py * half + dy * half;
1735
+ const blX = cx - px * half + dx * half;
1736
+ const blY = cy - py * half + dy * half;
1737
+ return `M${tlX},${tlY} L${trX},${trY} L${brX},${brY} L${blX},${blY} Z`;
1738
+ }
1739
+ function diamondPath(pt, direction, _filled) {
1740
+ const [dx, dy] = dirVec(direction);
1741
+ const [px, py] = perpVec(direction);
1742
+ const len = SIZE * 1.2;
1743
+ const cx = pt.x + dx * (len / 2);
1744
+ const cy = pt.y + dy * (len / 2);
1745
+ const aX = pt.x;
1746
+ const aY = pt.y;
1747
+ const bX = cx + px * (SIZE * 0.45);
1748
+ const bY = cy + py * (SIZE * 0.45);
1749
+ const tX = pt.x + dx * len;
1750
+ const tY = pt.y + dy * len;
1751
+ const dX = cx - px * (SIZE * 0.45);
1752
+ const dY = cy - py * (SIZE * 0.45);
1753
+ return `M${aX},${aY} L${bX},${bY} L${tX},${tY} L${dX},${dY} Z`;
1754
+ }
1755
+ function trianglePath(pt, direction, _filled) {
1756
+ const [dx, dy] = dirVec(direction);
1757
+ const [px, py] = perpVec(direction);
1758
+ const len = SIZE * 1.1;
1759
+ const baseCX = pt.x + dx * len;
1760
+ const baseCY = pt.y + dy * len;
1761
+ const baseAX = baseCX + px * (SIZE * 0.6);
1762
+ const baseAY = baseCY + py * (SIZE * 0.6);
1763
+ const baseBX = baseCX - px * (SIZE * 0.6);
1764
+ const baseBY = baseCY - py * (SIZE * 0.6);
1765
+ return `M${pt.x},${pt.y} L${baseAX},${baseAY} L${baseBX},${baseBY} Z`;
1766
+ }
1767
+ function crossPath(pt, direction) {
1768
+ const [dx, dy] = dirVec(direction);
1769
+ const [px, py] = perpVec(direction);
1770
+ const s = SIZE * 0.5;
1771
+ const cx = pt.x + dx * (SIZE * 0.5);
1772
+ const cy = pt.y + dy * (SIZE * 0.5);
1773
+ return [
1774
+ `M${cx - s * (px + dx)},${cy - s * (py + dy)} L${cx + s * (px + dx)},${cy + s * (py + dy)}`,
1775
+ `M${cx - s * (px - dx)},${cy - s * (py - dy)} L${cx + s * (px - dx)},${cy + s * (py - dy)}`
1776
+ ].join(" ");
1777
+ }
1778
+ function oneSymbol(pt, direction) {
1779
+ const [, py] = perpVec(direction);
1780
+ const [px] = perpVec(direction);
1781
+ const half = SIZE * 0.6;
1782
+ return `M${pt.x - px * half},${pt.y - py * half} L${pt.x + px * half},${pt.y + py * half}`;
1783
+ }
1784
+ function crowFootSymbol(pt, direction) {
1785
+ const [dx, dy] = dirVec(direction);
1786
+ const [px, py] = perpVec(direction);
1787
+ const tipX = pt.x + dx * SIZE;
1788
+ const tipY = pt.y + dy * SIZE;
1789
+ const spread = SIZE * 0.8;
1790
+ const aX = pt.x + px * spread, aY = pt.y + py * spread;
1791
+ const cX = pt.x - px * spread, cY = pt.y - py * spread;
1792
+ return [
1793
+ `M${aX},${aY} L${tipX},${tipY}`,
1794
+ `M${pt.x},${pt.y} L${tipX},${tipY}`,
1795
+ `M${cX},${cY} L${tipX},${tipY}`,
1796
+ `M${aX},${aY} L${cX},${cY}`
1797
+ ].join(" ");
1798
+ }
1799
+ function circleOuter(pt, direction) {
1800
+ const [dx, dy] = dirVec(direction);
1801
+ const r = SIZE * 0.4;
1802
+ const cx = pt.x + dx * (SIZE * 0.4 + r);
1803
+ const cy = pt.y + dy * (SIZE * 0.4 + r);
1804
+ return `M${cx - r},${cy} a${r},${r} 0 1 0 ${r * 2},0 a${r},${r} 0 1 0 ${-r * 2},0 Z`;
1805
+ }
1806
+ function oneSymbolOffset(pt, direction) {
1807
+ const [px, py] = perpVec(direction);
1808
+ const half = SIZE * 0.6;
1809
+ return `M${pt.x - px * half},${pt.y - py * half} L${pt.x + px * half},${pt.y + py * half}`;
1810
+ }
1811
+ function crowFootSymbolOffset(pt, direction) {
1812
+ const [dx, dy] = dirVec(direction);
1813
+ const [px, py] = perpVec(direction);
1814
+ const inset = SIZE * 0.8;
1815
+ const startX = pt.x + dx * inset;
1816
+ const startY = pt.y + dy * inset;
1817
+ const tipX = pt.x + dx * (inset + SIZE);
1818
+ const tipY = pt.y + dy * (inset + SIZE);
1819
+ const spread = SIZE * 0.8;
1820
+ const aX = startX + px * spread, aY = startY + py * spread;
1821
+ const cX = startX - px * spread, cY = startY - py * spread;
1822
+ return [
1823
+ `M${aX},${aY} L${tipX},${tipY}`,
1824
+ `M${startX},${startY} L${tipX},${tipY}`,
1825
+ `M${cX},${cY} L${tipX},${tipY}`
1826
+ ].join(" ");
1827
+ }
1828
+
1829
+ // src/components/Diagram/diagram.routing.ts
1830
+ var STUB = 24;
1831
+ var DODGE_PADDING = 16;
1832
+ var MAX_DODGE_ITERATIONS = 6;
1833
+ function pickAnchors(from, to, fromY, toY) {
1834
+ const fcx = from.x + from.width / 2;
1835
+ const fcy = from.y + from.height / 2;
1836
+ const tcx = to.x + to.width / 2;
1837
+ const tcy = to.y + to.height / 2;
1838
+ const dx = tcx - fcx;
1839
+ const dy = tcy - fcy;
1840
+ let fromSide, toSide;
1841
+ if (Math.abs(dx) >= Math.abs(dy)) {
1842
+ fromSide = dx >= 0 ? "right" : "left";
1843
+ toSide = dx >= 0 ? "left" : "right";
1844
+ } else {
1845
+ fromSide = dy >= 0 ? "bottom" : "top";
1846
+ toSide = dy >= 0 ? "top" : "bottom";
1847
+ }
1848
+ return {
1849
+ from: anchorOnSide(from, fromSide, fromY),
1850
+ to: anchorOnSide(to, toSide, toY)
1851
+ };
1852
+ }
1853
+ function anchorOnSide(box, side, fieldY) {
1854
+ switch (side) {
1855
+ case "right":
1856
+ return { side, x: box.x + box.width, y: box.y + (fieldY ?? box.height / 2) };
1857
+ case "left":
1858
+ return { side, x: box.x, y: box.y + (fieldY ?? box.height / 2) };
1859
+ case "top":
1860
+ return { side, x: box.x + box.width / 2, y: box.y };
1861
+ case "bottom":
1862
+ return { side, x: box.x + box.width / 2, y: box.y + box.height };
1863
+ }
1864
+ }
1865
+ function stubOut(a, distance2 = STUB) {
1866
+ switch (a.side) {
1867
+ case "right":
1868
+ return { x: a.x + distance2, y: a.y };
1869
+ case "left":
1870
+ return { x: a.x - distance2, y: a.y };
1871
+ case "top":
1872
+ return { x: a.x, y: a.y - distance2 };
1873
+ case "bottom":
1874
+ return { x: a.x, y: a.y + distance2 };
1875
+ }
1876
+ }
1877
+ function manhattanPath(from, to, obstacles = []) {
1878
+ const f = { x: from.x, y: from.y };
1879
+ const t = { x: to.x, y: to.y };
1880
+ const fs = stubOut(from);
1881
+ const ts = stubOut(to);
1882
+ const fHoriz = from.side === "left" || from.side === "right";
1883
+ const tHoriz = to.side === "left" || to.side === "right";
1884
+ if (fHoriz && tHoriz) {
1885
+ const midX = pickClearMidX((fs.x + ts.x) / 2, fs.y, ts.y, obstacles);
1886
+ return uniqPath([
1887
+ f,
1888
+ fs,
1889
+ { x: midX, y: fs.y },
1890
+ { x: midX, y: ts.y },
1891
+ ts,
1892
+ t
1893
+ ]);
1894
+ }
1895
+ if (!fHoriz && !tHoriz) {
1896
+ const midY = pickClearMidY((fs.y + ts.y) / 2, fs.x, ts.x, obstacles);
1897
+ return uniqPath([
1898
+ f,
1899
+ fs,
1900
+ { x: fs.x, y: midY },
1901
+ { x: ts.x, y: midY },
1902
+ ts,
1903
+ t
1904
+ ]);
1905
+ }
1906
+ if (fHoriz) {
1907
+ return uniqPath([
1908
+ f,
1909
+ fs,
1910
+ { x: ts.x, y: fs.y },
1911
+ ts,
1912
+ t
1913
+ ]);
1914
+ }
1915
+ return uniqPath([
1916
+ f,
1917
+ fs,
1918
+ { x: fs.x, y: ts.y },
1919
+ ts,
1920
+ t
1921
+ ]);
1922
+ }
1923
+ function pickClearMidX(idealX, y1, y2, obstacles) {
1924
+ if (obstacles.length === 0) return idealX;
1925
+ const yMin = Math.min(y1, y2);
1926
+ const yMax = Math.max(y1, y2);
1927
+ let midX = idealX;
1928
+ for (let i = 0; i < 4; i++) {
1929
+ let shifted = false;
1930
+ for (const ob of obstacles) {
1931
+ if (ob.y + ob.height + DODGE_PADDING < yMin) continue;
1932
+ if (ob.y - DODGE_PADDING > yMax) continue;
1933
+ const left = ob.x - DODGE_PADDING;
1934
+ const right = ob.x + ob.width + DODGE_PADDING;
1935
+ if (midX > left && midX < right) {
1936
+ midX = Math.abs(midX - left) <= Math.abs(midX - right) ? left - 1 : right + 1;
1937
+ shifted = true;
1938
+ }
1939
+ }
1940
+ if (!shifted) break;
1941
+ }
1942
+ return midX;
1943
+ }
1944
+ function pickClearMidY(idealY, x1, x2, obstacles) {
1945
+ if (obstacles.length === 0) return idealY;
1946
+ const xMin = Math.min(x1, x2);
1947
+ const xMax = Math.max(x1, x2);
1948
+ let midY = idealY;
1949
+ for (let i = 0; i < 4; i++) {
1950
+ let shifted = false;
1951
+ for (const ob of obstacles) {
1952
+ if (ob.x + ob.width + DODGE_PADDING < xMin) continue;
1953
+ if (ob.x - DODGE_PADDING > xMax) continue;
1954
+ const top = ob.y - DODGE_PADDING;
1955
+ const bot = ob.y + ob.height + DODGE_PADDING;
1956
+ if (midY > top && midY < bot) {
1957
+ midY = Math.abs(midY - top) <= Math.abs(midY - bot) ? top - 1 : bot + 1;
1958
+ shifted = true;
1959
+ }
1960
+ }
1961
+ if (!shifted) break;
1962
+ }
1963
+ return midY;
1964
+ }
1965
+ function uniqPath(points) {
1966
+ const out = [];
1967
+ for (const p of points) {
1968
+ const last = out[out.length - 1];
1969
+ if (!last || Math.abs(last.x - p.x) > 0.5 || Math.abs(last.y - p.y) > 0.5) {
1970
+ out.push(p);
1971
+ }
1972
+ }
1973
+ return out;
1974
+ }
1975
+ function dodgeObstacles(path, obstacles, padding = DODGE_PADDING) {
1976
+ if (obstacles.length === 0 || path.length < 2) return path;
1977
+ let working = path.slice();
1978
+ for (let iter = 0; iter < MAX_DODGE_ITERATIONS; iter++) {
1979
+ let dodged = false;
1980
+ const result = [working[0]];
1981
+ for (let i = 1; i < working.length; i++) {
1982
+ const a = result[result.length - 1];
1983
+ const b = working[i];
1984
+ const detour = detourAround(a, b, obstacles, padding);
1985
+ if (detour.length === 0) {
1986
+ result.push(b);
1987
+ } else {
1988
+ for (const p of detour) result.push(p);
1989
+ result.push(b);
1990
+ dodged = true;
1991
+ }
1992
+ }
1993
+ working = uniqPath(result);
1994
+ if (!dodged) break;
1995
+ }
1996
+ return working;
1997
+ }
1998
+ function detourAround(a, b, obstacles, padding) {
1999
+ const isHorizontal = Math.abs(a.y - b.y) < 0.5;
2000
+ const isVertical = Math.abs(a.x - b.x) < 0.5;
2001
+ if (!isHorizontal && !isVertical) return [];
2002
+ let best = null;
2003
+ for (const r of obstacles) {
2004
+ const expanded = expandRect(r, padding);
2005
+ if (!segmentCrossesRect(a, b, expanded)) continue;
2006
+ let entry, exit;
2007
+ if (isHorizontal) {
2008
+ entry = b.x > a.x ? expanded.x : expanded.x + expanded.width;
2009
+ exit = b.x > a.x ? expanded.x + expanded.width : expanded.x;
2010
+ } else {
2011
+ entry = b.y > a.y ? expanded.y : expanded.y + expanded.height;
2012
+ exit = b.y > a.y ? expanded.y + expanded.height : expanded.y;
2013
+ }
2014
+ const dist = isHorizontal ? Math.abs(entry - a.x) : Math.abs(entry - a.y);
2015
+ if (!best || dist < (isHorizontal ? Math.abs(best.entry - a.x) : Math.abs(best.entry - a.y))) {
2016
+ best = { rect: expanded, entry, exit };
2017
+ }
2018
+ }
2019
+ if (!best) return [];
2020
+ if (isHorizontal) {
2021
+ const aboveY = best.rect.y - 1;
2022
+ const belowY = best.rect.y + best.rect.height + 1;
2023
+ const detourY = Math.abs(a.y - aboveY) <= Math.abs(a.y - belowY) ? aboveY : belowY;
2024
+ return [
2025
+ { x: best.entry, y: a.y },
2026
+ { x: best.entry, y: detourY },
2027
+ { x: best.exit, y: detourY },
2028
+ { x: best.exit, y: a.y }
2029
+ ];
2030
+ } else {
2031
+ const leftX = best.rect.x - 1;
2032
+ const rightX = best.rect.x + best.rect.width + 1;
2033
+ const detourX = Math.abs(a.x - leftX) <= Math.abs(a.x - rightX) ? leftX : rightX;
2034
+ return [
2035
+ { x: a.x, y: best.entry },
2036
+ { x: detourX, y: best.entry },
2037
+ { x: detourX, y: best.exit },
2038
+ { x: a.x, y: best.exit }
2039
+ ];
2040
+ }
2041
+ }
2042
+ function expandRect(r, padding) {
2043
+ return {
2044
+ x: r.x - padding,
2045
+ y: r.y - padding,
2046
+ width: r.width + padding * 2,
2047
+ height: r.height + padding * 2
2048
+ };
2049
+ }
2050
+ function segmentCrossesRect(a, b, rect) {
2051
+ const xMin = Math.min(a.x, b.x);
2052
+ const xMax = Math.max(a.x, b.x);
2053
+ const yMin = Math.min(a.y, b.y);
2054
+ const yMax = Math.max(a.y, b.y);
2055
+ if (xMax < rect.x) return false;
2056
+ if (xMin > rect.x + rect.width) return false;
2057
+ if (yMax < rect.y) return false;
2058
+ if (yMin > rect.y + rect.height) return false;
2059
+ if (a.x >= rect.x + 1 && a.x <= rect.x + rect.width - 1 && a.y >= rect.y + 1 && a.y <= rect.y + rect.height - 1) return false;
2060
+ if (b.x >= rect.x + 1 && b.x <= rect.x + rect.width - 1 && b.y >= rect.y + 1 && b.y <= rect.y + rect.height - 1) return false;
2061
+ return true;
2062
+ }
2063
+ function pathFromPoints(points, cornerRadius = 8) {
2064
+ if (points.length === 0) return "";
2065
+ if (points.length === 1) return `M${points[0].x},${points[0].y}`;
2066
+ if (points.length === 2 || cornerRadius <= 0) {
2067
+ return points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ");
2068
+ }
2069
+ let d = `M${points[0].x},${points[0].y}`;
2070
+ for (let i = 1; i < points.length - 1; i++) {
2071
+ const prev = points[i - 1];
2072
+ const curr = points[i];
2073
+ const next = points[i + 1];
2074
+ const r = Math.min(
2075
+ cornerRadius,
2076
+ distance(prev, curr) / 2,
2077
+ distance(curr, next) / 2
2078
+ );
2079
+ if (r < 1) {
2080
+ d += ` L${curr.x},${curr.y}`;
2081
+ continue;
2082
+ }
2083
+ const beforeX = curr.x + Math.sign(prev.x - curr.x) * r;
2084
+ const beforeY = curr.y + Math.sign(prev.y - curr.y) * r;
2085
+ const afterX = curr.x + Math.sign(next.x - curr.x) * r;
2086
+ const afterY = curr.y + Math.sign(next.y - curr.y) * r;
2087
+ d += ` L${beforeX},${beforeY} Q${curr.x},${curr.y} ${afterX},${afterY}`;
2088
+ }
2089
+ const last = points[points.length - 1];
2090
+ d += ` L${last.x},${last.y}`;
2091
+ return d;
2092
+ }
2093
+ function distance(a, b) {
2094
+ return Math.hypot(b.x - a.x, b.y - a.y);
2095
+ }
2096
+ function bezierPath2(from, to) {
2097
+ const fs = stubOut(from, Math.max(40, distance(from, to) * 0.3));
2098
+ const ts = stubOut(to, Math.max(40, distance(from, to) * 0.3));
2099
+ return `M${from.x},${from.y} C${fs.x},${fs.y} ${ts.x},${ts.y} ${to.x},${to.y}`;
2100
+ }
2101
+ function midPoint(points) {
2102
+ if (points.length === 0) return { x: 0, y: 0 };
2103
+ if (points.length === 1) return points[0];
2104
+ let total = 0;
2105
+ const segLens = [];
2106
+ for (let i = 1; i < points.length; i++) {
2107
+ const len = distance(points[i - 1], points[i]);
2108
+ segLens.push(len);
2109
+ total += len;
2110
+ }
2111
+ let target = total / 2;
2112
+ for (let i = 0; i < segLens.length; i++) {
2113
+ if (target <= segLens[i]) {
2114
+ const t = segLens[i] === 0 ? 0 : target / segLens[i];
2115
+ const a = points[i];
2116
+ const b = points[i + 1];
2117
+ return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
2118
+ }
2119
+ target -= segLens[i];
2120
+ }
2121
+ return points[points.length - 1];
2122
+ }
2123
+ function insetEndpoints(points, inset) {
2124
+ if (points.length < 2) return points;
2125
+ const result = points.slice();
2126
+ if (inset.from > 0) {
2127
+ const a = result[0];
2128
+ const b = result[1];
2129
+ const len = distance(a, b);
2130
+ if (len > inset.from) {
2131
+ const t = inset.from / len;
2132
+ result[0] = { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
2133
+ }
2134
+ }
2135
+ if (inset.to > 0) {
2136
+ const lastIdx = result.length - 1;
2137
+ const a = result[lastIdx];
2138
+ const b = result[lastIdx - 1];
2139
+ const len = distance(a, b);
2140
+ if (len > inset.to) {
2141
+ const t = inset.to / len;
2142
+ result[lastIdx] = { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
2143
+ }
2144
+ }
2145
+ return result;
2146
+ }
2147
+ var HEADER_HEIGHT = 36;
2148
+ var FIELD_HEIGHT = 29;
2149
+ var DEFAULT_COLOR = "#71717a";
2150
+ function markerDirection(side) {
2151
+ switch (side) {
2152
+ case "left":
2153
+ return "right";
2154
+ // entity body is to the right of a left-side anchor
2155
+ case "right":
2156
+ return "left";
2157
+ case "top":
2158
+ return "down";
2159
+ case "bottom":
2160
+ return "up";
2161
+ }
2162
+ }
2163
+ function strokeDashArray(style) {
2164
+ switch (style) {
2165
+ case "dashed":
2166
+ return "8 4";
2167
+ case "dotted":
2168
+ return "2 4";
2169
+ default:
2170
+ return void 0;
2171
+ }
2172
+ }
2173
+ function DiagramRelation({
2174
+ from,
2175
+ to,
2176
+ fromField: fromFieldProp,
2177
+ toField: toFieldProp,
2178
+ type,
2179
+ fromMarker: fromMarkerProp,
2180
+ toMarker: toMarkerProp,
2181
+ lineStyle: lineStyleProp,
2182
+ routing = "manhattan",
2183
+ color = DEFAULT_COLOR,
2184
+ strokeWidth = 2,
2185
+ label
2186
+ }) {
2187
+ const { nodeRects, registryVersion } = useCanvas();
2188
+ const { schema } = useDiagram();
2189
+ const result = react.useMemo(() => {
2190
+ const fromRect = nodeRects.get(from);
2191
+ const toRect = nodeRects.get(to);
2192
+ if (!fromRect || !toRect) return null;
2193
+ const defaults = defaultMarkersForType(type);
2194
+ const fromMarker = fromMarkerProp ?? defaults.fromMarker;
2195
+ const toMarker = toMarkerProp ?? defaults.toMarker;
2196
+ const lineStyle = lineStyleProp ?? defaults.lineStyle;
2197
+ const fromEntity = schema.entities.find((e) => (e.id ?? e.name) === from);
2198
+ const toEntity = schema.entities.find((e) => (e.id ?? e.name) === to);
2199
+ const fromFieldY = resolveFieldY(fromRect, fromEntity?.fields, fromFieldProp, true);
2200
+ const toFieldY = resolveFieldY(toRect, toEntity?.fields, toFieldProp, false, fromEntity?.name ?? from);
2201
+ const anchors = pickAnchors(fromRect, toRect, fromFieldY, toFieldY);
2202
+ if (anchors.from.side === "top" || anchors.from.side === "bottom") {
2203
+ anchors.from.x = fromRect.x + fromRect.width / 2;
2204
+ }
2205
+ if (anchors.to.side === "top" || anchors.to.side === "bottom") {
2206
+ anchors.to.x = toRect.x + toRect.width / 2;
2207
+ }
2208
+ let points;
2209
+ if (routing === "straight") {
2210
+ points = [
2211
+ { x: anchors.from.x, y: anchors.from.y },
2212
+ { x: anchors.to.x, y: anchors.to.y }
2213
+ ];
2214
+ } else if (routing === "bezier") {
2215
+ points = [];
2216
+ } else {
2217
+ const obstacles = [];
2218
+ nodeRects.forEach((rect, id) => {
2219
+ if (id === from || id === to) return;
2220
+ obstacles.push(rect);
2221
+ });
2222
+ const initial = manhattanPath(anchors.from, anchors.to, obstacles);
2223
+ points = dodgeObstacles(initial, obstacles);
2224
+ }
2225
+ const insetAmount = { from: markerInset(fromMarker), to: markerInset(toMarker) };
2226
+ if (routing !== "bezier") {
2227
+ points = insetEndpoints(points, insetAmount);
2228
+ }
2229
+ const linePath = routing === "bezier" ? bezierPath2(anchors.from, anchors.to) : pathFromPoints(points);
2230
+ const fromMarkerRenderable = renderMarker(
2231
+ fromMarker,
2232
+ { x: anchors.from.x, y: anchors.from.y },
2233
+ markerDirection(anchors.from.side)
2234
+ );
2235
+ const toMarkerRenderable = renderMarker(
2236
+ toMarker,
2237
+ { x: anchors.to.x, y: anchors.to.y },
2238
+ markerDirection(anchors.to.side)
2239
+ );
2240
+ const mid = routing === "bezier" ? { x: (anchors.from.x + anchors.to.x) / 2, y: (anchors.from.y + anchors.to.y) / 2 } : midPoint(points);
2241
+ return {
2242
+ linePath,
2243
+ fromMarker: fromMarkerRenderable,
2244
+ toMarker: toMarkerRenderable,
2245
+ fromAnchor: anchors.from,
2246
+ toAnchor: anchors.to,
2247
+ mid,
2248
+ lineStyle
2249
+ };
2250
+ }, [
2251
+ from,
2252
+ to,
2253
+ fromFieldProp,
2254
+ toFieldProp,
2255
+ type,
2256
+ fromMarkerProp,
2257
+ toMarkerProp,
2258
+ lineStyleProp,
2259
+ routing,
2260
+ schema,
2261
+ nodeRects,
2262
+ registryVersion
2263
+ ]);
2264
+ if (!result) return null;
2265
+ const dashArray = strokeDashArray(result.lineStyle);
2266
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { "data-react-fancy-diagram-relation": "", children: [
2267
+ /* @__PURE__ */ jsxRuntime.jsx(
2268
+ "path",
2269
+ {
2270
+ d: result.linePath,
2271
+ fill: "none",
2272
+ stroke: color,
2273
+ strokeWidth,
2274
+ strokeDasharray: dashArray,
2275
+ strokeLinecap: "round",
2276
+ strokeLinejoin: "round"
2277
+ }
2278
+ ),
2279
+ result.fromMarker?.paths.map((shape, i) => /* @__PURE__ */ jsxRuntime.jsx(
2280
+ "path",
2281
+ {
2282
+ d: shape.d,
2283
+ fill: shape.fill === "stroke" ? color : shape.fill === "background" ? "#ffffff" : "none",
2284
+ stroke: color,
2285
+ strokeWidth,
2286
+ strokeLinecap: "round",
2287
+ strokeLinejoin: "round"
2288
+ },
2289
+ `fm-${i}`
2290
+ )),
2291
+ result.fromMarker?.text && /* @__PURE__ */ jsxRuntime.jsx(
2292
+ "text",
2293
+ {
2294
+ x: result.fromAnchor.x,
2295
+ y: result.fromAnchor.y,
2296
+ fontSize: 16,
2297
+ textAnchor: "middle",
2298
+ dominantBaseline: "middle",
2299
+ style: { userSelect: "none" },
2300
+ children: result.fromMarker.text
2301
+ }
2302
+ ),
2303
+ result.toMarker?.text && /* @__PURE__ */ jsxRuntime.jsx(
2304
+ "text",
2305
+ {
2306
+ x: result.toAnchor.x,
2307
+ y: result.toAnchor.y,
2308
+ fontSize: 16,
2309
+ textAnchor: "middle",
2310
+ dominantBaseline: "middle",
2311
+ style: { userSelect: "none" },
2312
+ children: result.toMarker.text
2313
+ }
2314
+ ),
2315
+ result.toMarker?.paths.map((shape, i) => /* @__PURE__ */ jsxRuntime.jsx(
2316
+ "path",
2317
+ {
2318
+ d: shape.d,
2319
+ fill: shape.fill === "stroke" ? color : shape.fill === "background" ? "#ffffff" : "none",
2320
+ stroke: color,
2321
+ strokeWidth,
2322
+ strokeLinecap: "round",
2323
+ strokeLinejoin: "round"
2324
+ },
2325
+ `tm-${i}`
2326
+ )),
2327
+ label && /* @__PURE__ */ jsxRuntime.jsx("foreignObject", { x: result.mid.x - 50, y: result.mid.y - 12, width: 100, height: 24, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center text-xs text-zinc-500", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded bg-white/90 px-1.5 py-0.5 dark:bg-zinc-900/90", children: label }) }) })
2328
+ ] });
2329
+ }
2330
+ function resolveFieldY(rect, fields, fieldProp, isFrom, fromName) {
2331
+ if (!fields || fields.length === 0) return void 0;
2332
+ let idx = -1;
2333
+ if (fieldProp) {
2334
+ idx = fields.findIndex((f) => f.name === fieldProp);
2335
+ } else if (isFrom) {
2336
+ idx = fields.findIndex((f) => f.primary);
2337
+ } else {
2338
+ const lower = (fromName ?? "").toLowerCase();
2339
+ idx = fields.findIndex(
2340
+ (f) => f.foreign && (f.name === `${lower}_id` || f.name === `${lower}Id`)
2341
+ );
2342
+ if (idx === -1) idx = fields.findIndex((f) => f.foreign);
2343
+ }
2344
+ if (idx < 0) return void 0;
2345
+ return HEADER_HEIGHT + idx * FIELD_HEIGHT + FIELD_HEIGHT / 2;
2346
+ }
2347
+ DiagramRelation._isCanvasEdge = true;
2348
+ DiagramRelation.displayName = "DiagramRelation";
2349
+ var FORMAT_LABELS = {
2350
+ erd: "ERD",
2351
+ uml: "UML",
2352
+ dfd: "DFD"
2353
+ };
2354
+ var FORMAT_EXTENSIONS = {
2355
+ erd: "erd",
2356
+ uml: "puml",
2357
+ dfd: "dfd"
2358
+ };
2359
+ function DiagramToolbar({ className }) {
2360
+ const { schema, downloadableRef, importableRef, exportFormats, onImport } = useDiagram();
2361
+ const fileInputRef = react.useRef(null);
2362
+ const canDownload = downloadableRef.current;
2363
+ const canImport = importableRef.current;
2364
+ const handleDownload = react.useCallback(
2365
+ async (format) => {
2366
+ const { serializeToERD: serializeToERD2, serializeToUML: serializeToUML2, serializeToDFD: serializeToDFD2 } = await Promise.resolve().then(() => (init_diagram_serializers(), diagram_serializers_exports));
2367
+ let content;
2368
+ switch (format) {
2369
+ case "erd":
2370
+ content = serializeToERD2(schema);
2371
+ break;
2372
+ case "uml":
2373
+ content = serializeToUML2(schema);
2374
+ break;
2375
+ case "dfd":
2376
+ content = serializeToDFD2(schema);
2377
+ break;
2378
+ }
2379
+ const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
2380
+ const url = URL.createObjectURL(blob);
2381
+ const a = document.createElement("a");
2382
+ a.href = url;
2383
+ a.download = `diagram.${FORMAT_EXTENSIONS[format]}`;
2384
+ document.body.appendChild(a);
2385
+ a.click();
2386
+ document.body.removeChild(a);
2387
+ URL.revokeObjectURL(url);
2388
+ },
2389
+ [schema]
2390
+ );
2391
+ const handleFileChange = react.useCallback(
2392
+ async (e) => {
2393
+ const file = e.target.files?.[0];
2394
+ if (!file || !onImport) return;
2395
+ const text = await file.text();
2396
+ const ext = file.name.split(".").pop()?.toLowerCase();
2397
+ const { deserializeSchema: deserializeSchema2 } = await Promise.resolve().then(() => (init_diagram_serializers(), diagram_serializers_exports));
2398
+ let format = "erd";
2399
+ if (ext === "puml" || ext === "uml") format = "uml";
2400
+ else if (ext === "dfd") format = "dfd";
2401
+ const parsed = deserializeSchema2(text, format);
2402
+ onImport(parsed);
2403
+ if (fileInputRef.current) {
2404
+ fileInputRef.current.value = "";
2405
+ }
2406
+ },
2407
+ [onImport]
2408
+ );
2409
+ if (!canDownload && !canImport) return null;
2410
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2411
+ "div",
2412
+ {
2413
+ "data-react-fancy-diagram-toolbar": "",
2414
+ className: cn(
2415
+ "absolute right-3 top-3 z-10 flex items-center gap-1 rounded-lg border border-zinc-200 bg-white/90 p-1 shadow-sm backdrop-blur-sm dark:border-zinc-700 dark:bg-zinc-800/90",
2416
+ className
2417
+ ),
2418
+ children: [
2419
+ canDownload && exportFormats.map((format) => /* @__PURE__ */ jsxRuntime.jsx(
2420
+ "button",
2421
+ {
2422
+ type: "button",
2423
+ onClick: () => handleDownload(format),
2424
+ className: "rounded px-2 py-1 text-xs font-medium text-zinc-600 transition-colors hover:bg-zinc-100 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:hover:text-zinc-200",
2425
+ children: FORMAT_LABELS[format]
2426
+ },
2427
+ format
2428
+ )),
2429
+ canImport && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2430
+ /* @__PURE__ */ jsxRuntime.jsx(
2431
+ "button",
2432
+ {
2433
+ type: "button",
2434
+ onClick: () => fileInputRef.current?.click(),
2435
+ className: "rounded px-2 py-1 text-xs font-medium text-zinc-600 transition-colors hover:bg-zinc-100 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:hover:text-zinc-200",
2436
+ children: "Import"
2437
+ }
2438
+ ),
2439
+ /* @__PURE__ */ jsxRuntime.jsx(
2440
+ "input",
2441
+ {
2442
+ ref: fileInputRef,
2443
+ type: "file",
2444
+ accept: ".erd,.puml,.uml,.dfd,.txt",
2445
+ className: "hidden",
2446
+ onChange: handleFileChange
2447
+ }
2448
+ )
2449
+ ] })
2450
+ ]
2451
+ }
2452
+ );
2453
+ }
2454
+ DiagramToolbar.displayName = "DiagramToolbar";
2455
+
2456
+ // src/components/Diagram/diagram.layout.ts
2457
+ var ENTITY_WIDTH = 220;
2458
+ var HEADER_HEIGHT2 = 40;
2459
+ var FIELD_HEIGHT2 = 28;
2460
+ var HORIZONTAL_GAP = 80;
2461
+ var VERTICAL_GAP = 60;
2462
+ function getEntityHeight(fieldCount) {
2463
+ return HEADER_HEIGHT2 + Math.max(fieldCount, 1) * FIELD_HEIGHT2;
2464
+ }
2465
+ function resolveEntityId(entity) {
2466
+ return entity.id ?? entity.name;
2467
+ }
2468
+ function computeDiagramLayout(schema) {
2469
+ const positions = /* @__PURE__ */ new Map();
2470
+ const entityIds = new Set(schema.entities.map(resolveEntityId));
2471
+ const incoming = /* @__PURE__ */ new Map();
2472
+ for (const id of entityIds) {
2473
+ incoming.set(id, /* @__PURE__ */ new Set());
2474
+ }
2475
+ for (const rel of schema.relations) {
2476
+ if (entityIds.has(rel.from) && entityIds.has(rel.to)) {
2477
+ incoming.get(rel.to).add(rel.from);
2478
+ }
2479
+ }
2480
+ const rowAssignment = /* @__PURE__ */ new Map();
2481
+ const assigned = /* @__PURE__ */ new Set();
2482
+ const queue = [];
2483
+ for (const id of entityIds) {
2484
+ if (incoming.get(id).size === 0) {
2485
+ rowAssignment.set(id, 0);
2486
+ assigned.add(id);
2487
+ queue.push(id);
2488
+ }
2489
+ }
2490
+ if (queue.length === 0 && entityIds.size > 0) {
2491
+ const firstId = resolveEntityId(schema.entities[0]);
2492
+ rowAssignment.set(firstId, 0);
2493
+ assigned.add(firstId);
2494
+ queue.push(firstId);
2495
+ }
2496
+ const outgoing = /* @__PURE__ */ new Map();
2497
+ for (const id of entityIds) {
2498
+ outgoing.set(id, []);
2499
+ }
2500
+ for (const rel of schema.relations) {
2501
+ if (entityIds.has(rel.from) && entityIds.has(rel.to)) {
2502
+ outgoing.get(rel.from).push(rel.to);
2503
+ }
2504
+ }
2505
+ let head = 0;
2506
+ while (head < queue.length) {
2507
+ const current = queue[head++];
2508
+ const currentRow = rowAssignment.get(current);
2509
+ for (const neighbor of outgoing.get(current) ?? []) {
2510
+ if (!assigned.has(neighbor)) {
2511
+ rowAssignment.set(neighbor, currentRow + 1);
2512
+ assigned.add(neighbor);
2513
+ queue.push(neighbor);
2514
+ }
2515
+ }
2516
+ }
2517
+ for (const id of entityIds) {
2518
+ if (!assigned.has(id)) {
2519
+ rowAssignment.set(id, 0);
2520
+ }
2521
+ }
2522
+ const rows = /* @__PURE__ */ new Map();
2523
+ for (const [id, row] of rowAssignment) {
2524
+ if (!rows.has(row)) {
2525
+ rows.set(row, []);
2526
+ }
2527
+ rows.get(row).push(id);
2528
+ }
2529
+ const fieldCounts = /* @__PURE__ */ new Map();
2530
+ for (const entity of schema.entities) {
2531
+ fieldCounts.set(resolveEntityId(entity), entity.fields?.length ?? 0);
2532
+ }
2533
+ const sortedRows = Array.from(rows.keys()).sort((a, b) => a - b);
2534
+ let currentY = 0;
2535
+ for (const rowIndex of sortedRows) {
2536
+ const rowEntities = rows.get(rowIndex);
2537
+ const totalWidth = rowEntities.length * ENTITY_WIDTH + (rowEntities.length - 1) * HORIZONTAL_GAP;
2538
+ const startX = -totalWidth / 2 + ENTITY_WIDTH / 2;
2539
+ let maxHeight = 0;
2540
+ for (let i = 0; i < rowEntities.length; i++) {
2541
+ const entityId = rowEntities[i];
2542
+ const x = startX + i * (ENTITY_WIDTH + HORIZONTAL_GAP) - ENTITY_WIDTH / 2;
2543
+ positions.set(entityId, { x, y: currentY });
2544
+ const height = getEntityHeight(fieldCounts.get(entityId) ?? 0);
2545
+ if (height > maxHeight) {
2546
+ maxHeight = height;
2547
+ }
2548
+ }
2549
+ currentY += maxHeight + VERTICAL_GAP;
2550
+ }
2551
+ return positions;
2552
+ }
2553
+ function DiagramRoot({
2554
+ children,
2555
+ schema,
2556
+ type = "general",
2557
+ viewport,
2558
+ defaultViewport,
2559
+ onViewportChange,
2560
+ downloadable = false,
2561
+ importable = false,
2562
+ exportFormats = ["erd"],
2563
+ onImport,
2564
+ minimap = false,
2565
+ className
2566
+ }) {
2567
+ const downloadableRef = react.useRef(downloadable);
2568
+ const importableRef = react.useRef(importable);
2569
+ const normalizedSchema = react.useMemo(() => {
2570
+ if (!schema) return { entities: [], relations: [] };
2571
+ const entities = schema.entities.map((e) => ({
2572
+ ...e,
2573
+ id: e.id ?? e.name
2574
+ }));
2575
+ const relations = schema.relations.map((r, i) => ({
2576
+ ...r,
2577
+ id: r.id ?? `rel-${i}`
2578
+ }));
2579
+ return { entities, relations };
2580
+ }, [schema]);
2581
+ const initialPositions = react.useMemo(() => {
2582
+ if (normalizedSchema.entities.length === 0) return /* @__PURE__ */ new Map();
2583
+ const layout = computeDiagramLayout(normalizedSchema);
2584
+ const positions = /* @__PURE__ */ new Map();
2585
+ for (const entity of normalizedSchema.entities) {
2586
+ if (entity.x !== void 0 && entity.y !== void 0) {
2587
+ positions.set(entity.id, { x: entity.x, y: entity.y });
2588
+ } else {
2589
+ const pos = layout.get(entity.id);
2590
+ positions.set(entity.id, pos ?? { x: 0, y: 0 });
2591
+ }
2592
+ }
2593
+ return positions;
2594
+ }, [normalizedSchema]);
2595
+ const computedDefaultViewport = react.useMemo(() => {
2596
+ if (defaultViewport) return defaultViewport;
2597
+ if (initialPositions.size === 0) return { panX: 0, panY: 0, zoom: 1 };
2598
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
2599
+ initialPositions.forEach((pos) => {
2600
+ minX = Math.min(minX, pos.x);
2601
+ minY = Math.min(minY, pos.y);
2602
+ maxX = Math.max(maxX, pos.x + 220);
2603
+ maxY = Math.max(maxY, pos.y + 200);
2604
+ });
2605
+ const padding = 40;
2606
+ const panX = -minX + padding;
2607
+ const panY = -minY + padding;
2608
+ return { panX, panY, zoom: 1 };
2609
+ }, [defaultViewport, initialPositions]);
2610
+ const [entityPositions, setEntityPositions] = react.useState(initialPositions);
2611
+ const handleEntityMove = react.useCallback((entityId, x, y) => {
2612
+ setEntityPositions((prev) => {
2613
+ const next = new Map(prev);
2614
+ next.set(entityId, { x, y });
2615
+ return next;
2616
+ });
2617
+ }, []);
2618
+ const ctx = react.useMemo(
2619
+ () => ({
2620
+ diagramType: type,
2621
+ schema: normalizedSchema,
2622
+ downloadableRef,
2623
+ importableRef,
2624
+ exportFormats,
2625
+ onImport
2626
+ }),
2627
+ [type, normalizedSchema, exportFormats, onImport]
2628
+ );
2629
+ return /* @__PURE__ */ jsxRuntime.jsx(DiagramContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx("div", { "data-react-fancy-diagram": "", className: "relative h-full w-full", children: /* @__PURE__ */ jsxRuntime.jsxs(
2630
+ Canvas,
2631
+ {
2632
+ viewport,
2633
+ defaultViewport: computedDefaultViewport,
2634
+ onViewportChange,
2635
+ showGrid: true,
2636
+ fitOnMount: true,
2637
+ className: cn("h-full w-full", className),
2638
+ children: [
2639
+ normalizedSchema.entities.map((entity) => {
2640
+ const pos = entityPositions.get(entity.id) ?? { x: 0, y: 0 };
2641
+ return /* @__PURE__ */ jsxRuntime.jsx(
2642
+ DiagramEntity,
2643
+ {
2644
+ id: entity.id,
2645
+ name: entity.name,
2646
+ x: pos.x,
2647
+ y: pos.y,
2648
+ draggable: true,
2649
+ onPositionChange: (nx, ny) => handleEntityMove(entity.id, nx, ny),
2650
+ children: entity.fields?.map((field) => /* @__PURE__ */ jsxRuntime.jsx(
2651
+ DiagramField,
2652
+ {
2653
+ name: field.name,
2654
+ type: field.type,
2655
+ primary: field.primary,
2656
+ foreign: field.foreign,
2657
+ nullable: field.nullable
2658
+ },
2659
+ field.name
2660
+ ))
2661
+ },
2662
+ entity.id
2663
+ );
2664
+ }),
2665
+ normalizedSchema.relations.map((rel) => /* @__PURE__ */ jsxRuntime.jsx(
2666
+ DiagramRelation,
2667
+ {
2668
+ from: rel.from,
2669
+ to: rel.to,
2670
+ fromField: rel.fromField,
2671
+ toField: rel.toField,
2672
+ type: rel.type,
2673
+ label: rel.label
2674
+ },
2675
+ rel.id
2676
+ )),
2677
+ children,
2678
+ /* @__PURE__ */ jsxRuntime.jsx(Canvas.Controls, {}),
2679
+ minimap && /* @__PURE__ */ jsxRuntime.jsx(Canvas.Minimap, {})
2680
+ ]
2681
+ }
2682
+ ) }) });
2683
+ }
2684
+ var Diagram = Object.assign(DiagramRoot, {
2685
+ Entity: DiagramEntity,
2686
+ Field: DiagramField,
2687
+ Relation: DiagramRelation,
2688
+ Toolbar: DiagramToolbar
2689
+ });
2690
+ function DataDiagram(props) {
2691
+ return /* @__PURE__ */ jsxRuntime.jsx(
2692
+ Diagram,
2693
+ {
2694
+ type: "erd",
2695
+ downloadable: true,
2696
+ exportFormats: ["erd", "uml"],
2697
+ minimap: true,
2698
+ ...props
2699
+ }
2700
+ );
2701
+ }
2702
+ DataDiagram.Entity = Diagram.Entity;
2703
+ DataDiagram.Field = Diagram.Field;
2704
+ DataDiagram.Relation = Diagram.Relation;
2705
+ DataDiagram.Toolbar = Diagram.Toolbar;
2706
+ DataDiagram.displayName = "DataDiagram";
2707
+ var COL_GAP = 220;
2708
+ var ROW_GAP = 140;
2709
+ function autoLayout(nodes) {
2710
+ let col = 0;
2711
+ let row = 0;
2712
+ const cols = Math.max(2, Math.ceil(Math.sqrt(nodes.length)));
2713
+ return nodes.map((n) => {
2714
+ if (n.x !== void 0 && n.y !== void 0) return n;
2715
+ const placed = { ...n, x: col * COL_GAP + 40, y: row * ROW_GAP + 40 };
2716
+ col += 1;
2717
+ if (col >= cols) {
2718
+ col = 0;
2719
+ row += 1;
2720
+ }
2721
+ return placed;
2722
+ });
2723
+ }
2724
+ function Flowchart({
2725
+ nodes,
2726
+ edges,
2727
+ routing = "manhattan",
2728
+ downloadable = false,
2729
+ minimap = false,
2730
+ className
2731
+ }) {
2732
+ const schema = react.useMemo(() => {
2733
+ const placed = autoLayout(nodes);
2734
+ return {
2735
+ entities: placed.map((n) => ({
2736
+ id: n.id,
2737
+ name: n.label,
2738
+ x: n.x,
2739
+ y: n.y
2740
+ })),
2741
+ relations: edges.map((e) => ({
2742
+ from: e.from,
2743
+ to: e.to,
2744
+ type: e.type ?? "association",
2745
+ routing,
2746
+ label: e.label
2747
+ }))
2748
+ };
2749
+ }, [nodes, edges, routing]);
2750
+ return /* @__PURE__ */ jsxRuntime.jsx(
2751
+ Diagram,
2752
+ {
2753
+ type: "flowchart",
2754
+ schema,
2755
+ downloadable,
2756
+ minimap,
2757
+ className
2758
+ }
2759
+ );
2760
+ }
2761
+ Flowchart.displayName = "Flowchart";
2762
+
2763
+ // src/components/Diagrams/mindmap.layout.ts
2764
+ var DEFAULT_RADII = [0, 220, 380, 520, 640, 740];
2765
+ function leafCount(node) {
2766
+ if (!node.children || node.children.length === 0) return 1;
2767
+ return node.children.reduce((sum, child) => sum + leafCount(child), 0);
2768
+ }
2769
+ function radiusForDepth(depth, radii) {
2770
+ if (depth < radii.length) return radii[depth];
2771
+ return radii[radii.length - 1] + (depth - radii.length + 1) * 110;
2772
+ }
2773
+ function layoutMindmap(root, radii = DEFAULT_RADII) {
2774
+ const positions = [];
2775
+ const edges = [];
2776
+ const placed = /* @__PURE__ */ new Map();
2777
+ function place(node, depth, angleStart, angleEnd) {
2778
+ const r = radiusForDepth(depth, radii);
2779
+ const angleMid = (angleStart + angleEnd) / 2;
2780
+ const x = depth === 0 ? 0 : r * Math.cos(angleMid);
2781
+ const y = depth === 0 ? 0 : r * Math.sin(angleMid);
2782
+ placed.set(node.id, { x, y });
2783
+ positions.push({ id: node.id, label: node.label, color: node.color, x, y });
2784
+ if (!node.children || node.children.length === 0) return;
2785
+ const total = leafCount(node);
2786
+ let cursor = angleStart;
2787
+ for (const child of node.children) {
2788
+ const span = leafCount(child) / total * (angleEnd - angleStart);
2789
+ place(child, depth + 1, cursor, cursor + span);
2790
+ edges.push({ from: node.id, to: child.id });
2791
+ cursor += span;
2792
+ }
2793
+ }
2794
+ place(root, 0, 0, Math.PI * 2);
2795
+ const PADDING2 = 100;
2796
+ const xs = positions.map((p) => p.x);
2797
+ const ys = positions.map((p) => p.y);
2798
+ const minX = Math.min(...xs);
2799
+ const minY = Math.min(...ys);
2800
+ const maxX = Math.max(...xs);
2801
+ const maxY = Math.max(...ys);
2802
+ const shifted = positions.map((p) => ({
2803
+ ...p,
2804
+ x: p.x - minX + PADDING2,
2805
+ y: p.y - minY + PADDING2
2806
+ }));
2807
+ return {
2808
+ nodes: shifted,
2809
+ edges,
2810
+ width: maxX - minX + PADDING2 * 2,
2811
+ height: maxY - minY + PADDING2 * 2
2812
+ };
2813
+ }
2814
+ function Mindmap({
2815
+ root,
2816
+ radii,
2817
+ downloadable = false,
2818
+ minimap = false,
2819
+ className
2820
+ }) {
2821
+ const schema = react.useMemo(() => {
2822
+ const layout = layoutMindmap(root, radii);
2823
+ return {
2824
+ entities: layout.nodes.map((n) => ({
2825
+ id: n.id,
2826
+ name: n.label,
2827
+ x: n.x,
2828
+ y: n.y
2829
+ })),
2830
+ relations: layout.edges.map((e) => ({
2831
+ from: e.from,
2832
+ to: e.to,
2833
+ type: "association",
2834
+ routing: "bezier",
2835
+ toMarker: "none",
2836
+ fromMarker: "none"
2837
+ }))
2838
+ };
2839
+ }, [root, radii]);
2840
+ return /* @__PURE__ */ jsxRuntime.jsx(
2841
+ Diagram,
2842
+ {
2843
+ type: "general",
2844
+ schema,
2845
+ downloadable,
2846
+ minimap,
2847
+ className
2848
+ }
2849
+ );
2850
+ }
2851
+ Mindmap.displayName = "Mindmap";
2852
+
2853
+ // src/components/Diagrams/orgchart.layout.ts
2854
+ var COL_WIDTH = 200;
2855
+ var ROW_HEIGHT = 140;
2856
+ var PADDING = 60;
2857
+ var slotCursor = 0;
2858
+ function size(node, depth) {
2859
+ if (!node.children || node.children.length === 0) {
2860
+ const startSlot2 = slotCursor++;
2861
+ return { node, depth, startSlot: startSlot2, span: 1, centerSlot: startSlot2, children: [] };
2862
+ }
2863
+ const startSlot = slotCursor;
2864
+ const sized = node.children.map((c) => size(c, depth + 1));
2865
+ const span = sized.reduce((sum, s) => sum + s.span, 0);
2866
+ const centerSlot = (sized[0].centerSlot + sized[sized.length - 1].centerSlot) / 2;
2867
+ return { node, depth, startSlot, span, centerSlot, children: sized };
2868
+ }
2869
+ function flatten2(s, out, edges) {
2870
+ out.push({
2871
+ id: s.node.id,
2872
+ label: s.node.label,
2873
+ color: s.node.color,
2874
+ x: s.centerSlot * COL_WIDTH + PADDING,
2875
+ y: s.depth * ROW_HEIGHT + PADDING
2876
+ });
2877
+ for (const child of s.children) {
2878
+ edges.push({ from: s.node.id, to: child.node.id });
2879
+ flatten2(child, out, edges);
2880
+ }
2881
+ }
2882
+ function layoutOrgChart(root) {
2883
+ slotCursor = 0;
2884
+ const sized = size(root, 0);
2885
+ const nodes = [];
2886
+ const edges = [];
2887
+ flatten2(sized, nodes, edges);
2888
+ const xs = nodes.map((n) => n.x);
2889
+ const ys = nodes.map((n) => n.y);
2890
+ return {
2891
+ nodes,
2892
+ edges,
2893
+ width: Math.max(...xs) + PADDING + COL_WIDTH,
2894
+ height: Math.max(...ys) + PADDING + ROW_HEIGHT
2895
+ };
2896
+ }
2897
+ function OrgChart({
2898
+ root,
2899
+ downloadable = false,
2900
+ minimap = false,
2901
+ className
2902
+ }) {
2903
+ const schema = react.useMemo(() => {
2904
+ const layout = layoutOrgChart(root);
2905
+ return {
2906
+ entities: layout.nodes.map((n) => ({
2907
+ id: n.id,
2908
+ name: n.label,
2909
+ x: n.x,
2910
+ y: n.y
2911
+ })),
2912
+ relations: layout.edges.map((e) => ({
2913
+ from: e.from,
2914
+ to: e.to,
2915
+ type: "association",
2916
+ routing: "manhattan",
2917
+ fromMarker: "none",
2918
+ toMarker: "triangle-open"
2919
+ }))
2920
+ };
2921
+ }, [root]);
2922
+ return /* @__PURE__ */ jsxRuntime.jsx(
2923
+ Diagram,
2924
+ {
2925
+ type: "general",
2926
+ schema,
2927
+ downloadable,
2928
+ minimap,
2929
+ className
2930
+ }
2931
+ );
2932
+ }
2933
+ OrgChart.displayName = "OrgChart";
2934
+
603
2935
  Object.defineProperty(exports, "BarChart", {
604
2936
  enumerable: true,
605
2937
  get: function () { return charts.BarChart; }
@@ -736,9 +3068,14 @@ Object.defineProperty(exports, "SVGRenderer", {
736
3068
  enumerable: true,
737
3069
  get: function () { return renderers.SVGRenderer; }
738
3070
  });
3071
+ exports.DataDiagram = DataDiagram;
3072
+ exports.Diagram = Diagram;
739
3073
  exports.EChart = EChart;
740
3074
  exports.EChart3D = EChart3D;
741
3075
  exports.EChartGraphic = EChartGraphic;
3076
+ exports.Flowchart = Flowchart;
3077
+ exports.Mindmap = Mindmap;
3078
+ exports.OrgChart = OrgChart;
742
3079
  exports.darkTheme = darkTheme;
743
3080
  exports.pastelTheme = pastelTheme;
744
3081
  exports.registerAll = registerAll;
@@ -746,6 +3083,7 @@ exports.registerBuiltinThemes = registerBuiltinThemes;
746
3083
  exports.registerCharts = registerCharts;
747
3084
  exports.registerComponents = registerComponents;
748
3085
  exports.registerTheme = registerTheme;
3086
+ exports.useDiagram = useDiagram;
749
3087
  exports.useECharts = useECharts;
750
3088
  exports.useResizeObserver = useResizeObserver;
751
3089
  exports.vintageTheme = vintageTheme;