@canmingir/link 1.2.8 → 1.2.11

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 (35) hide show
  1. package/.github/workflows/publish.yml +64 -7
  2. package/package.json +1 -1
  3. package/src/lib/Flow/connectors/DynamicConnector.jsx +247 -0
  4. package/src/lib/Flow/core/Flow.jsx +79 -0
  5. package/src/lib/Flow/core/FlowNode.jsx +68 -0
  6. package/src/lib/Flow/core/FlowViewport.jsx +259 -0
  7. package/src/lib/Flow/graph/FloatingGraph.jsx +44 -0
  8. package/src/lib/Flow/hooks/useGraphOperations.js +362 -0
  9. package/src/lib/Flow/hooks/useNodeStyle.js +56 -0
  10. package/src/lib/Flow/index.js +1 -1
  11. package/src/lib/Flow/layouts/InfoNode.jsx +115 -56
  12. package/src/lib/Flow/nodes/DefaultCard.jsx +107 -0
  13. package/src/lib/Flow/nodes/DraggableNode.jsx +162 -0
  14. package/src/lib/Flow/nodes/FlowNodeView.jsx +214 -0
  15. package/src/lib/Flow/selection/SelectionContext.jsx +259 -0
  16. package/src/lib/Flow/selection/SelectionOverlay.jsx +31 -0
  17. package/src/lib/Flow/styles.js +59 -19
  18. package/src/lib/Flow/utils/flowUtils.js +268 -0
  19. package/src/lib/index.js +1 -1
  20. package/.idea/codeStyles/Project.xml +0 -84
  21. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  22. package/.idea/copilot.data.migration.agent.xml +0 -6
  23. package/.idea/copilot.data.migration.ask.xml +0 -6
  24. package/.idea/copilot.data.migration.ask2agent.xml +0 -6
  25. package/.idea/copilot.data.migration.edit.xml +0 -6
  26. package/.idea/inspectionProfiles/Project_Default.xml +0 -6
  27. package/.idea/misc.xml +0 -5
  28. package/.idea/modules.xml +0 -8
  29. package/.idea/platform.iml +0 -9
  30. package/.idea/vcs.xml +0 -6
  31. package/src/lib/Flow/DraggableNode.jsx +0 -62
  32. package/src/lib/Flow/DynamicConnector.jsx +0 -176
  33. package/src/lib/Flow/Flow.jsx +0 -40
  34. package/src/lib/Flow/FlowNode.jsx +0 -371
  35. package/src/lib/Flow/flowUtils.js +0 -111
