avoid-nodes-edge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +501 -0
- package/dist/chunk-TO34Q3ID.cjs +87 -0
- package/dist/chunk-TO34Q3ID.cjs.map +1 -0
- package/dist/chunk-VPHZVUPR.js +87 -0
- package/dist/chunk-VPHZVUPR.js.map +1 -0
- package/dist/edge.cjs +235 -0
- package/dist/edge.cjs.map +1 -0
- package/dist/edge.d.cts +34 -0
- package/dist/edge.d.ts +34 -0
- package/dist/edge.js +235 -0
- package/dist/edge.js.map +1 -0
- package/dist/index.cjs +558 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +202 -0
- package/dist/index.d.ts +202 -0
- package/dist/index.js +558 -0
- package/dist/index.js.map +1 -0
- package/dist/workers/avoid-router.worker.js +348 -0
- package/dist/workers/avoid-router.worker.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store.ts","../src/constants.ts","../src/useAvoidNodesPath.ts"],"sourcesContent":["import { create } from \"zustand\";\nimport type { AvoidRoute } from \"./router\";\n\nexport interface AvoidRoutesState {\n loaded: boolean;\n routes: Record<string, AvoidRoute>;\n setLoaded: (loaded: boolean) => void;\n setRoutes: (routes: Record<string, AvoidRoute>) => void;\n}\n\nexport const useAvoidRoutesStore = create<AvoidRoutesState>((set) => ({\n loaded: false,\n routes: {},\n setLoaded: (loaded) => set({ loaded }),\n setRoutes: (routes) => set({ routes }),\n}));\n\nexport interface AvoidRouterActions {\n resetRouting: () => void;\n updateRoutesForNodeId: (nodeId: string) => void;\n}\n\nconst noop = () => {};\nconst noopId = (_nodeId: string) => {};\n\nexport const useAvoidRouterActionsStore = create<{\n actions: AvoidRouterActions;\n setActions: (a: AvoidRouterActions) => void;\n}>((set) => ({\n actions: { resetRouting: noop, updateRoutesForNodeId: noopId },\n setActions: (actions) => set({ actions }),\n}));\n","/** Log web worker / routing messages in development. */\nexport const DEV_LOG_WEB_WORKER_MESSAGES = false;\n\n/**\n * Debounce (ms) before routing runs after diagram changes.\n * 10 ms merges drag events into a single update.\n * For larger diagrams (500+ nodes) use ~50-100 ms.\n */\nexport const DEBOUNCE_ROUTING_MS = 0;\n\n/** Whether edges start at handle border (true) or center (false). */\nexport const SHOULD_START_EDGE_AT_HANDLE_BORDER = true;\n\n/** Default border radius (px) for routed path corners. */\nexport const EDGE_BORDER_RADIUS = 0;\n","import { useMemo } from \"react\";\nimport { getStraightPath, getSmoothStepPath, Position as RFPosition } from \"@xyflow/react\";\nimport { useAvoidRoutesStore } from \"./store\";\nimport { EDGE_BORDER_RADIUS } from \"./constants\";\n\nexport type Position = \"left\" | \"right\" | \"top\" | \"bottom\";\n\nconst RF_POS: Record<Position, RFPosition> = {\n left: \"left\" as RFPosition,\n right: \"right\" as RFPosition,\n top: \"top\" as RFPosition,\n bottom: \"bottom\" as RFPosition,\n};\n\nexport interface UseAvoidNodesPathParams {\n id: string;\n sourceX: number;\n sourceY: number;\n targetX: number;\n targetY: number;\n sourcePosition?: Position;\n targetPosition?: Position;\n points?: { x: number; y: number }[];\n borderRadius?: number;\n offset?: number;\n}\n\n/**\n * Returns [path, labelX, labelY, wasRouted] for an avoid-nodes edge.\n * Reads from the avoid store (set by the worker); falls back to straight/smooth-step.\n */\nexport function useAvoidNodesPath(\n params: UseAvoidNodesPathParams\n): [path: string, labelX: number, labelY: number, wasRouted: boolean] {\n const {\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n offset,\n } = params;\n\n const loaded = useAvoidRoutesStore((s) => s.loaded);\n const route = useAvoidRoutesStore((s) => s.routes[id]);\n\n return useMemo(() => {\n if (loaded && route) {\n return [route.path, route.labelX, route.labelY, true];\n }\n\n if (sourcePosition && targetPosition) {\n const [smoothPath, sLabelX, sLabelY] = getSmoothStepPath({\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition: RF_POS[sourcePosition],\n targetPosition: RF_POS[targetPosition],\n borderRadius: params.borderRadius ?? EDGE_BORDER_RADIUS,\n offset: offset ?? 20,\n });\n return [smoothPath, sLabelX, sLabelY, false];\n }\n\n const [straightPath, labelX, labelY] = getStraightPath({\n sourceX,\n sourceY,\n targetX,\n targetY,\n });\n return [straightPath, labelX, labelY, false];\n }, [loaded, route, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, offset, params.borderRadius]);\n}\n"],"mappings":";;;;;AAAA,SAAS,cAAc;AAUhB,IAAM,sBAAsB,OAAyB,CAAC,SAAS;AAAA,EACpE,QAAQ;AAAA,EACR,QAAQ,CAAC;AAAA,EACT,WAAW,CAAC,WAAW,IAAI,EAAE,OAAO,CAAC;AAAA,EACrC,WAAW,CAAC,WAAW,IAAI,EAAE,OAAO,CAAC;AACvC,EAAE;AAOF,IAAM,OAAO,MAAM;AAAC;AACpB,IAAM,SAAS,CAAC,YAAoB;AAAC;AAE9B,IAAM,6BAA6B,OAGvC,CAAC,SAAS;AAAA,EACX,SAAS,EAAE,cAAc,MAAM,uBAAuB,OAAO;AAAA,EAC7D,YAAY,CAAC,YAAY,IAAI,EAAE,QAAQ,CAAC;AAC1C,EAAE;;;AC9BK,IAAM,8BAA8B;AAOpC,IAAM,sBAAsB;AAG5B,IAAM,qCAAqC;AAG3C,IAAM,qBAAqB;;;ACdlC,SAAS,eAAe;AACxB,SAAS,iBAAiB,yBAAiD;AAM3E,IAAM,SAAuC;AAAA,EAC3C,MAAM;AAAA,EACN,OAAO;AAAA,EACP,KAAK;AAAA,EACL,QAAQ;AACV;AAmBO,SAAS,kBACd,QACoE;AACpE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,SAAS,oBAAoB,CAAC,MAAM,EAAE,MAAM;AAClD,QAAM,QAAQ,oBAAoB,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;AAErD,SAAO,QAAQ,MAAM;AACnB,QAAI,UAAU,OAAO;AACnB,aAAO,CAAC,MAAM,MAAM,MAAM,QAAQ,MAAM,QAAQ,IAAI;AAAA,IACtD;AAEA,QAAI,kBAAkB,gBAAgB;AACpC,YAAM,CAAC,YAAY,SAAS,OAAO,IAAI,kBAAkB;AAAA,QACvD;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,gBAAgB,OAAO,cAAc;AAAA,QACrC,gBAAgB,OAAO,cAAc;AAAA,QACrC,cAAc,OAAO,gBAAgB;AAAA,QACrC,QAAQ,UAAU;AAAA,MACpB,CAAC;AACD,aAAO,CAAC,YAAY,SAAS,SAAS,KAAK;AAAA,IAC7C;AAEA,UAAM,CAAC,cAAc,QAAQ,MAAM,IAAI,gBAAgB;AAAA,MACrD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,WAAO,CAAC,cAAc,QAAQ,QAAQ,KAAK;AAAA,EAC7C,GAAG,CAAC,QAAQ,OAAO,SAAS,SAAS,SAAS,SAAS,gBAAgB,gBAAgB,QAAQ,OAAO,YAAY,CAAC;AACrH;","names":[]}
|
package/dist/edge.cjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
var _chunkTO34Q3IDcjs = require('./chunk-TO34Q3ID.cjs');
|
|
4
|
+
|
|
5
|
+
// src/AvoidNodesEdge.tsx
|
|
6
|
+
var _react = require('react');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
var _react3 = require('@xyflow/react');
|
|
12
|
+
var _jsxruntime = require('react/jsx-runtime');
|
|
13
|
+
var PARALLEL_EDGE_GAP = 22;
|
|
14
|
+
var EDGE_STROKE_WIDTH = 1.5;
|
|
15
|
+
var MIN_EDGE_LENGTH_FOR_LABEL_PX = 72;
|
|
16
|
+
var LABEL_WIDTH_APPROX_PX_PER_CHAR = 7;
|
|
17
|
+
var LABEL_PADDING_PX = 32;
|
|
18
|
+
var ER_RELATIONS = [
|
|
19
|
+
{ id: null, label: "None", sourceLabel: "", targetLabel: "" },
|
|
20
|
+
{ id: "one-to-one", label: "One to One", sourceLabel: "1", targetLabel: "1" },
|
|
21
|
+
{ id: "one-to-many", label: "One to Many", sourceLabel: "1", targetLabel: "*" },
|
|
22
|
+
{ id: "many-to-one", label: "Many to One", sourceLabel: "*", targetLabel: "1" },
|
|
23
|
+
{ id: "many-to-many", label: "Many to Many", sourceLabel: "*", targetLabel: "*" }
|
|
24
|
+
];
|
|
25
|
+
function offsetPoint(fromX, fromY, toX, toY, gap) {
|
|
26
|
+
const dx = toX - fromX;
|
|
27
|
+
const dy = toY - fromY;
|
|
28
|
+
const len = Math.sqrt(dx * dx + dy * dy);
|
|
29
|
+
if (len === 0 || gap === 0) return { x: fromX, y: fromY };
|
|
30
|
+
const ratio = gap / len;
|
|
31
|
+
return { x: fromX + dx * ratio, y: fromY + dy * ratio };
|
|
32
|
+
}
|
|
33
|
+
function getStraightPathWithParallelOffset(sourceX, sourceY, targetX, targetY, offsetX, offsetY) {
|
|
34
|
+
const s2 = sourceX + offsetX;
|
|
35
|
+
const t2 = targetX + offsetX;
|
|
36
|
+
const s3 = sourceY + offsetY;
|
|
37
|
+
const t3 = targetY + offsetY;
|
|
38
|
+
return `M ${sourceX} ${sourceY} L ${s2} ${s3} L ${t2} ${t3} L ${targetX} ${targetY}`;
|
|
39
|
+
}
|
|
40
|
+
function translatePath(pathStr, offsetX, offsetY) {
|
|
41
|
+
return pathStr.replace(
|
|
42
|
+
/([\d.-]+)\s+([\d.-]+)/g,
|
|
43
|
+
(_, a, b) => `${Number(a) + offsetX} ${Number(b) + offsetY}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
function applyAvoidPathParallelOffset(pathStr, sourceX, sourceY, targetX, targetY, offsetX, offsetY) {
|
|
47
|
+
const translated = translatePath(pathStr, offsetX, offsetY);
|
|
48
|
+
const rest = translated.replace(/^M\s*[\d.-]+\s+[\d.-]+/, "").trim();
|
|
49
|
+
return "M " + sourceX + " " + sourceY + " L " + (sourceX + offsetX) + " " + (sourceY + offsetY) + " " + rest + " L " + targetX + " " + targetY;
|
|
50
|
+
}
|
|
51
|
+
function AvoidNodesEdgeComponent(props) {
|
|
52
|
+
const {
|
|
53
|
+
id,
|
|
54
|
+
source,
|
|
55
|
+
target,
|
|
56
|
+
sourceX,
|
|
57
|
+
sourceY,
|
|
58
|
+
targetX,
|
|
59
|
+
targetY,
|
|
60
|
+
data,
|
|
61
|
+
sourcePosition,
|
|
62
|
+
targetPosition,
|
|
63
|
+
markerEnd: markerEndProp,
|
|
64
|
+
markerStart: markerStartProp
|
|
65
|
+
} = props;
|
|
66
|
+
const { getEdges } = _react3.useReactFlow.call(void 0, );
|
|
67
|
+
const edgeData = data;
|
|
68
|
+
const [basePath, labelX, labelY, isRouted] = _chunkTO34Q3IDcjs.useAvoidNodesPath.call(void 0, {
|
|
69
|
+
id,
|
|
70
|
+
sourceX,
|
|
71
|
+
sourceY,
|
|
72
|
+
targetX,
|
|
73
|
+
targetY,
|
|
74
|
+
sourcePosition,
|
|
75
|
+
targetPosition,
|
|
76
|
+
points: _optionalChain([edgeData, 'optionalAccess', _2 => _2.pathPoints])
|
|
77
|
+
});
|
|
78
|
+
const strokeColor = _nullishCoalesce(_optionalChain([edgeData, 'optionalAccess', _3 => _3.strokeColor]), () => ( "#94a3b8"));
|
|
79
|
+
const strokeWidth = _nullishCoalesce(_optionalChain([edgeData, 'optionalAccess', _4 => _4.strokeWidth]), () => ( EDGE_STROKE_WIDTH));
|
|
80
|
+
const strokeDasharray = _optionalChain([edgeData, 'optionalAccess', _5 => _5.strokeDasharray]);
|
|
81
|
+
const flowDirection = _nullishCoalesce(_optionalChain([edgeData, 'optionalAccess', _6 => _6.flowDirection]), () => ( "mono"));
|
|
82
|
+
const label = _nullishCoalesce(_optionalChain([edgeData, 'optionalAccess', _7 => _7.label]), () => ( ""));
|
|
83
|
+
const erRelation = _nullishCoalesce(_optionalChain([edgeData, 'optionalAccess', _8 => _8.erRelation]), () => ( null));
|
|
84
|
+
const connectorType = _nullishCoalesce(_optionalChain([edgeData, 'optionalAccess', _9 => _9.connectorType]), () => ( "default"));
|
|
85
|
+
const dataMarkerEnd = _optionalChain([edgeData, 'optionalAccess', _10 => _10.markerEnd]);
|
|
86
|
+
const dataMarkerStart = _optionalChain([edgeData, 'optionalAccess', _11 => _11.markerStart]);
|
|
87
|
+
const flowDirectionMarkerEnd = flowDirection === "mono" || flowDirection === "bi" ? "url(#arrowClosed)" : void 0;
|
|
88
|
+
const flowDirectionMarkerStart = flowDirection === "bi" ? "url(#arrowClosed)" : void 0;
|
|
89
|
+
const effectiveMarkerEnd = _nullishCoalesce(_nullishCoalesce(dataMarkerEnd, () => ( markerEndProp)), () => ( flowDirectionMarkerEnd));
|
|
90
|
+
const effectiveMarkerStart = _nullishCoalesce(_nullishCoalesce(dataMarkerStart, () => ( markerStartProp)), () => ( flowDirectionMarkerStart));
|
|
91
|
+
const parallelEdgeOffset = _react.useMemo.call(void 0, () => {
|
|
92
|
+
const edges = getEdges().filter(
|
|
93
|
+
(e) => e.source === source && e.target === target && e.type === "avoidNodes"
|
|
94
|
+
);
|
|
95
|
+
if (edges.length <= 1) return { offsetX: 0, offsetY: 0 };
|
|
96
|
+
const sorted = [...edges].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
97
|
+
const index = sorted.findIndex((e) => e.id === id);
|
|
98
|
+
if (index < 0) return { offsetX: 0, offsetY: 0 };
|
|
99
|
+
const dx = targetX - sourceX;
|
|
100
|
+
const dy = targetY - sourceY;
|
|
101
|
+
const len = Math.hypot(dx, dy);
|
|
102
|
+
if (len === 0) return { offsetX: 0, offsetY: 0 };
|
|
103
|
+
const perpX = -dy / len;
|
|
104
|
+
const perpY = dx / len;
|
|
105
|
+
const amount = (index - (edges.length - 1) / 2) * PARALLEL_EDGE_GAP;
|
|
106
|
+
return { offsetX: perpX * amount, offsetY: perpY * amount };
|
|
107
|
+
}, [source, target, id, sourceX, sourceY, targetX, targetY, getEdges]);
|
|
108
|
+
const hasParallelOffset = parallelEdgeOffset.offsetX !== 0 || parallelEdgeOffset.offsetY !== 0;
|
|
109
|
+
const edgePath = hasParallelOffset ? connectorType === "straight" ? getStraightPathWithParallelOffset(
|
|
110
|
+
sourceX,
|
|
111
|
+
sourceY,
|
|
112
|
+
targetX,
|
|
113
|
+
targetY,
|
|
114
|
+
parallelEdgeOffset.offsetX,
|
|
115
|
+
parallelEdgeOffset.offsetY
|
|
116
|
+
) : applyAvoidPathParallelOffset(
|
|
117
|
+
basePath,
|
|
118
|
+
sourceX,
|
|
119
|
+
sourceY,
|
|
120
|
+
targetX,
|
|
121
|
+
targetY,
|
|
122
|
+
parallelEdgeOffset.offsetX,
|
|
123
|
+
parallelEdgeOffset.offsetY
|
|
124
|
+
) : basePath;
|
|
125
|
+
const effectiveStrokeDasharray = _nullishCoalesce(strokeDasharray, () => ( (!isRouted ? "12,4" : void 0)));
|
|
126
|
+
const edgeLength = Math.hypot(targetX - sourceX, targetY - sourceY);
|
|
127
|
+
const labelWidthApprox = (_nullishCoalesce(_optionalChain([label, 'optionalAccess', _12 => _12.length]), () => ( 0))) * LABEL_WIDTH_APPROX_PX_PER_CHAR;
|
|
128
|
+
const minLengthToShowLabel = Math.max(
|
|
129
|
+
MIN_EDGE_LENGTH_FOR_LABEL_PX,
|
|
130
|
+
labelWidthApprox + LABEL_PADDING_PX
|
|
131
|
+
);
|
|
132
|
+
const showLabelByLength = !label || edgeLength >= minLengthToShowLabel;
|
|
133
|
+
const style = _react.useMemo.call(void 0,
|
|
134
|
+
() => ({
|
|
135
|
+
strokeWidth,
|
|
136
|
+
stroke: strokeColor,
|
|
137
|
+
strokeDasharray: effectiveStrokeDasharray
|
|
138
|
+
}),
|
|
139
|
+
[strokeWidth, strokeColor, effectiveStrokeDasharray]
|
|
140
|
+
);
|
|
141
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
142
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
143
|
+
_react3.BaseEdge,
|
|
144
|
+
{
|
|
145
|
+
id,
|
|
146
|
+
path: edgePath,
|
|
147
|
+
markerEnd: effectiveMarkerEnd,
|
|
148
|
+
markerStart: effectiveMarkerStart,
|
|
149
|
+
style
|
|
150
|
+
}
|
|
151
|
+
),
|
|
152
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _react3.EdgeLabelRenderer, { children: [
|
|
153
|
+
erRelation && (() => {
|
|
154
|
+
const relDef = ER_RELATIONS.find((r) => r.id === erRelation);
|
|
155
|
+
if (!relDef) return null;
|
|
156
|
+
const sOff = offsetPoint(sourceX, sourceY, targetX, targetY, 20);
|
|
157
|
+
const tOff = offsetPoint(targetX, targetY, sourceX, sourceY, 20);
|
|
158
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
159
|
+
relDef.sourceLabel && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
160
|
+
"div",
|
|
161
|
+
{
|
|
162
|
+
style: {
|
|
163
|
+
position: "absolute",
|
|
164
|
+
transform: `translate(-50%, -50%) translate(${sOff.x}px,${sOff.y}px)`,
|
|
165
|
+
pointerEvents: "none",
|
|
166
|
+
zIndex: 10
|
|
167
|
+
},
|
|
168
|
+
className: "nodrag nopan",
|
|
169
|
+
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { style: {
|
|
170
|
+
padding: "2px 6px",
|
|
171
|
+
fontSize: 10,
|
|
172
|
+
fontWeight: "bold",
|
|
173
|
+
fontFamily: "monospace",
|
|
174
|
+
borderRadius: 4,
|
|
175
|
+
background: "white",
|
|
176
|
+
border: "1px solid #d1d5db",
|
|
177
|
+
color: "#374151",
|
|
178
|
+
boxShadow: "0 1px 2px rgba(0,0,0,0.05)"
|
|
179
|
+
}, children: relDef.sourceLabel })
|
|
180
|
+
}
|
|
181
|
+
),
|
|
182
|
+
relDef.targetLabel && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
183
|
+
"div",
|
|
184
|
+
{
|
|
185
|
+
style: {
|
|
186
|
+
position: "absolute",
|
|
187
|
+
transform: `translate(-50%, -50%) translate(${tOff.x}px,${tOff.y}px)`,
|
|
188
|
+
pointerEvents: "none",
|
|
189
|
+
zIndex: 10
|
|
190
|
+
},
|
|
191
|
+
className: "nodrag nopan",
|
|
192
|
+
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { style: {
|
|
193
|
+
padding: "2px 6px",
|
|
194
|
+
fontSize: 10,
|
|
195
|
+
fontWeight: "bold",
|
|
196
|
+
fontFamily: "monospace",
|
|
197
|
+
borderRadius: 4,
|
|
198
|
+
background: "white",
|
|
199
|
+
border: "1px solid #d1d5db",
|
|
200
|
+
color: "#374151",
|
|
201
|
+
boxShadow: "0 1px 2px rgba(0,0,0,0.05)"
|
|
202
|
+
}, children: relDef.targetLabel })
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
] });
|
|
206
|
+
})(),
|
|
207
|
+
label && showLabelByLength && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
208
|
+
"div",
|
|
209
|
+
{
|
|
210
|
+
style: {
|
|
211
|
+
position: "absolute",
|
|
212
|
+
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
213
|
+
pointerEvents: "all",
|
|
214
|
+
zIndex: 10,
|
|
215
|
+
background: "white",
|
|
216
|
+
padding: "2px 10px",
|
|
217
|
+
borderRadius: 6,
|
|
218
|
+
fontSize: 12,
|
|
219
|
+
fontWeight: 500,
|
|
220
|
+
border: "1px solid #e2e8f0",
|
|
221
|
+
color: "#475569",
|
|
222
|
+
boxShadow: "0 1px 2px rgba(0,0,0,0.05)"
|
|
223
|
+
},
|
|
224
|
+
className: "nodrag nopan",
|
|
225
|
+
children: label
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
] }, id)
|
|
229
|
+
] });
|
|
230
|
+
}
|
|
231
|
+
var AvoidNodesEdge = _react.memo.call(void 0, AvoidNodesEdgeComponent);
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
exports.AvoidNodesEdge = AvoidNodesEdge;
|
|
235
|
+
//# sourceMappingURL=edge.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/awaisshah228/Documents/coding/ai-diagram-generator/avoid-nodes-pro-example/packages/avoid-nodes-edge/dist/edge.cjs","../src/AvoidNodesEdge.tsx"],"names":[],"mappings":"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","file":"/Users/awaisshah228/Documents/coding/ai-diagram-generator/avoid-nodes-pro-example/packages/avoid-nodes-edge/dist/edge.cjs","sourcesContent":[null,"import { memo, useMemo } from \"react\";\nimport {\n BaseEdge as RFBaseEdge,\n EdgeLabelRenderer,\n useReactFlow,\n type EdgeProps,\n type EdgeMarker,\n} from \"@xyflow/react\";\nimport { useAvoidNodesPath } from \"./useAvoidNodesPath\";\n\n/** Gap (px) between parallel avoid-nodes edges (same source/target). */\nconst PARALLEL_EDGE_GAP = 22;\nconst EDGE_STROKE_WIDTH = 1.5;\nconst MIN_EDGE_LENGTH_FOR_LABEL_PX = 72;\nconst LABEL_WIDTH_APPROX_PX_PER_CHAR = 7;\nconst LABEL_PADDING_PX = 32;\n\n/** ER relationship types. */\ntype ERRelation = \"one-to-one\" | \"one-to-many\" | \"many-to-one\" | \"many-to-many\" | null;\n\nconst ER_RELATIONS: { id: ERRelation; label: string; sourceLabel: string; targetLabel: string }[] = [\n { id: null, label: \"None\", sourceLabel: \"\", targetLabel: \"\" },\n { id: \"one-to-one\", label: \"One to One\", sourceLabel: \"1\", targetLabel: \"1\" },\n { id: \"one-to-many\", label: \"One to Many\", sourceLabel: \"1\", targetLabel: \"*\" },\n { id: \"many-to-one\", label: \"Many to One\", sourceLabel: \"*\", targetLabel: \"1\" },\n { id: \"many-to-many\", label: \"Many to Many\", sourceLabel: \"*\", targetLabel: \"*\" },\n];\n\nfunction offsetPoint(\n fromX: number,\n fromY: number,\n toX: number,\n toY: number,\n gap: number\n): { x: number; y: number } {\n const dx = toX - fromX;\n const dy = toY - fromY;\n const len = Math.sqrt(dx * dx + dy * dy);\n if (len === 0 || gap === 0) return { x: fromX, y: fromY };\n const ratio = gap / len;\n return { x: fromX + dx * ratio, y: fromY + dy * ratio };\n}\n\n/** Connector type: controls parallel-offset path shape. */\ntype ConnectorType = \"smoothstep\" | \"default\" | \"straight\" | \"step\";\n\n/** Straight path with parallel offset: short segments at start/end so path still connects to nodes. */\nfunction getStraightPathWithParallelOffset(\n sourceX: number,\n sourceY: number,\n targetX: number,\n targetY: number,\n offsetX: number,\n offsetY: number\n): string {\n const s2 = sourceX + offsetX;\n const t2 = targetX + offsetX;\n const s3 = sourceY + offsetY;\n const t3 = targetY + offsetY;\n return `M ${sourceX} ${sourceY} L ${s2} ${s3} L ${t2} ${t3} L ${targetX} ${targetY}`;\n}\n\n/** Translate every coordinate pair in an SVG path by (offsetX, offsetY). */\nfunction translatePath(pathStr: string, offsetX: number, offsetY: number): string {\n return pathStr.replace(/([\\d.-]+)\\s+([\\d.-]+)/g, (_, a, b) =>\n `${Number(a) + offsetX} ${Number(b) + offsetY}`\n );\n}\n\n/** Apply parallel offset to path: translate middle, keep start/end at handles. */\nfunction applyAvoidPathParallelOffset(\n pathStr: string,\n sourceX: number,\n sourceY: number,\n targetX: number,\n targetY: number,\n offsetX: number,\n offsetY: number\n): string {\n const translated = translatePath(pathStr, offsetX, offsetY);\n const rest = translated.replace(/^M\\s*[\\d.-]+\\s+[\\d.-]+/, \"\").trim();\n return (\n \"M \" +\n sourceX +\n \" \" +\n sourceY +\n \" L \" +\n (sourceX + offsetX) +\n \" \" +\n (sourceY + offsetY) +\n \" \" +\n rest +\n \" L \" +\n targetX +\n \" \" +\n targetY\n );\n}\n\n/** Edge data: all per-edge settings (stroke, markers, label, ER, connector type). */\nexport interface AvoidNodesEdgeData {\n label?: string;\n strokeColor?: string;\n strokeWidth?: number;\n strokeDasharray?: string;\n flowDirection?: \"mono\" | \"bi\" | \"none\";\n markerEnd?: EdgeMarker | string;\n markerStart?: EdgeMarker | string;\n markerColor?: string;\n markerScale?: number;\n erRelation?: ERRelation;\n connectorType?: ConnectorType;\n pathPoints?: { x: number; y: number }[];\n}\n\n/**\n * Avoid-nodes edge: orthogonal routing via libavoid-js WASM.\n * Supports stroke color, width, dash, markers, ER labels, parallel offset.\n */\nfunction AvoidNodesEdgeComponent(props: EdgeProps) {\n const {\n id,\n source,\n target,\n sourceX,\n sourceY,\n targetX,\n targetY,\n data,\n sourcePosition,\n targetPosition,\n markerEnd: markerEndProp,\n markerStart: markerStartProp,\n } = props;\n\n const { getEdges } = useReactFlow();\n\n const edgeData = data as AvoidNodesEdgeData | undefined;\n const [basePath, labelX, labelY, isRouted] = useAvoidNodesPath({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition: sourcePosition as \"left\" | \"right\" | \"top\" | \"bottom\" | undefined,\n targetPosition: targetPosition as \"left\" | \"right\" | \"top\" | \"bottom\" | undefined,\n points: edgeData?.pathPoints,\n });\n\n // ── Per-edge settings (no global store) ──\n const strokeColor = edgeData?.strokeColor ?? \"#94a3b8\";\n const strokeWidth = edgeData?.strokeWidth ?? EDGE_STROKE_WIDTH;\n const strokeDasharray = edgeData?.strokeDasharray;\n const flowDirection = edgeData?.flowDirection ?? \"mono\";\n const label = edgeData?.label ?? \"\";\n const erRelation = edgeData?.erRelation ?? null;\n const connectorType: ConnectorType = edgeData?.connectorType ?? \"default\";\n\n // ── Markers ──\n const dataMarkerEnd = edgeData?.markerEnd as string | undefined;\n const dataMarkerStart = edgeData?.markerStart as string | undefined;\n const flowDirectionMarkerEnd =\n flowDirection === \"mono\" || flowDirection === \"bi\" ? \"url(#arrowClosed)\" : undefined;\n const flowDirectionMarkerStart = flowDirection === \"bi\" ? \"url(#arrowClosed)\" : undefined;\n const effectiveMarkerEnd =\n dataMarkerEnd ?? (markerEndProp as string | undefined) ?? flowDirectionMarkerEnd;\n const effectiveMarkerStart =\n dataMarkerStart ?? (markerStartProp as string | undefined) ?? flowDirectionMarkerStart;\n\n // ── Parallel edge offset ──\n const parallelEdgeOffset = useMemo(() => {\n const edges = getEdges().filter(\n (e) => e.source === source && e.target === target && e.type === \"avoidNodes\"\n );\n if (edges.length <= 1) return { offsetX: 0, offsetY: 0 };\n const sorted = [...edges].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));\n const index = sorted.findIndex((e) => e.id === id);\n if (index < 0) return { offsetX: 0, offsetY: 0 };\n const dx = targetX - sourceX;\n const dy = targetY - sourceY;\n const len = Math.hypot(dx, dy);\n if (len === 0) return { offsetX: 0, offsetY: 0 };\n const perpX = -dy / len;\n const perpY = dx / len;\n const amount = (index - (edges.length - 1) / 2) * PARALLEL_EDGE_GAP;\n return { offsetX: perpX * amount, offsetY: perpY * amount };\n }, [source, target, id, sourceX, sourceY, targetX, targetY, getEdges]);\n\n const hasParallelOffset =\n parallelEdgeOffset.offsetX !== 0 || parallelEdgeOffset.offsetY !== 0;\n const edgePath = hasParallelOffset\n ? connectorType === \"straight\"\n ? getStraightPathWithParallelOffset(\n sourceX,\n sourceY,\n targetX,\n targetY,\n parallelEdgeOffset.offsetX,\n parallelEdgeOffset.offsetY\n )\n : applyAvoidPathParallelOffset(\n basePath,\n sourceX,\n sourceY,\n targetX,\n targetY,\n parallelEdgeOffset.offsetX,\n parallelEdgeOffset.offsetY\n )\n : basePath;\n\n const effectiveStrokeDasharray = strokeDasharray ?? (!isRouted ? \"12,4\" : undefined);\n\n // ── Label visibility ──\n const edgeLength = Math.hypot(targetX - sourceX, targetY - sourceY);\n const labelWidthApprox = (label?.length ?? 0) * LABEL_WIDTH_APPROX_PX_PER_CHAR;\n const minLengthToShowLabel = Math.max(\n MIN_EDGE_LENGTH_FOR_LABEL_PX,\n labelWidthApprox + LABEL_PADDING_PX\n );\n const showLabelByLength = !label || edgeLength >= minLengthToShowLabel;\n\n const style = useMemo(\n () => ({\n strokeWidth,\n stroke: strokeColor,\n strokeDasharray: effectiveStrokeDasharray,\n }),\n [strokeWidth, strokeColor, effectiveStrokeDasharray]\n );\n\n return (\n <>\n <RFBaseEdge\n id={id}\n path={edgePath}\n markerEnd={effectiveMarkerEnd}\n markerStart={effectiveMarkerStart}\n style={style}\n />\n <EdgeLabelRenderer key={id}>\n {erRelation && (() => {\n const relDef = ER_RELATIONS.find((r) => r.id === erRelation);\n if (!relDef) return null;\n const sOff = offsetPoint(sourceX, sourceY, targetX, targetY, 20);\n const tOff = offsetPoint(targetX, targetY, sourceX, sourceY, 20);\n return (\n <>\n {relDef.sourceLabel && (\n <div\n style={{\n position: \"absolute\",\n transform: `translate(-50%, -50%) translate(${sOff.x}px,${sOff.y}px)`,\n pointerEvents: \"none\",\n zIndex: 10,\n }}\n className=\"nodrag nopan\"\n >\n <span style={{\n padding: \"2px 6px\",\n fontSize: 10,\n fontWeight: \"bold\",\n fontFamily: \"monospace\",\n borderRadius: 4,\n background: \"white\",\n border: \"1px solid #d1d5db\",\n color: \"#374151\",\n boxShadow: \"0 1px 2px rgba(0,0,0,0.05)\",\n }}>\n {relDef.sourceLabel}\n </span>\n </div>\n )}\n {relDef.targetLabel && (\n <div\n style={{\n position: \"absolute\",\n transform: `translate(-50%, -50%) translate(${tOff.x}px,${tOff.y}px)`,\n pointerEvents: \"none\",\n zIndex: 10,\n }}\n className=\"nodrag nopan\"\n >\n <span style={{\n padding: \"2px 6px\",\n fontSize: 10,\n fontWeight: \"bold\",\n fontFamily: \"monospace\",\n borderRadius: 4,\n background: \"white\",\n border: \"1px solid #d1d5db\",\n color: \"#374151\",\n boxShadow: \"0 1px 2px rgba(0,0,0,0.05)\",\n }}>\n {relDef.targetLabel}\n </span>\n </div>\n )}\n </>\n );\n })()}\n {label && showLabelByLength && (\n <div\n style={{\n position: \"absolute\",\n transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,\n pointerEvents: \"all\",\n zIndex: 10,\n background: \"white\",\n padding: \"2px 10px\",\n borderRadius: 6,\n fontSize: 12,\n fontWeight: 500,\n border: \"1px solid #e2e8f0\",\n color: \"#475569\",\n boxShadow: \"0 1px 2px rgba(0,0,0,0.05)\",\n }}\n className=\"nodrag nopan\"\n >\n {label}\n </div>\n )}\n </EdgeLabelRenderer>\n </>\n );\n}\n\nexport const AvoidNodesEdge = memo(AvoidNodesEdgeComponent);\n"]}
|
package/dist/edge.d.cts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
import { EdgeProps, EdgeMarker } from '@xyflow/react';
|
|
4
|
+
|
|
5
|
+
/** ER relationship types. */
|
|
6
|
+
type ERRelation = "one-to-one" | "one-to-many" | "many-to-one" | "many-to-many" | null;
|
|
7
|
+
/** Connector type: controls parallel-offset path shape. */
|
|
8
|
+
type ConnectorType = "smoothstep" | "default" | "straight" | "step";
|
|
9
|
+
/** Edge data: all per-edge settings (stroke, markers, label, ER, connector type). */
|
|
10
|
+
interface AvoidNodesEdgeData {
|
|
11
|
+
label?: string;
|
|
12
|
+
strokeColor?: string;
|
|
13
|
+
strokeWidth?: number;
|
|
14
|
+
strokeDasharray?: string;
|
|
15
|
+
flowDirection?: "mono" | "bi" | "none";
|
|
16
|
+
markerEnd?: EdgeMarker | string;
|
|
17
|
+
markerStart?: EdgeMarker | string;
|
|
18
|
+
markerColor?: string;
|
|
19
|
+
markerScale?: number;
|
|
20
|
+
erRelation?: ERRelation;
|
|
21
|
+
connectorType?: ConnectorType;
|
|
22
|
+
pathPoints?: {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
}[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Avoid-nodes edge: orthogonal routing via libavoid-js WASM.
|
|
29
|
+
* Supports stroke color, width, dash, markers, ER labels, parallel offset.
|
|
30
|
+
*/
|
|
31
|
+
declare function AvoidNodesEdgeComponent(props: EdgeProps): react_jsx_runtime.JSX.Element;
|
|
32
|
+
declare const AvoidNodesEdge: react.MemoExoticComponent<typeof AvoidNodesEdgeComponent>;
|
|
33
|
+
|
|
34
|
+
export { AvoidNodesEdge, type AvoidNodesEdgeData };
|
package/dist/edge.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
import { EdgeProps, EdgeMarker } from '@xyflow/react';
|
|
4
|
+
|
|
5
|
+
/** ER relationship types. */
|
|
6
|
+
type ERRelation = "one-to-one" | "one-to-many" | "many-to-one" | "many-to-many" | null;
|
|
7
|
+
/** Connector type: controls parallel-offset path shape. */
|
|
8
|
+
type ConnectorType = "smoothstep" | "default" | "straight" | "step";
|
|
9
|
+
/** Edge data: all per-edge settings (stroke, markers, label, ER, connector type). */
|
|
10
|
+
interface AvoidNodesEdgeData {
|
|
11
|
+
label?: string;
|
|
12
|
+
strokeColor?: string;
|
|
13
|
+
strokeWidth?: number;
|
|
14
|
+
strokeDasharray?: string;
|
|
15
|
+
flowDirection?: "mono" | "bi" | "none";
|
|
16
|
+
markerEnd?: EdgeMarker | string;
|
|
17
|
+
markerStart?: EdgeMarker | string;
|
|
18
|
+
markerColor?: string;
|
|
19
|
+
markerScale?: number;
|
|
20
|
+
erRelation?: ERRelation;
|
|
21
|
+
connectorType?: ConnectorType;
|
|
22
|
+
pathPoints?: {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
}[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Avoid-nodes edge: orthogonal routing via libavoid-js WASM.
|
|
29
|
+
* Supports stroke color, width, dash, markers, ER labels, parallel offset.
|
|
30
|
+
*/
|
|
31
|
+
declare function AvoidNodesEdgeComponent(props: EdgeProps): react_jsx_runtime.JSX.Element;
|
|
32
|
+
declare const AvoidNodesEdge: react.MemoExoticComponent<typeof AvoidNodesEdgeComponent>;
|
|
33
|
+
|
|
34
|
+
export { AvoidNodesEdge, type AvoidNodesEdgeData };
|
package/dist/edge.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useAvoidNodesPath
|
|
3
|
+
} from "./chunk-VPHZVUPR.js";
|
|
4
|
+
|
|
5
|
+
// src/AvoidNodesEdge.tsx
|
|
6
|
+
import { memo, useMemo } from "react";
|
|
7
|
+
import {
|
|
8
|
+
BaseEdge as RFBaseEdge,
|
|
9
|
+
EdgeLabelRenderer,
|
|
10
|
+
useReactFlow
|
|
11
|
+
} from "@xyflow/react";
|
|
12
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
var PARALLEL_EDGE_GAP = 22;
|
|
14
|
+
var EDGE_STROKE_WIDTH = 1.5;
|
|
15
|
+
var MIN_EDGE_LENGTH_FOR_LABEL_PX = 72;
|
|
16
|
+
var LABEL_WIDTH_APPROX_PX_PER_CHAR = 7;
|
|
17
|
+
var LABEL_PADDING_PX = 32;
|
|
18
|
+
var ER_RELATIONS = [
|
|
19
|
+
{ id: null, label: "None", sourceLabel: "", targetLabel: "" },
|
|
20
|
+
{ id: "one-to-one", label: "One to One", sourceLabel: "1", targetLabel: "1" },
|
|
21
|
+
{ id: "one-to-many", label: "One to Many", sourceLabel: "1", targetLabel: "*" },
|
|
22
|
+
{ id: "many-to-one", label: "Many to One", sourceLabel: "*", targetLabel: "1" },
|
|
23
|
+
{ id: "many-to-many", label: "Many to Many", sourceLabel: "*", targetLabel: "*" }
|
|
24
|
+
];
|
|
25
|
+
function offsetPoint(fromX, fromY, toX, toY, gap) {
|
|
26
|
+
const dx = toX - fromX;
|
|
27
|
+
const dy = toY - fromY;
|
|
28
|
+
const len = Math.sqrt(dx * dx + dy * dy);
|
|
29
|
+
if (len === 0 || gap === 0) return { x: fromX, y: fromY };
|
|
30
|
+
const ratio = gap / len;
|
|
31
|
+
return { x: fromX + dx * ratio, y: fromY + dy * ratio };
|
|
32
|
+
}
|
|
33
|
+
function getStraightPathWithParallelOffset(sourceX, sourceY, targetX, targetY, offsetX, offsetY) {
|
|
34
|
+
const s2 = sourceX + offsetX;
|
|
35
|
+
const t2 = targetX + offsetX;
|
|
36
|
+
const s3 = sourceY + offsetY;
|
|
37
|
+
const t3 = targetY + offsetY;
|
|
38
|
+
return `M ${sourceX} ${sourceY} L ${s2} ${s3} L ${t2} ${t3} L ${targetX} ${targetY}`;
|
|
39
|
+
}
|
|
40
|
+
function translatePath(pathStr, offsetX, offsetY) {
|
|
41
|
+
return pathStr.replace(
|
|
42
|
+
/([\d.-]+)\s+([\d.-]+)/g,
|
|
43
|
+
(_, a, b) => `${Number(a) + offsetX} ${Number(b) + offsetY}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
function applyAvoidPathParallelOffset(pathStr, sourceX, sourceY, targetX, targetY, offsetX, offsetY) {
|
|
47
|
+
const translated = translatePath(pathStr, offsetX, offsetY);
|
|
48
|
+
const rest = translated.replace(/^M\s*[\d.-]+\s+[\d.-]+/, "").trim();
|
|
49
|
+
return "M " + sourceX + " " + sourceY + " L " + (sourceX + offsetX) + " " + (sourceY + offsetY) + " " + rest + " L " + targetX + " " + targetY;
|
|
50
|
+
}
|
|
51
|
+
function AvoidNodesEdgeComponent(props) {
|
|
52
|
+
const {
|
|
53
|
+
id,
|
|
54
|
+
source,
|
|
55
|
+
target,
|
|
56
|
+
sourceX,
|
|
57
|
+
sourceY,
|
|
58
|
+
targetX,
|
|
59
|
+
targetY,
|
|
60
|
+
data,
|
|
61
|
+
sourcePosition,
|
|
62
|
+
targetPosition,
|
|
63
|
+
markerEnd: markerEndProp,
|
|
64
|
+
markerStart: markerStartProp
|
|
65
|
+
} = props;
|
|
66
|
+
const { getEdges } = useReactFlow();
|
|
67
|
+
const edgeData = data;
|
|
68
|
+
const [basePath, labelX, labelY, isRouted] = useAvoidNodesPath({
|
|
69
|
+
id,
|
|
70
|
+
sourceX,
|
|
71
|
+
sourceY,
|
|
72
|
+
targetX,
|
|
73
|
+
targetY,
|
|
74
|
+
sourcePosition,
|
|
75
|
+
targetPosition,
|
|
76
|
+
points: edgeData?.pathPoints
|
|
77
|
+
});
|
|
78
|
+
const strokeColor = edgeData?.strokeColor ?? "#94a3b8";
|
|
79
|
+
const strokeWidth = edgeData?.strokeWidth ?? EDGE_STROKE_WIDTH;
|
|
80
|
+
const strokeDasharray = edgeData?.strokeDasharray;
|
|
81
|
+
const flowDirection = edgeData?.flowDirection ?? "mono";
|
|
82
|
+
const label = edgeData?.label ?? "";
|
|
83
|
+
const erRelation = edgeData?.erRelation ?? null;
|
|
84
|
+
const connectorType = edgeData?.connectorType ?? "default";
|
|
85
|
+
const dataMarkerEnd = edgeData?.markerEnd;
|
|
86
|
+
const dataMarkerStart = edgeData?.markerStart;
|
|
87
|
+
const flowDirectionMarkerEnd = flowDirection === "mono" || flowDirection === "bi" ? "url(#arrowClosed)" : void 0;
|
|
88
|
+
const flowDirectionMarkerStart = flowDirection === "bi" ? "url(#arrowClosed)" : void 0;
|
|
89
|
+
const effectiveMarkerEnd = dataMarkerEnd ?? markerEndProp ?? flowDirectionMarkerEnd;
|
|
90
|
+
const effectiveMarkerStart = dataMarkerStart ?? markerStartProp ?? flowDirectionMarkerStart;
|
|
91
|
+
const parallelEdgeOffset = useMemo(() => {
|
|
92
|
+
const edges = getEdges().filter(
|
|
93
|
+
(e) => e.source === source && e.target === target && e.type === "avoidNodes"
|
|
94
|
+
);
|
|
95
|
+
if (edges.length <= 1) return { offsetX: 0, offsetY: 0 };
|
|
96
|
+
const sorted = [...edges].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
97
|
+
const index = sorted.findIndex((e) => e.id === id);
|
|
98
|
+
if (index < 0) return { offsetX: 0, offsetY: 0 };
|
|
99
|
+
const dx = targetX - sourceX;
|
|
100
|
+
const dy = targetY - sourceY;
|
|
101
|
+
const len = Math.hypot(dx, dy);
|
|
102
|
+
if (len === 0) return { offsetX: 0, offsetY: 0 };
|
|
103
|
+
const perpX = -dy / len;
|
|
104
|
+
const perpY = dx / len;
|
|
105
|
+
const amount = (index - (edges.length - 1) / 2) * PARALLEL_EDGE_GAP;
|
|
106
|
+
return { offsetX: perpX * amount, offsetY: perpY * amount };
|
|
107
|
+
}, [source, target, id, sourceX, sourceY, targetX, targetY, getEdges]);
|
|
108
|
+
const hasParallelOffset = parallelEdgeOffset.offsetX !== 0 || parallelEdgeOffset.offsetY !== 0;
|
|
109
|
+
const edgePath = hasParallelOffset ? connectorType === "straight" ? getStraightPathWithParallelOffset(
|
|
110
|
+
sourceX,
|
|
111
|
+
sourceY,
|
|
112
|
+
targetX,
|
|
113
|
+
targetY,
|
|
114
|
+
parallelEdgeOffset.offsetX,
|
|
115
|
+
parallelEdgeOffset.offsetY
|
|
116
|
+
) : applyAvoidPathParallelOffset(
|
|
117
|
+
basePath,
|
|
118
|
+
sourceX,
|
|
119
|
+
sourceY,
|
|
120
|
+
targetX,
|
|
121
|
+
targetY,
|
|
122
|
+
parallelEdgeOffset.offsetX,
|
|
123
|
+
parallelEdgeOffset.offsetY
|
|
124
|
+
) : basePath;
|
|
125
|
+
const effectiveStrokeDasharray = strokeDasharray ?? (!isRouted ? "12,4" : void 0);
|
|
126
|
+
const edgeLength = Math.hypot(targetX - sourceX, targetY - sourceY);
|
|
127
|
+
const labelWidthApprox = (label?.length ?? 0) * LABEL_WIDTH_APPROX_PX_PER_CHAR;
|
|
128
|
+
const minLengthToShowLabel = Math.max(
|
|
129
|
+
MIN_EDGE_LENGTH_FOR_LABEL_PX,
|
|
130
|
+
labelWidthApprox + LABEL_PADDING_PX
|
|
131
|
+
);
|
|
132
|
+
const showLabelByLength = !label || edgeLength >= minLengthToShowLabel;
|
|
133
|
+
const style = useMemo(
|
|
134
|
+
() => ({
|
|
135
|
+
strokeWidth,
|
|
136
|
+
stroke: strokeColor,
|
|
137
|
+
strokeDasharray: effectiveStrokeDasharray
|
|
138
|
+
}),
|
|
139
|
+
[strokeWidth, strokeColor, effectiveStrokeDasharray]
|
|
140
|
+
);
|
|
141
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
142
|
+
/* @__PURE__ */ jsx(
|
|
143
|
+
RFBaseEdge,
|
|
144
|
+
{
|
|
145
|
+
id,
|
|
146
|
+
path: edgePath,
|
|
147
|
+
markerEnd: effectiveMarkerEnd,
|
|
148
|
+
markerStart: effectiveMarkerStart,
|
|
149
|
+
style
|
|
150
|
+
}
|
|
151
|
+
),
|
|
152
|
+
/* @__PURE__ */ jsxs(EdgeLabelRenderer, { children: [
|
|
153
|
+
erRelation && (() => {
|
|
154
|
+
const relDef = ER_RELATIONS.find((r) => r.id === erRelation);
|
|
155
|
+
if (!relDef) return null;
|
|
156
|
+
const sOff = offsetPoint(sourceX, sourceY, targetX, targetY, 20);
|
|
157
|
+
const tOff = offsetPoint(targetX, targetY, sourceX, sourceY, 20);
|
|
158
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
159
|
+
relDef.sourceLabel && /* @__PURE__ */ jsx(
|
|
160
|
+
"div",
|
|
161
|
+
{
|
|
162
|
+
style: {
|
|
163
|
+
position: "absolute",
|
|
164
|
+
transform: `translate(-50%, -50%) translate(${sOff.x}px,${sOff.y}px)`,
|
|
165
|
+
pointerEvents: "none",
|
|
166
|
+
zIndex: 10
|
|
167
|
+
},
|
|
168
|
+
className: "nodrag nopan",
|
|
169
|
+
children: /* @__PURE__ */ jsx("span", { style: {
|
|
170
|
+
padding: "2px 6px",
|
|
171
|
+
fontSize: 10,
|
|
172
|
+
fontWeight: "bold",
|
|
173
|
+
fontFamily: "monospace",
|
|
174
|
+
borderRadius: 4,
|
|
175
|
+
background: "white",
|
|
176
|
+
border: "1px solid #d1d5db",
|
|
177
|
+
color: "#374151",
|
|
178
|
+
boxShadow: "0 1px 2px rgba(0,0,0,0.05)"
|
|
179
|
+
}, children: relDef.sourceLabel })
|
|
180
|
+
}
|
|
181
|
+
),
|
|
182
|
+
relDef.targetLabel && /* @__PURE__ */ jsx(
|
|
183
|
+
"div",
|
|
184
|
+
{
|
|
185
|
+
style: {
|
|
186
|
+
position: "absolute",
|
|
187
|
+
transform: `translate(-50%, -50%) translate(${tOff.x}px,${tOff.y}px)`,
|
|
188
|
+
pointerEvents: "none",
|
|
189
|
+
zIndex: 10
|
|
190
|
+
},
|
|
191
|
+
className: "nodrag nopan",
|
|
192
|
+
children: /* @__PURE__ */ jsx("span", { style: {
|
|
193
|
+
padding: "2px 6px",
|
|
194
|
+
fontSize: 10,
|
|
195
|
+
fontWeight: "bold",
|
|
196
|
+
fontFamily: "monospace",
|
|
197
|
+
borderRadius: 4,
|
|
198
|
+
background: "white",
|
|
199
|
+
border: "1px solid #d1d5db",
|
|
200
|
+
color: "#374151",
|
|
201
|
+
boxShadow: "0 1px 2px rgba(0,0,0,0.05)"
|
|
202
|
+
}, children: relDef.targetLabel })
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
] });
|
|
206
|
+
})(),
|
|
207
|
+
label && showLabelByLength && /* @__PURE__ */ jsx(
|
|
208
|
+
"div",
|
|
209
|
+
{
|
|
210
|
+
style: {
|
|
211
|
+
position: "absolute",
|
|
212
|
+
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
213
|
+
pointerEvents: "all",
|
|
214
|
+
zIndex: 10,
|
|
215
|
+
background: "white",
|
|
216
|
+
padding: "2px 10px",
|
|
217
|
+
borderRadius: 6,
|
|
218
|
+
fontSize: 12,
|
|
219
|
+
fontWeight: 500,
|
|
220
|
+
border: "1px solid #e2e8f0",
|
|
221
|
+
color: "#475569",
|
|
222
|
+
boxShadow: "0 1px 2px rgba(0,0,0,0.05)"
|
|
223
|
+
},
|
|
224
|
+
className: "nodrag nopan",
|
|
225
|
+
children: label
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
] }, id)
|
|
229
|
+
] });
|
|
230
|
+
}
|
|
231
|
+
var AvoidNodesEdge = memo(AvoidNodesEdgeComponent);
|
|
232
|
+
export {
|
|
233
|
+
AvoidNodesEdge
|
|
234
|
+
};
|
|
235
|
+
//# sourceMappingURL=edge.js.map
|
package/dist/edge.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/AvoidNodesEdge.tsx"],"sourcesContent":["import { memo, useMemo } from \"react\";\nimport {\n BaseEdge as RFBaseEdge,\n EdgeLabelRenderer,\n useReactFlow,\n type EdgeProps,\n type EdgeMarker,\n} from \"@xyflow/react\";\nimport { useAvoidNodesPath } from \"./useAvoidNodesPath\";\n\n/** Gap (px) between parallel avoid-nodes edges (same source/target). */\nconst PARALLEL_EDGE_GAP = 22;\nconst EDGE_STROKE_WIDTH = 1.5;\nconst MIN_EDGE_LENGTH_FOR_LABEL_PX = 72;\nconst LABEL_WIDTH_APPROX_PX_PER_CHAR = 7;\nconst LABEL_PADDING_PX = 32;\n\n/** ER relationship types. */\ntype ERRelation = \"one-to-one\" | \"one-to-many\" | \"many-to-one\" | \"many-to-many\" | null;\n\nconst ER_RELATIONS: { id: ERRelation; label: string; sourceLabel: string; targetLabel: string }[] = [\n { id: null, label: \"None\", sourceLabel: \"\", targetLabel: \"\" },\n { id: \"one-to-one\", label: \"One to One\", sourceLabel: \"1\", targetLabel: \"1\" },\n { id: \"one-to-many\", label: \"One to Many\", sourceLabel: \"1\", targetLabel: \"*\" },\n { id: \"many-to-one\", label: \"Many to One\", sourceLabel: \"*\", targetLabel: \"1\" },\n { id: \"many-to-many\", label: \"Many to Many\", sourceLabel: \"*\", targetLabel: \"*\" },\n];\n\nfunction offsetPoint(\n fromX: number,\n fromY: number,\n toX: number,\n toY: number,\n gap: number\n): { x: number; y: number } {\n const dx = toX - fromX;\n const dy = toY - fromY;\n const len = Math.sqrt(dx * dx + dy * dy);\n if (len === 0 || gap === 0) return { x: fromX, y: fromY };\n const ratio = gap / len;\n return { x: fromX + dx * ratio, y: fromY + dy * ratio };\n}\n\n/** Connector type: controls parallel-offset path shape. */\ntype ConnectorType = \"smoothstep\" | \"default\" | \"straight\" | \"step\";\n\n/** Straight path with parallel offset: short segments at start/end so path still connects to nodes. */\nfunction getStraightPathWithParallelOffset(\n sourceX: number,\n sourceY: number,\n targetX: number,\n targetY: number,\n offsetX: number,\n offsetY: number\n): string {\n const s2 = sourceX + offsetX;\n const t2 = targetX + offsetX;\n const s3 = sourceY + offsetY;\n const t3 = targetY + offsetY;\n return `M ${sourceX} ${sourceY} L ${s2} ${s3} L ${t2} ${t3} L ${targetX} ${targetY}`;\n}\n\n/** Translate every coordinate pair in an SVG path by (offsetX, offsetY). */\nfunction translatePath(pathStr: string, offsetX: number, offsetY: number): string {\n return pathStr.replace(/([\\d.-]+)\\s+([\\d.-]+)/g, (_, a, b) =>\n `${Number(a) + offsetX} ${Number(b) + offsetY}`\n );\n}\n\n/** Apply parallel offset to path: translate middle, keep start/end at handles. */\nfunction applyAvoidPathParallelOffset(\n pathStr: string,\n sourceX: number,\n sourceY: number,\n targetX: number,\n targetY: number,\n offsetX: number,\n offsetY: number\n): string {\n const translated = translatePath(pathStr, offsetX, offsetY);\n const rest = translated.replace(/^M\\s*[\\d.-]+\\s+[\\d.-]+/, \"\").trim();\n return (\n \"M \" +\n sourceX +\n \" \" +\n sourceY +\n \" L \" +\n (sourceX + offsetX) +\n \" \" +\n (sourceY + offsetY) +\n \" \" +\n rest +\n \" L \" +\n targetX +\n \" \" +\n targetY\n );\n}\n\n/** Edge data: all per-edge settings (stroke, markers, label, ER, connector type). */\nexport interface AvoidNodesEdgeData {\n label?: string;\n strokeColor?: string;\n strokeWidth?: number;\n strokeDasharray?: string;\n flowDirection?: \"mono\" | \"bi\" | \"none\";\n markerEnd?: EdgeMarker | string;\n markerStart?: EdgeMarker | string;\n markerColor?: string;\n markerScale?: number;\n erRelation?: ERRelation;\n connectorType?: ConnectorType;\n pathPoints?: { x: number; y: number }[];\n}\n\n/**\n * Avoid-nodes edge: orthogonal routing via libavoid-js WASM.\n * Supports stroke color, width, dash, markers, ER labels, parallel offset.\n */\nfunction AvoidNodesEdgeComponent(props: EdgeProps) {\n const {\n id,\n source,\n target,\n sourceX,\n sourceY,\n targetX,\n targetY,\n data,\n sourcePosition,\n targetPosition,\n markerEnd: markerEndProp,\n markerStart: markerStartProp,\n } = props;\n\n const { getEdges } = useReactFlow();\n\n const edgeData = data as AvoidNodesEdgeData | undefined;\n const [basePath, labelX, labelY, isRouted] = useAvoidNodesPath({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition: sourcePosition as \"left\" | \"right\" | \"top\" | \"bottom\" | undefined,\n targetPosition: targetPosition as \"left\" | \"right\" | \"top\" | \"bottom\" | undefined,\n points: edgeData?.pathPoints,\n });\n\n // ── Per-edge settings (no global store) ──\n const strokeColor = edgeData?.strokeColor ?? \"#94a3b8\";\n const strokeWidth = edgeData?.strokeWidth ?? EDGE_STROKE_WIDTH;\n const strokeDasharray = edgeData?.strokeDasharray;\n const flowDirection = edgeData?.flowDirection ?? \"mono\";\n const label = edgeData?.label ?? \"\";\n const erRelation = edgeData?.erRelation ?? null;\n const connectorType: ConnectorType = edgeData?.connectorType ?? \"default\";\n\n // ── Markers ──\n const dataMarkerEnd = edgeData?.markerEnd as string | undefined;\n const dataMarkerStart = edgeData?.markerStart as string | undefined;\n const flowDirectionMarkerEnd =\n flowDirection === \"mono\" || flowDirection === \"bi\" ? \"url(#arrowClosed)\" : undefined;\n const flowDirectionMarkerStart = flowDirection === \"bi\" ? \"url(#arrowClosed)\" : undefined;\n const effectiveMarkerEnd =\n dataMarkerEnd ?? (markerEndProp as string | undefined) ?? flowDirectionMarkerEnd;\n const effectiveMarkerStart =\n dataMarkerStart ?? (markerStartProp as string | undefined) ?? flowDirectionMarkerStart;\n\n // ── Parallel edge offset ──\n const parallelEdgeOffset = useMemo(() => {\n const edges = getEdges().filter(\n (e) => e.source === source && e.target === target && e.type === \"avoidNodes\"\n );\n if (edges.length <= 1) return { offsetX: 0, offsetY: 0 };\n const sorted = [...edges].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));\n const index = sorted.findIndex((e) => e.id === id);\n if (index < 0) return { offsetX: 0, offsetY: 0 };\n const dx = targetX - sourceX;\n const dy = targetY - sourceY;\n const len = Math.hypot(dx, dy);\n if (len === 0) return { offsetX: 0, offsetY: 0 };\n const perpX = -dy / len;\n const perpY = dx / len;\n const amount = (index - (edges.length - 1) / 2) * PARALLEL_EDGE_GAP;\n return { offsetX: perpX * amount, offsetY: perpY * amount };\n }, [source, target, id, sourceX, sourceY, targetX, targetY, getEdges]);\n\n const hasParallelOffset =\n parallelEdgeOffset.offsetX !== 0 || parallelEdgeOffset.offsetY !== 0;\n const edgePath = hasParallelOffset\n ? connectorType === \"straight\"\n ? getStraightPathWithParallelOffset(\n sourceX,\n sourceY,\n targetX,\n targetY,\n parallelEdgeOffset.offsetX,\n parallelEdgeOffset.offsetY\n )\n : applyAvoidPathParallelOffset(\n basePath,\n sourceX,\n sourceY,\n targetX,\n targetY,\n parallelEdgeOffset.offsetX,\n parallelEdgeOffset.offsetY\n )\n : basePath;\n\n const effectiveStrokeDasharray = strokeDasharray ?? (!isRouted ? \"12,4\" : undefined);\n\n // ── Label visibility ──\n const edgeLength = Math.hypot(targetX - sourceX, targetY - sourceY);\n const labelWidthApprox = (label?.length ?? 0) * LABEL_WIDTH_APPROX_PX_PER_CHAR;\n const minLengthToShowLabel = Math.max(\n MIN_EDGE_LENGTH_FOR_LABEL_PX,\n labelWidthApprox + LABEL_PADDING_PX\n );\n const showLabelByLength = !label || edgeLength >= minLengthToShowLabel;\n\n const style = useMemo(\n () => ({\n strokeWidth,\n stroke: strokeColor,\n strokeDasharray: effectiveStrokeDasharray,\n }),\n [strokeWidth, strokeColor, effectiveStrokeDasharray]\n );\n\n return (\n <>\n <RFBaseEdge\n id={id}\n path={edgePath}\n markerEnd={effectiveMarkerEnd}\n markerStart={effectiveMarkerStart}\n style={style}\n />\n <EdgeLabelRenderer key={id}>\n {erRelation && (() => {\n const relDef = ER_RELATIONS.find((r) => r.id === erRelation);\n if (!relDef) return null;\n const sOff = offsetPoint(sourceX, sourceY, targetX, targetY, 20);\n const tOff = offsetPoint(targetX, targetY, sourceX, sourceY, 20);\n return (\n <>\n {relDef.sourceLabel && (\n <div\n style={{\n position: \"absolute\",\n transform: `translate(-50%, -50%) translate(${sOff.x}px,${sOff.y}px)`,\n pointerEvents: \"none\",\n zIndex: 10,\n }}\n className=\"nodrag nopan\"\n >\n <span style={{\n padding: \"2px 6px\",\n fontSize: 10,\n fontWeight: \"bold\",\n fontFamily: \"monospace\",\n borderRadius: 4,\n background: \"white\",\n border: \"1px solid #d1d5db\",\n color: \"#374151\",\n boxShadow: \"0 1px 2px rgba(0,0,0,0.05)\",\n }}>\n {relDef.sourceLabel}\n </span>\n </div>\n )}\n {relDef.targetLabel && (\n <div\n style={{\n position: \"absolute\",\n transform: `translate(-50%, -50%) translate(${tOff.x}px,${tOff.y}px)`,\n pointerEvents: \"none\",\n zIndex: 10,\n }}\n className=\"nodrag nopan\"\n >\n <span style={{\n padding: \"2px 6px\",\n fontSize: 10,\n fontWeight: \"bold\",\n fontFamily: \"monospace\",\n borderRadius: 4,\n background: \"white\",\n border: \"1px solid #d1d5db\",\n color: \"#374151\",\n boxShadow: \"0 1px 2px rgba(0,0,0,0.05)\",\n }}>\n {relDef.targetLabel}\n </span>\n </div>\n )}\n </>\n );\n })()}\n {label && showLabelByLength && (\n <div\n style={{\n position: \"absolute\",\n transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,\n pointerEvents: \"all\",\n zIndex: 10,\n background: \"white\",\n padding: \"2px 10px\",\n borderRadius: 6,\n fontSize: 12,\n fontWeight: 500,\n border: \"1px solid #e2e8f0\",\n color: \"#475569\",\n boxShadow: \"0 1px 2px rgba(0,0,0,0.05)\",\n }}\n className=\"nodrag nopan\"\n >\n {label}\n </div>\n )}\n </EdgeLabelRenderer>\n </>\n );\n}\n\nexport const AvoidNodesEdge = memo(AvoidNodesEdgeComponent);\n"],"mappings":"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","names":[]}
|