@canmingir/link 1.2.17 → 1.2.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canmingir/link",
3
- "version": "1.2.17",
3
+ "version": "1.2.19",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js",
@@ -1,3 +1,5 @@
1
+ import { Box } from "@mui/material";
2
+
1
3
  import React, { useId, useLayoutEffect, useMemo, useState } from "react";
2
4
 
3
5
  const DynamicConnector = ({
@@ -18,6 +20,12 @@ const DynamicConnector = ({
18
20
  animationSpeed = 1,
19
21
  gradient = null,
20
22
  curvature = 0.5,
23
+ connectorType = "curved",
24
+ label = null,
25
+ labelStyle = {},
26
+ labelPosition = 0.5,
27
+ labelOffsetX = 0,
28
+ labelOffsetY = -10,
21
29
  }) => {
22
30
  const uniqueId = useId();
23
31
  const [dims, setDims] = useState(null);
@@ -91,6 +99,19 @@ const DynamicConnector = ({
91
99
  );
92
100
 
93
101
  const getPath = (from, to) => {
102
+ const isCornered =
103
+ connectorType === "cornered" || connectorType === "normal";
104
+
105
+ if (isCornered) {
106
+ if (isHorizontal) {
107
+ const midX = from.x + (to.x - from.x) / 2;
108
+ return `M ${from.x} ${from.y} L ${midX} ${from.y} L ${midX} ${to.y} L ${to.x} ${to.y}`;
109
+ }
110
+
111
+ const midY = from.y + (to.y - from.y) / 2;
112
+ return `M ${from.x} ${from.y} L ${from.x} ${midY} L ${to.x} ${midY} L ${to.x} ${to.y}`;
113
+ }
114
+
94
115
  const dx = Math.abs(to.x - from.x);
95
116
  const dy = Math.abs(to.y - from.y);
96
117
  const distance = Math.sqrt(dx * dx + dy * dy);
@@ -129,118 +150,169 @@ const DynamicConnector = ({
129
150
  const dashArray = getDashArray();
130
151
 
131
152
  return (
132
- <svg
133
- style={{
134
- position: "absolute",
135
- inset: 0,
136
- pointerEvents: "none",
137
- overflow: "visible",
138
- zIndex: 0,
139
- }}
140
- width="100%"
141
- height="100%"
142
- viewBox={`0 0 ${dims.w} ${dims.h}`}
143
- >
144
- <defs>
145
- {gradient && (
146
- <linearGradient
147
- id={ids.gradient}
148
- gradientUnits="userSpaceOnUse"
149
- x1={points.parent.x}
150
- y1={points.parent.y}
151
- x2={points.children[0]?.x || points.parent.x}
152
- y2={points.children[0]?.y || points.parent.y}
153
- >
154
- <stop offset="0%" stopColor={gradient.from} />
155
- <stop offset="100%" stopColor={gradient.to} />
156
- </linearGradient>
157
- )}
153
+ <>
154
+ <svg
155
+ style={{
156
+ position: "absolute",
157
+ inset: 0,
158
+ pointerEvents: "none",
159
+ overflow: "visible",
160
+ zIndex: 0,
161
+ }}
162
+ width="100%"
163
+ height="100%"
164
+ viewBox={`0 0 ${dims.w} ${dims.h}`}
165
+ >
166
+ <defs>
167
+ {gradient && (
168
+ <linearGradient
169
+ id={ids.gradient}
170
+ gradientUnits="userSpaceOnUse"
171
+ x1={points.parent.x}
172
+ y1={points.parent.y}
173
+ x2={points.children[0]?.x || points.parent.x}
174
+ y2={points.children[0]?.y || points.parent.y}
175
+ >
176
+ <stop offset="0%" stopColor={gradient.from} />
177
+ <stop offset="100%" stopColor={gradient.to} />
178
+ </linearGradient>
179
+ )}
158
180
 
159
- {showArrow && (
160
- <marker
161
- id={ids.arrow}
162
- viewBox="0 0 10 10"
163
- refX="9"
164
- refY="5"
165
- markerWidth={arrowSize}
166
- markerHeight={arrowSize}
167
- orient="auto-start-reverse"
168
- >
169
- <path
170
- d="M 0 0 L 10 5 L 0 10 z"
171
- fill={gradient ? `url(#${ids.gradient})` : stroke}
172
- />
173
- </marker>
174
- )}
181
+ {showArrow && (
182
+ <marker
183
+ id={ids.arrow}
184
+ viewBox="0 0 10 10"
185
+ refX="9"
186
+ refY="5"
187
+ markerWidth={arrowSize}
188
+ markerHeight={arrowSize}
189
+ orient="auto-start-reverse"
190
+ >
191
+ <path
192
+ d="M 0 0 L 10 5 L 0 10 z"
193
+ fill={gradient ? `url(#${ids.gradient})` : stroke}
194
+ />
195
+ </marker>
196
+ )}
175
197
 
176
- {animated && <style>{animationStyle}</style>}
177
- </defs>
178
-
179
- {points.children.map((child, i) => {
180
- const pathD = getPath(points.parent, child);
181
- const pathStroke = gradient ? `url(#${ids.gradient})` : stroke;
182
-
183
- return (
184
- <g key={i}>
185
- <path
186
- d={pathD}
187
- fill="none"
188
- stroke={pathStroke}
189
- strokeWidth={strokeWidth}
190
- strokeDasharray={animated ? "8,4" : dashArray}
191
- strokeLinecap="round"
192
- strokeLinejoin="round"
193
- markerEnd={showArrow ? `url(#${ids.arrow})` : undefined}
194
- style={{
195
- transition: "stroke 0.2s ease, stroke-width 0.2s ease",
196
- ...(animated
197
- ? {
198
- animation: `flowAnimation ${
199
- 0.5 / animationSpeed
200
- }s linear infinite`,
201
- }
202
- : {}),
203
- }}
204
- />
205
- </g>
206
- );
207
- })}
208
-
209
- {showDots && (
210
- <>
211
- <circle
212
- cx={points.parent.x}
213
- cy={points.parent.y}
214
- r={dotRadius}
215
- fill={gradient ? gradient.from : effectiveDotColor}
216
- stroke="#fff"
217
- strokeWidth={1.5}
218
- style={{
219
- filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.15))",
220
- }}
221
- />
222
-
223
- {points.children.map((child, i) => {
224
- const dotFill = gradient ? gradient.to : effectiveDotColor;
225
-
226
- return (
227
- <circle
228
- key={i}
229
- cx={child.x}
230
- cy={child.y}
231
- r={dotRadius}
232
- fill={dotFill}
233
- stroke="#fff"
234
- strokeWidth={1.5}
198
+ {animated && <style>{animationStyle}</style>}
199
+ </defs>
200
+
201
+ {points.children.map((child, i) => {
202
+ const pathD = getPath(points.parent, child);
203
+ const pathStroke = gradient ? `url(#${ids.gradient})` : stroke;
204
+
205
+ const labelX =
206
+ points.parent.x +
207
+ (child.x - points.parent.x) * labelPosition +
208
+ labelOffsetX;
209
+ const labelY =
210
+ points.parent.y +
211
+ (child.y - points.parent.y) * labelPosition +
212
+ labelOffsetY;
213
+
214
+ return (
215
+ <g key={i}>
216
+ <path
217
+ d={pathD}
218
+ fill="none"
219
+ stroke={pathStroke}
220
+ strokeWidth={strokeWidth}
221
+ strokeDasharray={animated ? "8,4" : dashArray}
222
+ strokeLinecap="round"
223
+ strokeLinejoin="round"
224
+ markerEnd={showArrow ? `url(#${ids.arrow})` : undefined}
235
225
  style={{
236
- filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.15))",
226
+ transition: "stroke 0.2s ease, stroke-width 0.2s ease",
227
+ ...(animated
228
+ ? {
229
+ animation: `flowAnimation ${
230
+ 0.5 / animationSpeed
231
+ }s linear infinite`,
232
+ }
233
+ : {}),
237
234
  }}
238
235
  />
239
- );
240
- })}
241
- </>
242
- )}
243
- </svg>
236
+ </g>
237
+ );
238
+ })}
239
+
240
+ {showDots && (
241
+ <>
242
+ <circle
243
+ cx={points.parent.x}
244
+ cy={points.parent.y}
245
+ r={dotRadius}
246
+ fill={gradient ? gradient.from : effectiveDotColor}
247
+ stroke="#fff"
248
+ strokeWidth={1.5}
249
+ style={{
250
+ filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.15))",
251
+ }}
252
+ />
253
+
254
+ {points.children.map((child, i) => {
255
+ const dotFill = gradient ? gradient.to : effectiveDotColor;
256
+
257
+ return (
258
+ <circle
259
+ key={i}
260
+ cx={child.x}
261
+ cy={child.y}
262
+ r={dotRadius}
263
+ fill={dotFill}
264
+ stroke="#fff"
265
+ strokeWidth={1.5}
266
+ style={{
267
+ filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.15))",
268
+ }}
269
+ />
270
+ );
271
+ })}
272
+ </>
273
+ )}
274
+ </svg>
275
+
276
+ {label &&
277
+ points.children.map((child, i) => {
278
+ const labelX =
279
+ points.parent.x +
280
+ (child.x - points.parent.x) * labelPosition +
281
+ labelOffsetX;
282
+ const labelY =
283
+ points.parent.y +
284
+ (child.y - points.parent.y) * labelPosition +
285
+ labelOffsetY;
286
+
287
+ return (
288
+ <Box
289
+ key={`label-${i}`}
290
+ sx={{
291
+ position: "absolute",
292
+ left: labelX,
293
+ top: labelY,
294
+ transform: "translate(-50%, -50%)",
295
+ px: 1,
296
+ py: 0.5,
297
+ borderRadius: 1,
298
+ backgroundColor: "background.paper",
299
+ border: 1,
300
+ borderColor: labelStyle.color || stroke,
301
+ fontSize: labelStyle.fontSize || "12px",
302
+ fontWeight: labelStyle.fontWeight || 600,
303
+ color: labelStyle.textColor || stroke,
304
+ pointerEvents: "none",
305
+ userSelect: "none",
306
+ zIndex: 10,
307
+ boxShadow: 1,
308
+ whiteSpace: "nowrap",
309
+ }}
310
+ >
311
+ {label}
312
+ </Box>
313
+ );
314
+ })}
315
+ </>
244
316
  );