@@ -0,0 +1,268 @@
1
+ export function assertLinkedGraph(data) {
2
+ if (!data || typeof data !== "object") {
3
+ throw new Error(
4
+ "FlowChart expected a linked graph object: { nodes, roots? }."
5
+ );
6
+ }
7
+ const { nodes, roots } = data;
8
+ if (!nodes || typeof nodes !== "object") {
9
+ throw new Error("FlowChart expected data.nodes to be an object.");
10
+ }
11
+
12
+ let useRoots = Array.isArray(roots) ? [...roots] : null;
13
+ if (!useRoots || useRoots.length === 0) {
14
+ useRoots = Object.keys(nodes).filter((id) => !nodes[id]?.previous);
15
+ }
16
+ if (useRoots.length === 0) {
17
+ const first = Object.keys(nodes)[0];
18
+ if (first) useRoots = [first];
19
+ }
20
+
21
+ for (const r of useRoots) {
22
+ if (!nodes[r]) throw new Error(`Root id "${r}" not found in data.nodes.`);
23
+ }
24
+
25
+ return { nodesById: nodes, roots: useRoots };
26
+ }
27
+
28
+ export function buildTreeFromLinked(rootId, nodesById) {
29
+ if (!rootId || !nodesById?.[rootId]) return null;
30
+ const seen = new Set();
31
+
32
+ const cloneNode = (node) => {
33
+ if (!node) return null;
34
+ const { next, previous, children, ...rest } = node;
35
+ return { ...rest, id: node.id, previous, next, children: [] };
36
+ };
37
+
38
+ const dfs = (id) => {
39
+ if (!id || seen.has(id) || !nodesById[id]) return null;
40
+ seen.add(id);
41
+
42
+ const node = nodesById[id];
43
+ const out = cloneNode(node);
44
+
45
+ const nextArr = Array.isArray(node.next)
46
+ ? node.next
47
+ : node.next != null
48
+ ? [node.next]
49
+ : [];
50
+
51
+ for (const nxt of nextArr) {
52
+ const nextId = typeof nxt === "string" ? nxt : nxt?.id;
53
+ if (!nextId || !nodesById[nextId]) continue;
54
+
55
+ const target = nodesById[nextId];
56
+ if (target.previous == null || target.previous === id) {
57
+ const built = dfs(nextId);
58
+ if (built) out.children.push(built);
59
+ }
60
+ }
61
+ return out;
62
+ };
63
+
64
+ return dfs(rootId);
65
+ }
66
+
67
+ export const getContentParts = (n) => {
68
+ const entries = Object.entries(n).filter(
69
+ ([key]) =>
70
+ key !== "children" && key !== "id" && key !== "previous" && key !== "next"
71
+ );
72
+
73
+ if (entries.length === 0) {
74
+ return {
75
+ title: "(empty)",
76
+ subtitle: null,
77
+ metaEntries: [],
78
+ };
79
+ }
80
+
81
+ const preferredTitleKeys = ["label", "title", "name"];
82
+ const titleEntry =
83
+ entries.find(([key]) => preferredTitleKeys.includes(key)) || entries[0];
84
+ const [titleKey, rawTitle] = titleEntry;
85
+ const title = String(rawTitle);
86
+ let remaining = entries.filter(([key]) => key !== titleKey);
87
+
88
+ const preferredSubtitleKeys = ["description", "role", "type", "status"];
89
+ const subtitleEntry =
90
+ remaining.find(([key]) => preferredSubtitleKeys.includes(key)) || null;
91
+
92
+ let subtitle = null;
93
+ if (subtitleEntry) {
94
+ const [subtitleKey, raw] = subtitleEntry;
95
+ subtitle = String(raw);
96
+ remaining = remaining.filter(([key]) => key !== subtitleKey);
97
+ }
98
+
99
+ const metaEntries = remaining
100
+ .filter(([, value]) => {
101
+ const t = typeof value;
102
+ return (
103
+ (t === "string" || t === "number" || t === "boolean") &&
104
+ value !== "" &&
105
+ value !== null
106
+ );
107
+ })
108
+ .map(([k, v]) => [k, v]);
109
+
110
+ return { title, subtitle, metaEntries };
111
+ };
112
+
113
+ export const toNextArray = (next) => {
114
+ if (!next) return [];
115
+ return Array.isArray(next) ? next : [next];
116
+ };
117
+
118
+ export const setNextProperty = (node, nextIds) => {
119
+ if (!nextIds || nextIds.length === 0) {
120
+ delete node.next;
121
+ } else if (nextIds.length === 1) {
122
+ node.next = nextIds[0];
123
+ } else {
124
+ node.next = nextIds;
125
+ }
126
+ };
127
+
128
+ export const addToNext = (node, childIds) => {
129
+ const current = toNextArray(node.next);
130
+ const newIds = childIds.filter((id) => !current.includes(id));
131
+ setNextProperty(node, [...current, ...newIds]);
132
+ };
133
+
134
+ export const removeFromNext = (node, childIds) => {
135
+ const removeSet = new Set(childIds);
136
+ const filtered = toNextArray(node.next).filter((id) => !removeSet.has(id));
137
+ setNextProperty(node, filtered);
138
+ };
139
+
140
+ export const cleanupReferences = (nodes, removedIds) => {
141
+ const removeSet = new Set(removedIds);
142
+ Object.values(nodes).forEach((node) => {
143
+ if (removeSet.has(node.next)) {
144
+ delete node.next;
145
+ } else if (Array.isArray(node.next)) {
146
+ const filtered = node.next.filter((n) => !removeSet.has(n));
147
+ setNextProperty(node, filtered);
148
+ }
149
+ if (removeSet.has(node.previous)) {
150
+ delete node.previous;
151
+ }
152
+ });
153
+ };
154
+
155
+ export const hexToRgba = (hex, alpha) => {
156
+ const r = parseInt(hex.slice(1, 3), 16);
157
+ const g = parseInt(hex.slice(3, 5), 16);
158
+ const b = parseInt(hex.slice(5, 7), 16);
159
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
160
+ };
161
+
162
+ export const buildDetachedTree = (rootId, nodesById) => {
163
+ if (!rootId || !nodesById?.[rootId]) return null;
164
+ const seen = new Set();
165
+
166
+ const buildNode = (id) => {
167
+ if (!id || seen.has(id) || !nodesById[id]) return null;
168
+ seen.add(id);
169
+
170
+ const node = nodesById[id];
171
+ const { next, previous, ...rest } = node;
172
+ const result = { ...rest, id, children: [] };
173
+
174
+ const nextIds = Array.isArray(next) ? next : next != null ? [next] : [];
175
+
176
+ nextIds.forEach((nxt) => {
177
+ const nextId = typeof nxt === "string" ? nxt : nxt?.id;
178
+ if (!nextId || !nodesById[nextId]) return;
179
+
180
+ const child = buildNode(nextId);
181
+ if (child) result.children.push(child);
182
+ });
183
+
184
+ return result;
185
+ };
186
+
187
+ return buildNode(rootId);
188
+ };
189
+
190
+ export const getSelectedInStructure = (structure, selectedIds) =>
191
+ selectedIds.filter((id) => structure.nodes?.[id]);
192
+
193
+ export const getRootsToConnect = (structure, selectedIds) => {
194
+ const selectedSet = new Set(selectedIds);
195
+ const roots = [];
196
+
197
+ selectedIds.forEach((id) => {
198
+ let current = structure.nodes[id];
199
+ if (!current) return;
200
+
201
+ let rootId = id;
202
+
203
+ while (current.previous && structure.nodes[current.previous]) {
204
+ if (selectedSet.has(current.previous)) {
205
+ rootId = current.previous;
206
+ current = structure.nodes[current.previous];
207
+ } else break;
208
+ }
209
+
210
+ if (!current.previous && !roots.includes(rootId)) {
211
+ roots.push(rootId);
212
+ }
213
+ });
214
+
215
+ if (roots.length > 0) return roots;
216
+
217
+ return selectedIds.filter((id) => {
218
+ const node = structure.nodes[id];
219
+ return node && (!node.previous || !structure.nodes[node.previous]);
220
+ });
221
+ };
222
+
223
+ export const collectSubtree = (structure, roots, selectedIds) => {
224
+ const selectedSet = new Set(selectedIds);
225
+ const collected = new Set();
226
+
227
+ const dfs = (id) => {
228
+ if (collected.has(id) || !structure.nodes[id]) return;
229
+ collected.add(id);
230
+
231
+ toNextArray(structure.nodes[id].next).forEach((childId) => {
232
+ if (structure.nodes[childId] && selectedSet.has(childId)) {
233
+ dfs(childId);
234
+ }
235
+ });
236
+ };
237
+
238
+ roots.forEach(dfs);
239
+ return collected;
240
+ };
241
+
242
+ export const splitFloatingStructure = (structure, removedIds) => {
243
+ const remainingNodes = {};
244
+
245
+ Object.entries(structure.nodes).forEach(([id, node]) => {
246
+ if (!removedIds.has(id)) {
247
+ remainingNodes[id] = node;
248
+ }
249
+ });
250
+
251
+ cleanupReferences(remainingNodes, [...removedIds]);
252
+
253
+ const remainingRoots =
254
+ structure.roots?.filter((r) => !removedIds.has(r)) ?? [];
255
+
256
+ const roots =
257
+ remainingRoots.length > 0
258
+ ? remainingRoots
259
+ : Object.keys(remainingNodes).filter(
260
+ (id) => !remainingNodes[id].previous
261
+ );
262
+
263
+ return {
264
+ ...structure,
265
+ nodes: remainingNodes,
266
+ roots: [...new Set(roots)],
267
+ };
268
+ };
package/src/lib/index.js CHANGED
@@ -22,7 +22,7 @@ export { default as TableSelectedAction } from "./TableSelectedAction/TableSelec
22
22
  export { default as useTable } from "./useTable/useTable";