245
317
  };
246
318
 
@@ -38,6 +38,15 @@ export const useNodeStyle = ({ node, type, variant, style, plugin }) => {
38
38
  }) || {};
39
39
  }
40
40
 
41
+ let pluginEdgeTokens = {};
42
+ if (resolvedPlugin && typeof resolvedPlugin.edge === "function") {
43
+ pluginEdgeTokens =
44
+ resolvedPlugin.edge({
45
+ node,
46
+ style: styleTokens,
47
+ }) || {};
48
+ }
49
+
41
50
  const rawNodeStyle = {
42
51
  ...baseStyle,
43
52
  ...variantTokens,
@@ -47,9 +56,14 @@ export const useNodeStyle = ({ node, type, variant, style, plugin }) => {
47
56
 
48
57
  const nodeStyle = applySemanticTokens(rawNodeStyle, baseStyle);
49
58
 
59
+ const edgeStyle = {
60
+ ...pluginEdgeTokens,
61
+ };
62
+
50
63
  return {
51
64
  baseStyle,
52
65
  nodeStyle,
66
+ edgeStyle,
53
67
  plugin: resolvedPlugin,
54
68
  };
55
69
  }, [node, type, variant, style, plugin]);
@@ -24,6 +24,7 @@ const FlowNodeView = ({
24
24
  const {
25
25
  baseStyle,
26
26
  nodeStyle,
27
+ edgeStyle,
27
28
  plugin: _plugin,
28
29
  } = useNodeStyle({
29
30
  node,
@@ -56,14 +57,33 @@ const FlowNodeView = ({
56
57
  animationSpeed = baseStyle.animationSpeed ?? 1,
57
58
  gradient = baseStyle.gradient ?? null,
58
59
  curvature = baseStyle.curvature ?? 0.5,
60
+ connectorType = baseStyle.connectorType ?? "curved",
59
61
  selectionColor = baseStyle.selectionColor ?? "#64748b",
60
62
  } = nodeStyle;
61
63
 
64
+ const edgeProps = {
65
+ lineColor: edgeStyle.lineColor ?? lineColor,
66
+ lineWidth: edgeStyle.lineWidth ?? lineWidth,
67
+ lineStyle: edgeStyle.lineStyle ?? lineStyle,
68
+ showDots: edgeStyle.showDots ?? showDots,
69
+ dotRadius: edgeStyle.dotRadius ?? dotRadius,
70
+ dotColor: edgeStyle.dotColor ?? dotColor,
71
+ showArrow: edgeStyle.showArrow ?? showArrow,
72
+ arrowSize: edgeStyle.arrowSize ?? arrowSize,
73
+ animated: edgeStyle.animated ?? animated,
74
+ animationSpeed: edgeStyle.animationSpeed ?? animationSpeed,
75
+ gradient: edgeStyle.gradient ?? gradient,
76
+ curvature: edgeStyle.curvature ?? curvature,
77
+ connectorType: edgeStyle.connectorType ?? connectorType,
78
+ };
79
+
62
80
  const isHorizontal = direction === "horizontal";
63
81
 
64
- const strokeWidth = toPxNumber(lineWidth, 1.5);
82
+ const strokeWidth = toPxNumber(edgeProps.lineWidth, 1.5);
65
83
  const dashStyle =
66
- lineStyle === "dashed" || lineStyle === "dotted" ? lineStyle : "solid";
84
+ edgeProps.lineStyle === "dashed" || edgeProps.lineStyle === "dotted"
85
+ ? edgeProps.lineStyle
86
+ : "solid";
67
87
 
68
88
  const containerRef = useRef(null);
69
89
  const parentRef = useRef(null);
@@ -152,25 +172,89 @@ const FlowNodeView = ({
152
172
 
153
173
  {hasChildren && (
154
174
  <>
155
- <DynamicConnector
156
- containerEl={containerRef.current}
157
- parentEl={parentRef.current}
158
- childEls={childElList}
159
- stroke={lineColor}
160
- strokeWidth={strokeWidth}
161
- lineStyle={dashStyle}
162
- tick={connectorTick}
163
- orientation={direction}
164
- showDots={showDots}
165
- dotRadius={dotRadius}
166
- dotColor={dotColor}
167
- showArrow={showArrow}
168
- arrowSize={arrowSize}
169
- animated={animated}
170
- animationSpeed={animationSpeed}
171
- gradient={gradient}
172
- curvature={curvature}
173
- />
175
+ {node.children.map((child, index) => {
176
+ let childEdgeProps = { ...edgeProps };
177
+ if (_plugin && typeof _plugin.edge === "function") {
178
+ const childSpecificStyle = _plugin.edge({
179
+ node,
180
+ child,
181
+ style: nodeStyle,
182
+ });
183
+ if (childSpecificStyle) {
184
+ childEdgeProps = {
185
+ lineColor:
186
+ childSpecificStyle.lineColor ?? childEdgeProps.lineColor,
187
+ lineWidth:
188
+ childSpecificStyle.lineWidth ?? childEdgeProps.lineWidth,
189
+ lineStyle:
190
+ childSpecificStyle.lineStyle ?? childEdgeProps.lineStyle,
191
+ showDots:
192
+ childSpecificStyle.showDots ?? childEdgeProps.showDots,
193
+ dotRadius:
194
+ childSpecificStyle.dotRadius ?? childEdgeProps.dotRadius,
195
+ dotColor:
196
+ childSpecificStyle.dotColor ?? childEdgeProps.dotColor,
197
+ showArrow:
198
+ childSpecificStyle.showArrow ?? childEdgeProps.showArrow,
199
+ arrowSize:
200
+ childSpecificStyle.arrowSize ?? childEdgeProps.arrowSize,
201
+ animated:
202
+ childSpecificStyle.animated ?? childEdgeProps.animated,
203
+ animationSpeed:
204
+ childSpecificStyle.animationSpeed ??
205
+ childEdgeProps.animationSpeed,
206
+ gradient:
207
+ childSpecificStyle.gradient ?? childEdgeProps.gradient,
208
+ curvature:
209
+ childSpecificStyle.curvature ?? childEdgeProps.curvature,
210
+ connectorType:
211
+ childSpecificStyle.connectorType ??
212
+ childEdgeProps.connectorType,
213
+ label: childSpecificStyle.label,
214
+ labelStyle: childSpecificStyle.labelStyle,
215
+ labelPosition: childSpecificStyle.labelPosition,
216
+ labelOffsetX: childSpecificStyle.labelOffsetX,
217
+ labelOffsetY: childSpecificStyle.labelOffsetY,
218
+ };
219
+ }
220
+ }
221
+
222
+ const childStrokeWidth = toPxNumber(childEdgeProps.lineWidth, 1.5);
223
+ const childDashStyle =
224
+ childEdgeProps.lineStyle === "dashed" ||
225
+ childEdgeProps.lineStyle === "dotted"
226
+ ? childEdgeProps.lineStyle
227
+ : "solid";
228
+
229
+ return (
230
+ <DynamicConnector
231
+ key={child.id}
232
+ containerEl={containerRef.current}
233
+ parentEl={parentRef.current}
234
+ childEls={[childElList[index]]}
235
+ stroke={childEdgeProps.lineColor}
236
+ strokeWidth={childStrokeWidth}
237
+ lineStyle={childDashStyle}
238
+ tick={connectorTick}
239
+ orientation={direction}
240
+ showDots={childEdgeProps.showDots}
241
+ dotRadius={childEdgeProps.dotRadius}
242
+ dotColor={childEdgeProps.dotColor}
243
+ showArrow={childEdgeProps.showArrow}
244
+ arrowSize={childEdgeProps.arrowSize}
245
+ animated={childEdgeProps.animated}
246
+ animationSpeed={childEdgeProps.animationSpeed}
247
+ gradient={childEdgeProps.gradient}
248
+ curvature={childEdgeProps.curvature}
249
+ connectorType={childEdgeProps.connectorType}
250
+ label={childEdgeProps.label}
251
+ labelStyle={childEdgeProps.labelStyle}
252
+ labelPosition={childEdgeProps.labelPosition}
253
+ labelOffsetX={childEdgeProps.labelOffsetX}
254
+ labelOffsetY={childEdgeProps.labelOffsetY}
255
+ />
256
+ );
257
+ })}
174
258
 
175
259
  <Box
176
260
  sx={{
@@ -25,6 +25,7 @@ export const getBaseStyleForVariant = (v) => {
25
25
  animationSpeed: 1,
26
26
  gradient: null,
27
27
  curvature: 0.5,
28
+ connectorType: "curved",
28
29
  };
29
30
  case "pill":
30
31
  return {
@@ -44,6 +45,7 @@ export const getBaseStyleForVariant = (v) => {
44
45
  animationSpeed: 1,
45
46
  gradient: null,
46
47
  curvature: 0.4,
48
+ connectorType: "curved",
47
49
  };
48
50
  case "n8n":
49
51
  return {
@@ -63,6 +65,7 @@ export const getBaseStyleForVariant = (v) => {
63
65
  animationSpeed: 1,
64
66
  gradient: null,
65
67
  curvature: 0.5,
68
+ connectorType: "curved",
66
69
  };
67
70
  case "simple":
68
71
  default:
@@ -83,6 +86,7 @@ export const getBaseStyleForVariant = (v) => {
83
86
  animationSpeed: 1,
84
87
  gradient: null,
85
88
  curvature: 0.5,
89
+ connectorType: "curved",
86
90
  };
87
91
  }
88
92
  };