23
23
  export { default as useChart } from "./useChart/useChart";
24
24
 
25
- export { default as Flow } from "./Flow/Flow";
25
+ export { default as Flow } from "./Flow/core/Flow";
26
26
 
27
27
  export {
28
28
  HeaderCard,
@@ -1,84 +0,0 @@
1
- <component name="ProjectCodeStyleConfiguration">
2
- <code_scheme name="Project" version="173">
3
- <HTMLCodeStyleSettings>
4
- <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
5
- </HTMLCodeStyleSettings>
6
- <JSCodeStyleSettings version="0">
7
- <option name="FORCE_SEMICOLON_STYLE" value="true" />
8
- <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
9
- <option name="FORCE_QUOTE_STYlE" value="true" />
10
- <option name="ENFORCE_TRAILING_COMMA" value="Remove" />
11
- <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
12
- <option name="SPACES_WITHIN_IMPORTS" value="true" />
13
- <option name="CHAINED_CALL_DOT_ON_NEW_LINE" value="false" />
14
- </JSCodeStyleSettings>
15
- <JetCodeStyleSettings>
16
- <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
17
- </JetCodeStyleSettings>
18
- <TypeScriptCodeStyleSettings version="0">
19
- <option name="FORCE_SEMICOLON_STYLE" value="true" />
20
- <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
21
- <option name="FORCE_QUOTE_STYlE" value="true" />
22
- <option name="ENFORCE_TRAILING_COMMA" value="Remove" />
23
- <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
24
- <option name="SPACES_WITHIN_IMPORTS" value="true" />
25
- </TypeScriptCodeStyleSettings>
26
- <VueCodeStyleSettings>
27
- <option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
28
- <option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
29
- </VueCodeStyleSettings>
30
- <codeStyleSettings language="HTML">
31
- <option name="SOFT_MARGINS" value="80" />
32
- <indentOptions>
33
- <option name="INDENT_SIZE" value="2" />
34
- <option name="CONTINUATION_INDENT_SIZE" value="2" />
35
- <option name="TAB_SIZE" value="2" />
36
- </indentOptions>
37
- </codeStyleSettings>
38
- <codeStyleSettings language="JSON">
39
- <indentOptions>
40
- <option name="TAB_SIZE" value="2" />
41
- </indentOptions>
42
- </codeStyleSettings>
43
- <codeStyleSettings language="JavaScript">
44
- <option name="RIGHT_MARGIN" value="80" />
45
- <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
46
- <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
47
- <option name="ALIGN_MULTILINE_FOR" value="false" />
48
- <option name="CALL_PARAMETERS_WRAP" value="1" />
49
- <option name="METHOD_PARAMETERS_WRAP" value="1" />
50
- <option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
51
- <option name="METHOD_CALL_CHAIN_WRAP" value="5" />
52
- <option name="BINARY_OPERATION_WRAP" value="1" />
53
- <option name="TERNARY_OPERATION_WRAP" value="5" />
54
- <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
55
- <option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
56
- <option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
57
- <option name="ARRAY_INITIALIZER_WRAP" value="5" />
58
- <option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
59
- <option name="SOFT_MARGINS" value="80" />
60
- <indentOptions>
61
- <option name="INDENT_SIZE" value="2" />
62
- <option name="CONTINUATION_INDENT_SIZE" value="2" />
63
- <option name="TAB_SIZE" value="2" />
64
- </indentOptions>
65
- </codeStyleSettings>
66
- <codeStyleSettings language="TypeScript">
67
- <option name="SOFT_MARGINS" value="80" />
68
- <indentOptions>
69
- <option name="INDENT_SIZE" value="2" />
70
- <option name="CONTINUATION_INDENT_SIZE" value="2" />
71
- <option name="TAB_SIZE" value="2" />
72
- </indentOptions>
73
- </codeStyleSettings>
74
- <codeStyleSettings language="Vue">
75
- <option name="SOFT_MARGINS" value="80" />
76
- <indentOptions>
77
- <option name="CONTINUATION_INDENT_SIZE" value="2" />
78
- </indentOptions>
79
- </codeStyleSettings>
80
- <codeStyleSettings language="kotlin">
81
- <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
82
- </codeStyleSettings>
83
- </code_scheme>
84
- </component>
@@ -1,5 +0,0 @@
1
- <component name="ProjectCodeStyleConfiguration">
2
- <state>
3
- <option name="USE_PER_PROJECT_SETTINGS" value="true" />
4
- </state>
5
- </component>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="AgentMigrationStateService">
4
- <option name="migrationStatus" value="COMPLETED" />
5
- </component>
6
- </project>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="AskMigrationStateService">
4
- <option name="migrationStatus" value="COMPLETED" />
5
- </component>
6
- </project>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="Ask2AgentMigrationStateService">
4
- <option name="migrationStatus" value="COMPLETED" />
5
- </component>
6
- </project>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="EditMigrationStateService">
4
- <option name="migrationStatus" value="COMPLETED" />
5
- </component>
6
- </project>
@@ -1,6 +0,0 @@
1
- <component name="InspectionProjectProfileManager">
2
- <profile version="1.0">
3
- <option name="myName" value="Project Default" />
4
- <inspection_tool class="JpaEntityGraphsInspection" enabled="true" level="ERROR" enabled_by_default="true" />
5
- </profile>
6
- </component>
package/.idea/misc.xml DELETED
@@ -1,5 +0,0 @@
1
- <project version="4">
2
- <component name="ProjectRootManager">
3
- <output url="file://$PROJECT_DIR$/out" />
4
- </component>
5
- </project>
package/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/platform.iml" filepath="$PROJECT_DIR$/.idea/platform.iml" />
6
- </modules>
7
- </component>
8
- </project>
@@ -1,9 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <module type="JAVA_MODULE" version="4">
3
- <component name="NewModuleRootManager" inherit-compiler-output="true">
4
- <exclude-output />
5
- <content url="file://$MODULE_DIR$" />
6
- <orderEntry type="inheritedJdk" />
7
- <orderEntry type="sourceFolder" forTests="false" />
8
- </component>
9
- </module>
package/.idea/vcs.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="" vcs="Git" />
5
- </component>
6
- </project>
@@ -1,62 +0,0 @@
1
- import { Box } from "@mui/material";
2
-
3
- import React, { useRef, useState } from "react";
4
-
5
- const DraggableNode = ({ children, registerRef, onDrag }) => {
6
- const [offset, setOffset] = useState({ x: 0, y: 0 });
7
- const localRef = useRef(null);
8
-
9
- const setRef = (el) => {
10
- localRef.current = el;
11
- if (registerRef) registerRef(el);
12
- };
13
-
14
- const handleMouseDown = (e) => {
15
- if (e.button !== 0) return;
16
- e.stopPropagation();
17
-
18
- const startX = e.clientX;
19
- const startY = e.clientY;
20
- const startOffset = { ...offset };
21
-
22
- const onMove = (ev) => {
23
- const dx = ev.clientX - startX;
24
- const dy = ev.clientY - startY;
25
- setOffset({
26
- x: startOffset.x + dx,
27
- y: startOffset.y + dy,
28
- });
29
- if (onDrag) onDrag();
30
- };
31
-
32
- const onUp = () => {
33
- window.removeEventListener("mousemove", onMove);
34
- window.removeEventListener("mouseup", onUp);
35
- };
36
-
37
- window.addEventListener("mousemove", onMove);
38
- window.addEventListener("mouseup", onUp);
39
- };
40
-
41
- return (
42
- <Box
43
- ref={setRef}
44
- onMouseDown={handleMouseDown}
45
- sx={{
46
- display: "inline-flex",
47
- flexDirection: "column",
48
- alignItems: "center",
49
- position: "relative",
50
- transform: `translate(${offset.x}px, ${offset.y}px)`,
51
- cursor: "grab",
52
- "&:active": {
53
- cursor: "grabbing",
54
- },
55
- }}
56
- >
57
- {children}
58
- </Box>
59
- );
60
- };
61
-
62
- export default DraggableNode;
@@ -1,176 +0,0 @@
1
- import React, { useLayoutEffect, useState } from "react";
2
-
3
- const DynamicConnector = ({
4
- containerEl,
5
- parentEl,
6
- childEls,
7
- stroke,
8
- strokeWidth,
9
- lineStyle,
10
- connectorType = "default",
11
- tick = 0,
12
- }) => {
13
- const [dims, setDims] = useState(null);
14
- const [points, setPoints] = useState({
15
- parent: null,
16
- children: [],
17
- yMid: null,
18
- });
19
-
20
- useLayoutEffect(() => {
21
- if (!containerEl || !parentEl || !childEls?.length) return;
22
-
23
- const update = () => {
24
- const cRect = containerEl.getBoundingClientRect();
25
- const pRect = parentEl.getBoundingClientRect();
26
-
27
- const parent = {
28
- x: pRect.left + pRect.width / 2 - cRect.left,
29
- y: pRect.bottom - cRect.top,
30
- };
31
-
32
- const children = childEls.map((el) => {
33
- if (!el) return { x: parent.x, y: parent.y };
34
- const r = el.getBoundingClientRect();
35
- return {
36
- x: r.left + r.width / 2 - cRect.left,
37
- y: r.top - cRect.top,
38
- };
39
- });
40
-
41
- const firstTop = Math.min(...children.map((c) => c.y));
42
- const yMid =
43
- parent.y + Math.max(12, Math.min(24, (firstTop - parent.y) * 0.4));
44
-
45
- setPoints({ parent, children, yMid });
46
- setDims({ w: cRect.width, h: cRect.height });
47
- };
48
-
49
- update();
50
-
51
- const ro = new ResizeObserver(update);
52
- ro.observe(containerEl);
53
- ro.observe(parentEl);
54
- childEls.forEach((el) => el && ro.observe(el));
55
-
56
- return () => ro.disconnect();
57
- }, [containerEl, parentEl, childEls, tick]);
58
-
59
- if (!dims || !points.parent || !points.children.length) return null;
60
-
61
- const dash =
62
- lineStyle === "dashed"
63
- ? `${strokeWidth * 3},${strokeWidth * 2}`
64
- : lineStyle === "dotted"
65
- ? `${strokeWidth},${strokeWidth * 1.5}`
66
- : undefined;
67
-
68
- const onlyOne = points.children.length === 1;
69
-
70
- const svgProps = {
71
- style: {
72
- position: "absolute",
73
- inset: 0,
74
- pointerEvents: "none",
75
- overflow: "visible",
76
- },
77
- width: "100%",
78
- height: "100%",
79
- viewBox: `0 0 ${dims.w} ${dims.h}`,
80
- };
81
-
82
- if (connectorType === "curved" || connectorType === "n8n") {
83
- const createN8nPath = (from, to) => {
84
- const v = Math.max(32, Math.abs(to.y - from.y) * 0.35);
85
- const h = Math.max(24, Math.abs(to.x - from.x) * 0.35);
86
-
87
- const c1 = {
88
- x: to.x > from.x ? from.x + h : from.x - h,
89
- y: from.y + v,
90
- };
91
- const c2 = {
92
- x: to.x > from.x ? to.x - h : to.x + h,
93
- y: to.y - v,
94
- };
95
-
96
- return `M ${from.x} ${from.y} C ${c1.x} ${c1.y} ${c2.x} ${c2.y} ${to.x} ${to.y}`;
97
- };
98
-
99
- return (
100
- <svg {...svgProps}>
101
- {points.children.map((child, i) => (
102
- <path
103
- key={i}
104
- d={createN8nPath(points.parent, child)}
105
- fill="none"
106
- stroke={stroke}
107
- strokeWidth={strokeWidth}
108
- strokeDasharray={dash}
109
- strokeLinecap="round"
110
- strokeLinejoin="round"
111
- />
112
- ))}
113
- </svg>
114
- );
115
- }
116
-
117
- return (
118
- <svg {...svgProps}>
119
- {onlyOne ? (
120
- <line
121
- x1={points.parent.x}
122
- y1={points.parent.y}
123
- x2={points.children[0].x}
124
- y2={points.children[0].y}
125
- stroke={stroke}
126
- strokeWidth={strokeWidth}
127
- strokeDasharray={dash}
128
- />
129
- ) : (
130
- <>
131
- <line
132
- x1={points.parent.x}
133
- y1={points.parent.y}
134
- x2={points.parent.x}
135
- y2={points.yMid}
136
- stroke={stroke}
137
- strokeWidth={strokeWidth}
138
- strokeDasharray={dash}
139
- />
140
-
141
- {(() => {
142
- const xs = points.children.map((c) => c.x);
143
- const xMin = Math.min(...xs, points.parent.x);
144
- const xMax = Math.max(...xs, points.parent.x);
145
- return (
146
- <line
147
- x1={xMin}
148
- y1={points.yMid}
149
- x2={xMax}
150
- y2={points.yMid}
151
- stroke={stroke}
152
- strokeWidth={strokeWidth}
153
- strokeDasharray={dash}
154
- />
155
- );
156
- })()}
157
-
158
- {points.children.map((c, i) => (
159
- <line
160
- key={i}
161
- x1={c.x}
162
- y1={points.yMid}
163
- x2={c.x}
164
- y2={c.y}
165
- stroke={stroke}
166
- strokeWidth={strokeWidth}
167
- strokeDasharray={dash}
168
- />
169
- ))}
170
- </>
171
- )}
172
- </svg>
173
- );
174
- };
175
-
176
- export default DynamicConnector;