@particle-academy/react-fancy 2.8.1 → 2.10.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/dist/index.cjs +808 -129
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -5
- package/dist/index.d.ts +54 -5
- package/dist/index.js +808 -129
- package/dist/index.js.map +1 -1
- package/docs/Canvas.md +5 -2
- package/docs/Diagram.md +34 -2
- package/docs/TreeNav.md +27 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11461,7 +11461,7 @@ function useCanvas() {
|
|
|
11461
11461
|
return ctx;
|
|
11462
11462
|
}
|
|
11463
11463
|
function CanvasNode({ children, id, x, y, draggable, onPositionChange, className, style }) {
|
|
11464
|
-
const { registerNode, unregisterNode, viewport } = useCanvas();
|
|
11464
|
+
const { registerNode, unregisterNode, viewport, gridSize, snapToGrid } = useCanvas();
|
|
11465
11465
|
const nodeRef = useRef(null);
|
|
11466
11466
|
const isDragging = useRef(false);
|
|
11467
11467
|
const dragStart = useRef({ mouseX: 0, mouseY: 0, nodeX: 0, nodeY: 0 });
|
|
@@ -11494,9 +11494,15 @@ function CanvasNode({ children, id, x, y, draggable, onPositionChange, className
|
|
|
11494
11494
|
if (!isDragging.current) return;
|
|
11495
11495
|
const dx = (e.clientX - dragStart.current.mouseX) / viewport.zoom;
|
|
11496
11496
|
const dy = (e.clientY - dragStart.current.mouseY) / viewport.zoom;
|
|
11497
|
-
|
|
11497
|
+
let nx = dragStart.current.nodeX + dx;
|
|
11498
|
+
let ny = dragStart.current.nodeY + dy;
|
|
11499
|
+
if (snapToGrid && gridSize > 0) {
|
|
11500
|
+
nx = Math.round(nx / gridSize) * gridSize;
|
|
11501
|
+
ny = Math.round(ny / gridSize) * gridSize;
|
|
11502
|
+
}
|
|
11503
|
+
onPositionChange?.(nx, ny);
|
|
11498
11504
|
},
|
|
11499
|
-
[viewport.zoom, onPositionChange]
|
|
11505
|
+
[viewport.zoom, onPositionChange, snapToGrid, gridSize]
|
|
11500
11506
|
);
|
|
11501
11507
|
const handlePointerUp = useCallback(() => {
|
|
11502
11508
|
isDragging.current = false;
|
|
@@ -11744,6 +11750,10 @@ function CanvasRoot({
|
|
|
11744
11750
|
pannable = true,
|
|
11745
11751
|
zoomable = true,
|
|
11746
11752
|
showGrid = false,
|
|
11753
|
+
gridStyle = "dots",
|
|
11754
|
+
gridSize = 20,
|
|
11755
|
+
gridColor = "rgb(161 161 170 / 0.3)",
|
|
11756
|
+
snapToGrid = false,
|
|
11747
11757
|
fitOnMount = false,
|
|
11748
11758
|
className,
|
|
11749
11759
|
style
|
|
@@ -11761,8 +11771,8 @@ function CanvasRoot({
|
|
|
11761
11771
|
containerRef
|
|
11762
11772
|
});
|
|
11763
11773
|
const ctx = useMemo(
|
|
11764
|
-
() => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef }),
|
|
11765
|
-
[viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion]
|
|
11774
|
+
() => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef, gridSize, snapToGrid }),
|
|
11775
|
+
[viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, gridSize, snapToGrid]
|
|
11766
11776
|
);
|
|
11767
11777
|
const hasFitted = useRef(false);
|
|
11768
11778
|
useEffect(() => {
|
|
@@ -11818,9 +11828,13 @@ function CanvasRoot({
|
|
|
11818
11828
|
{
|
|
11819
11829
|
"data-canvas-bg": "",
|
|
11820
11830
|
className: "absolute inset-0",
|
|
11821
|
-
style: showGrid ? {
|
|
11822
|
-
backgroundImage: `
|
|
11823
|
-
backgroundSize: `${
|
|
11831
|
+
style: showGrid && gridStyle !== "none" ? gridStyle === "lines" ? {
|
|
11832
|
+
backgroundImage: `linear-gradient(to right, ${gridColor} 1px, transparent 1px), linear-gradient(to bottom, ${gridColor} 1px, transparent 1px)`,
|
|
11833
|
+
backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
|
|
11834
|
+
backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
|
|
11835
|
+
} : {
|
|
11836
|
+
backgroundImage: `radial-gradient(circle, ${gridColor} 1px, transparent 1px)`,
|
|
11837
|
+
backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
|
|
11824
11838
|
backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
|
|
11825
11839
|
} : void 0
|
|
11826
11840
|
}
|
|
@@ -11960,65 +11974,615 @@ function DiagramEntity({
|
|
|
11960
11974
|
) });
|
|
11961
11975
|
}
|
|
11962
11976
|
DiagramEntity.displayName = "DiagramEntity";
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
11977
|
+
|
|
11978
|
+
// src/components/Diagram/diagram.markers.ts
|
|
11979
|
+
function defaultMarkersForType(type) {
|
|
11980
|
+
switch (type) {
|
|
11981
|
+
case "one-to-one":
|
|
11982
|
+
return { fromMarker: "one", toMarker: "one", lineStyle: "solid" };
|
|
11983
|
+
case "one-to-many":
|
|
11984
|
+
return { fromMarker: "one", toMarker: "many", lineStyle: "solid" };
|
|
11985
|
+
case "many-to-one":
|
|
11986
|
+
return { fromMarker: "many", toMarker: "one", lineStyle: "solid" };
|
|
11987
|
+
case "many-to-many":
|
|
11988
|
+
return { fromMarker: "many", toMarker: "many", lineStyle: "solid" };
|
|
11989
|
+
case "association":
|
|
11990
|
+
return { fromMarker: "none", toMarker: "arrow", lineStyle: "solid" };
|
|
11991
|
+
case "aggregation":
|
|
11992
|
+
return { fromMarker: "diamond-open", toMarker: "none", lineStyle: "solid" };
|
|
11993
|
+
case "composition":
|
|
11994
|
+
return { fromMarker: "diamond", toMarker: "none", lineStyle: "solid" };
|
|
11995
|
+
case "inheritance":
|
|
11996
|
+
return { fromMarker: "none", toMarker: "triangle-open", lineStyle: "solid" };
|
|
11997
|
+
case "implementation":
|
|
11998
|
+
return { fromMarker: "none", toMarker: "triangle-open", lineStyle: "dashed" };
|
|
11999
|
+
case "dependency":
|
|
12000
|
+
return { fromMarker: "none", toMarker: "arrow", lineStyle: "dashed" };
|
|
12001
|
+
default:
|
|
12002
|
+
return { fromMarker: "none", toMarker: "none", lineStyle: "solid" };
|
|
12003
|
+
}
|
|
12004
|
+
}
|
|
12005
|
+
var SIZE = 12;
|
|
12006
|
+
function renderMarker(marker, pt, direction) {
|
|
12007
|
+
if (typeof marker === "string" && marker.startsWith("emoji:")) {
|
|
12008
|
+
return { paths: [], text: marker.slice(6) };
|
|
12009
|
+
}
|
|
12010
|
+
switch (marker) {
|
|
12011
|
+
case "none":
|
|
12012
|
+
return null;
|
|
12013
|
+
case "arrow":
|
|
12014
|
+
return { paths: [{ d: arrowPath(pt, direction), fill: "stroke" }] };
|
|
12015
|
+
case "arrow-open":
|
|
12016
|
+
return { paths: [{ d: arrowPath(pt, direction), fill: "none" }] };
|
|
12017
|
+
case "circle":
|
|
12018
|
+
return { paths: [{ d: circlePath(pt), fill: "stroke" }] };
|
|
12019
|
+
case "circle-open":
|
|
12020
|
+
return { paths: [{ d: circlePath(pt), fill: "background" }] };
|
|
12021
|
+
case "square":
|
|
12022
|
+
return { paths: [{ d: squarePath(pt, direction), fill: "stroke" }] };
|
|
12023
|
+
case "square-open":
|
|
12024
|
+
return { paths: [{ d: squarePath(pt, direction), fill: "background" }] };
|
|
12025
|
+
case "diamond":
|
|
12026
|
+
return { paths: [{ d: diamondPath(pt, direction), fill: "stroke" }] };
|
|
12027
|
+
case "diamond-open":
|
|
12028
|
+
return { paths: [{ d: diamondPath(pt, direction), fill: "background" }] };
|
|
12029
|
+
case "triangle":
|
|
12030
|
+
return { paths: [{ d: trianglePath(pt, direction), fill: "stroke" }] };
|
|
12031
|
+
case "triangle-open":
|
|
12032
|
+
return { paths: [{ d: trianglePath(pt, direction), fill: "background" }] };
|
|
12033
|
+
case "cross":
|
|
12034
|
+
return { paths: [{ d: crossPath(pt, direction), fill: "none" }] };
|
|
12035
|
+
case "one":
|
|
12036
|
+
return { paths: [{ d: oneSymbol(pt, direction), fill: "none" }] };
|
|
12037
|
+
case "many":
|
|
12038
|
+
return { paths: [{ d: crowFootSymbol(pt, direction), fill: "none" }] };
|
|
12039
|
+
case "optional-one":
|
|
12040
|
+
return {
|
|
12041
|
+
paths: [
|
|
12042
|
+
{ d: circleOuter(pt, direction), fill: "background" },
|
|
12043
|
+
{ d: oneSymbolOffset(pt, direction), fill: "none" }
|
|
12044
|
+
]
|
|
12045
|
+
};
|
|
12046
|
+
case "optional-many":
|
|
12047
|
+
return {
|
|
12048
|
+
paths: [
|
|
12049
|
+
{ d: circleOuter(pt, direction), fill: "background" },
|
|
12050
|
+
{ d: crowFootSymbolOffset(pt, direction), fill: "none" }
|
|
12051
|
+
]
|
|
12052
|
+
};
|
|
12053
|
+
default:
|
|
12054
|
+
if (typeof marker === "string" && marker !== "") {
|
|
12055
|
+
return { paths: [], text: marker };
|
|
12056
|
+
}
|
|
12057
|
+
return null;
|
|
12058
|
+
}
|
|
12059
|
+
}
|
|
12060
|
+
function markerInset(marker) {
|
|
12061
|
+
if (marker === "none" || marker === void 0) return 0;
|
|
12062
|
+
if (typeof marker === "string" && (marker.startsWith("emoji:") || !KNOWN_MARKERS.has(marker))) {
|
|
12063
|
+
return SIZE;
|
|
12064
|
+
}
|
|
12065
|
+
switch (marker) {
|
|
12066
|
+
case "circle":
|
|
12067
|
+
case "circle-open":
|
|
12068
|
+
return SIZE * 0.6;
|
|
12069
|
+
case "one":
|
|
12070
|
+
return 0;
|
|
12071
|
+
// bar sits AT endpoint
|
|
12072
|
+
case "many":
|
|
12073
|
+
return SIZE;
|
|
12074
|
+
case "optional-one":
|
|
12075
|
+
return SIZE * 1.2;
|
|
12076
|
+
case "optional-many":
|
|
12077
|
+
return SIZE * 1.8;
|
|
12078
|
+
default:
|
|
12079
|
+
return SIZE;
|
|
12080
|
+
}
|
|
12081
|
+
}
|
|
12082
|
+
var KNOWN_MARKERS = /* @__PURE__ */ new Set([
|
|
12083
|
+
"none",
|
|
12084
|
+
"arrow",
|
|
12085
|
+
"arrow-open",
|
|
12086
|
+
"circle",
|
|
12087
|
+
"circle-open",
|
|
12088
|
+
"square",
|
|
12089
|
+
"square-open",
|
|
12090
|
+
"diamond",
|
|
12091
|
+
"diamond-open",
|
|
12092
|
+
"triangle",
|
|
12093
|
+
"triangle-open",
|
|
12094
|
+
"one",
|
|
12095
|
+
"many",
|
|
12096
|
+
"optional-one",
|
|
12097
|
+
"optional-many",
|
|
12098
|
+
"cross"
|
|
12099
|
+
]);
|
|
12100
|
+
function dirVec(direction) {
|
|
11968
12101
|
switch (direction) {
|
|
11969
12102
|
case "left":
|
|
12103
|
+
return [-1, 0];
|
|
11970
12104
|
case "right":
|
|
11971
|
-
return
|
|
12105
|
+
return [1, 0];
|
|
11972
12106
|
case "up":
|
|
12107
|
+
return [0, -1];
|
|
11973
12108
|
case "down":
|
|
11974
|
-
return
|
|
12109
|
+
return [0, 1];
|
|
11975
12110
|
}
|
|
11976
12111
|
}
|
|
11977
|
-
function
|
|
11978
|
-
const s = SYMBOL_SIZE;
|
|
11979
|
-
const spread = s * 0.8;
|
|
11980
|
-
let tip;
|
|
12112
|
+
function perpVec(direction) {
|
|
11981
12113
|
switch (direction) {
|
|
11982
|
-
case "right":
|
|
11983
|
-
tip = { x: pt.x - s, y: pt.y };
|
|
11984
|
-
return [
|
|
11985
|
-
`M${pt.x},${pt.y - spread} L${tip.x},${tip.y}`,
|
|
11986
|
-
`M${pt.x},${pt.y} L${tip.x},${tip.y}`,
|
|
11987
|
-
`M${pt.x},${pt.y + spread} L${tip.x},${tip.y}`,
|
|
11988
|
-
// bar at entity edge
|
|
11989
|
-
`M${pt.x},${pt.y - spread} L${pt.x},${pt.y + spread}`
|
|
11990
|
-
].join(" ");
|
|
11991
12114
|
case "left":
|
|
11992
|
-
|
|
11993
|
-
return [
|
|
11994
|
-
`M${pt.x},${pt.y - spread} L${tip.x},${tip.y}`,
|
|
11995
|
-
`M${pt.x},${pt.y} L${tip.x},${tip.y}`,
|
|
11996
|
-
`M${pt.x},${pt.y + spread} L${tip.x},${tip.y}`,
|
|
11997
|
-
`M${pt.x},${pt.y - spread} L${pt.x},${pt.y + spread}`
|
|
11998
|
-
].join(" ");
|
|
11999
|
-
case "down":
|
|
12000
|
-
tip = { x: pt.x, y: pt.y - s };
|
|
12001
|
-
return [
|
|
12002
|
-
`M${pt.x - spread},${pt.y} L${tip.x},${tip.y}`,
|
|
12003
|
-
`M${pt.x},${pt.y} L${tip.x},${tip.y}`,
|
|
12004
|
-
`M${pt.x + spread},${pt.y} L${tip.x},${tip.y}`,
|
|
12005
|
-
`M${pt.x - spread},${pt.y} L${pt.x + spread},${pt.y}`
|
|
12006
|
-
].join(" ");
|
|
12115
|
+
case "right":
|
|
12116
|
+
return [0, 1];
|
|
12007
12117
|
case "up":
|
|
12008
|
-
|
|
12009
|
-
return [
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
const
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
return
|
|
12118
|
+
case "down":
|
|
12119
|
+
return [1, 0];
|
|
12120
|
+
}
|
|
12121
|
+
}
|
|
12122
|
+
function arrowPath(pt, direction, _filled) {
|
|
12123
|
+
const [dx, dy] = dirVec(direction);
|
|
12124
|
+
const [px, py] = perpVec(direction);
|
|
12125
|
+
const tipX = pt.x + dx * SIZE;
|
|
12126
|
+
const tipY = pt.y + dy * SIZE;
|
|
12127
|
+
const baseAX = pt.x + px * (SIZE * 0.55);
|
|
12128
|
+
const baseAY = pt.y + py * (SIZE * 0.55);
|
|
12129
|
+
const baseBX = pt.x - px * (SIZE * 0.55);
|
|
12130
|
+
const baseBY = pt.y - py * (SIZE * 0.55);
|
|
12131
|
+
return `M${baseAX},${baseAY} L${tipX},${tipY} L${baseBX},${baseBY} Z`;
|
|
12132
|
+
}
|
|
12133
|
+
function circlePath(pt, _filled) {
|
|
12134
|
+
const r = SIZE * 0.45;
|
|
12135
|
+
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`;
|
|
12136
|
+
}
|
|
12137
|
+
function squarePath(pt, direction, _filled) {
|
|
12138
|
+
const [dx, dy] = dirVec(direction);
|
|
12139
|
+
const [px, py] = perpVec(direction);
|
|
12140
|
+
const half = SIZE * 0.5;
|
|
12141
|
+
const cx = pt.x + dx * half;
|
|
12142
|
+
const cy = pt.y + dy * half;
|
|
12143
|
+
const tlX = cx - px * half - dx * half;
|
|
12144
|
+
const tlY = cy - py * half - dy * half;
|
|
12145
|
+
const trX = cx + px * half - dx * half;
|
|
12146
|
+
const trY = cy + py * half - dy * half;
|
|
12147
|
+
const brX = cx + px * half + dx * half;
|
|
12148
|
+
const brY = cy + py * half + dy * half;
|
|
12149
|
+
const blX = cx - px * half + dx * half;
|
|
12150
|
+
const blY = cy - py * half + dy * half;
|
|
12151
|
+
return `M${tlX},${tlY} L${trX},${trY} L${brX},${brY} L${blX},${blY} Z`;
|
|
12152
|
+
}
|
|
12153
|
+
function diamondPath(pt, direction, _filled) {
|
|
12154
|
+
const [dx, dy] = dirVec(direction);
|
|
12155
|
+
const [px, py] = perpVec(direction);
|
|
12156
|
+
const len = SIZE * 1.2;
|
|
12157
|
+
const cx = pt.x + dx * (len / 2);
|
|
12158
|
+
const cy = pt.y + dy * (len / 2);
|
|
12159
|
+
const aX = pt.x;
|
|
12160
|
+
const aY = pt.y;
|
|
12161
|
+
const bX = cx + px * (SIZE * 0.45);
|
|
12162
|
+
const bY = cy + py * (SIZE * 0.45);
|
|
12163
|
+
const tX = pt.x + dx * len;
|
|
12164
|
+
const tY = pt.y + dy * len;
|
|
12165
|
+
const dX = cx - px * (SIZE * 0.45);
|
|
12166
|
+
const dY = cy - py * (SIZE * 0.45);
|
|
12167
|
+
return `M${aX},${aY} L${bX},${bY} L${tX},${tY} L${dX},${dY} Z`;
|
|
12168
|
+
}
|
|
12169
|
+
function trianglePath(pt, direction, _filled) {
|
|
12170
|
+
const [dx, dy] = dirVec(direction);
|
|
12171
|
+
const [px, py] = perpVec(direction);
|
|
12172
|
+
const len = SIZE * 1.1;
|
|
12173
|
+
const baseCX = pt.x + dx * len;
|
|
12174
|
+
const baseCY = pt.y + dy * len;
|
|
12175
|
+
const baseAX = baseCX + px * (SIZE * 0.6);
|
|
12176
|
+
const baseAY = baseCY + py * (SIZE * 0.6);
|
|
12177
|
+
const baseBX = baseCX - px * (SIZE * 0.6);
|
|
12178
|
+
const baseBY = baseCY - py * (SIZE * 0.6);
|
|
12179
|
+
return `M${pt.x},${pt.y} L${baseAX},${baseAY} L${baseBX},${baseBY} Z`;
|
|
12180
|
+
}
|
|
12181
|
+
function crossPath(pt, direction) {
|
|
12182
|
+
const [dx, dy] = dirVec(direction);
|
|
12183
|
+
const [px, py] = perpVec(direction);
|
|
12184
|
+
const s = SIZE * 0.5;
|
|
12185
|
+
const cx = pt.x + dx * (SIZE * 0.5);
|
|
12186
|
+
const cy = pt.y + dy * (SIZE * 0.5);
|
|
12187
|
+
return [
|
|
12188
|
+
`M${cx - s * (px + dx)},${cy - s * (py + dy)} L${cx + s * (px + dx)},${cy + s * (py + dy)}`,
|
|
12189
|
+
`M${cx - s * (px - dx)},${cy - s * (py - dy)} L${cx + s * (px - dx)},${cy + s * (py - dy)}`
|
|
12190
|
+
].join(" ");
|
|
12191
|
+
}
|
|
12192
|
+
function oneSymbol(pt, direction) {
|
|
12193
|
+
const [, py] = perpVec(direction);
|
|
12194
|
+
const [px] = perpVec(direction);
|
|
12195
|
+
const half = SIZE * 0.6;
|
|
12196
|
+
return `M${pt.x - px * half},${pt.y - py * half} L${pt.x + px * half},${pt.y + py * half}`;
|
|
12197
|
+
}
|
|
12198
|
+
function crowFootSymbol(pt, direction) {
|
|
12199
|
+
const [dx, dy] = dirVec(direction);
|
|
12200
|
+
const [px, py] = perpVec(direction);
|
|
12201
|
+
const tipX = pt.x + dx * SIZE;
|
|
12202
|
+
const tipY = pt.y + dy * SIZE;
|
|
12203
|
+
const spread = SIZE * 0.8;
|
|
12204
|
+
const aX = pt.x + px * spread, aY = pt.y + py * spread;
|
|
12205
|
+
const cX = pt.x - px * spread, cY = pt.y - py * spread;
|
|
12206
|
+
return [
|
|
12207
|
+
`M${aX},${aY} L${tipX},${tipY}`,
|
|
12208
|
+
`M${pt.x},${pt.y} L${tipX},${tipY}`,
|
|
12209
|
+
`M${cX},${cY} L${tipX},${tipY}`,
|
|
12210
|
+
`M${aX},${aY} L${cX},${cY}`
|
|
12211
|
+
].join(" ");
|
|
12212
|
+
}
|
|
12213
|
+
function circleOuter(pt, direction) {
|
|
12214
|
+
const [dx, dy] = dirVec(direction);
|
|
12215
|
+
const r = SIZE * 0.4;
|
|
12216
|
+
const cx = pt.x + dx * (SIZE * 0.4 + r);
|
|
12217
|
+
const cy = pt.y + dy * (SIZE * 0.4 + r);
|
|
12218
|
+
return `M${cx - r},${cy} a${r},${r} 0 1 0 ${r * 2},0 a${r},${r} 0 1 0 ${-r * 2},0 Z`;
|
|
12219
|
+
}
|
|
12220
|
+
function oneSymbolOffset(pt, direction) {
|
|
12221
|
+
const [px, py] = perpVec(direction);
|
|
12222
|
+
const half = SIZE * 0.6;
|
|
12223
|
+
return `M${pt.x - px * half},${pt.y - py * half} L${pt.x + px * half},${pt.y + py * half}`;
|
|
12224
|
+
}
|
|
12225
|
+
function crowFootSymbolOffset(pt, direction) {
|
|
12226
|
+
const [dx, dy] = dirVec(direction);
|
|
12227
|
+
const [px, py] = perpVec(direction);
|
|
12228
|
+
const inset = SIZE * 0.8;
|
|
12229
|
+
const startX = pt.x + dx * inset;
|
|
12230
|
+
const startY = pt.y + dy * inset;
|
|
12231
|
+
const tipX = pt.x + dx * (inset + SIZE);
|
|
12232
|
+
const tipY = pt.y + dy * (inset + SIZE);
|
|
12233
|
+
const spread = SIZE * 0.8;
|
|
12234
|
+
const aX = startX + px * spread, aY = startY + py * spread;
|
|
12235
|
+
const cX = startX - px * spread, cY = startY - py * spread;
|
|
12236
|
+
return [
|
|
12237
|
+
`M${aX},${aY} L${tipX},${tipY}`,
|
|
12238
|
+
`M${startX},${startY} L${tipX},${tipY}`,
|
|
12239
|
+
`M${cX},${cY} L${tipX},${tipY}`
|
|
12240
|
+
].join(" ");
|
|
12241
|
+
}
|
|
12242
|
+
|
|
12243
|
+
// src/components/Diagram/diagram.routing.ts
|
|
12244
|
+
var STUB = 24;
|
|
12245
|
+
var DODGE_PADDING = 16;
|
|
12246
|
+
var MAX_DODGE_ITERATIONS = 6;
|
|
12247
|
+
function pickAnchors(from, to, fromY, toY) {
|
|
12248
|
+
const fcx = from.x + from.width / 2;
|
|
12249
|
+
const fcy = from.y + from.height / 2;
|
|
12250
|
+
const tcx = to.x + to.width / 2;
|
|
12251
|
+
const tcy = to.y + to.height / 2;
|
|
12252
|
+
const dx = tcx - fcx;
|
|
12253
|
+
const dy = tcy - fcy;
|
|
12254
|
+
let fromSide, toSide;
|
|
12255
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
12256
|
+
fromSide = dx >= 0 ? "right" : "left";
|
|
12257
|
+
toSide = dx >= 0 ? "left" : "right";
|
|
12258
|
+
} else {
|
|
12259
|
+
fromSide = dy >= 0 ? "bottom" : "top";
|
|
12260
|
+
toSide = dy >= 0 ? "top" : "bottom";
|
|
12261
|
+
}
|
|
12262
|
+
return {
|
|
12263
|
+
from: anchorOnSide(from, fromSide, fromY),
|
|
12264
|
+
to: anchorOnSide(to, toSide, toY)
|
|
12265
|
+
};
|
|
12266
|
+
}
|
|
12267
|
+
function anchorOnSide(box, side, fieldY) {
|
|
12268
|
+
switch (side) {
|
|
12269
|
+
case "right":
|
|
12270
|
+
return { side, x: box.x + box.width, y: box.y + (fieldY ?? box.height / 2) };
|
|
12271
|
+
case "left":
|
|
12272
|
+
return { side, x: box.x, y: box.y + (fieldY ?? box.height / 2) };
|
|
12273
|
+
case "top":
|
|
12274
|
+
return { side, x: box.x + box.width / 2, y: box.y };
|
|
12275
|
+
case "bottom":
|
|
12276
|
+
return { side, x: box.x + box.width / 2, y: box.y + box.height };
|
|
12277
|
+
}
|
|
12278
|
+
}
|
|
12279
|
+
function stubOut(a, distance2 = STUB) {
|
|
12280
|
+
switch (a.side) {
|
|
12281
|
+
case "right":
|
|
12282
|
+
return { x: a.x + distance2, y: a.y };
|
|
12283
|
+
case "left":
|
|
12284
|
+
return { x: a.x - distance2, y: a.y };
|
|
12285
|
+
case "top":
|
|
12286
|
+
return { x: a.x, y: a.y - distance2 };
|
|
12287
|
+
case "bottom":
|
|
12288
|
+
return { x: a.x, y: a.y + distance2 };
|
|
12289
|
+
}
|
|
12290
|
+
}
|
|
12291
|
+
function manhattanPath(from, to, obstacles = []) {
|
|
12292
|
+
const f = { x: from.x, y: from.y };
|
|
12293
|
+
const t = { x: to.x, y: to.y };
|
|
12294
|
+
const fs = stubOut(from);
|
|
12295
|
+
const ts = stubOut(to);
|
|
12296
|
+
const fHoriz = from.side === "left" || from.side === "right";
|
|
12297
|
+
const tHoriz = to.side === "left" || to.side === "right";
|
|
12298
|
+
if (fHoriz && tHoriz) {
|
|
12299
|
+
const midX = pickClearMidX((fs.x + ts.x) / 2, fs.y, ts.y, obstacles);
|
|
12300
|
+
return uniqPath([
|
|
12301
|
+
f,
|
|
12302
|
+
fs,
|
|
12303
|
+
{ x: midX, y: fs.y },
|
|
12304
|
+
{ x: midX, y: ts.y },
|
|
12305
|
+
ts,
|
|
12306
|
+
t
|
|
12307
|
+
]);
|
|
12308
|
+
}
|
|
12309
|
+
if (!fHoriz && !tHoriz) {
|
|
12310
|
+
const midY = pickClearMidY((fs.y + ts.y) / 2, fs.x, ts.x, obstacles);
|
|
12311
|
+
return uniqPath([
|
|
12312
|
+
f,
|
|
12313
|
+
fs,
|
|
12314
|
+
{ x: fs.x, y: midY },
|
|
12315
|
+
{ x: ts.x, y: midY },
|
|
12316
|
+
ts,
|
|
12317
|
+
t
|
|
12318
|
+
]);
|
|
12319
|
+
}
|
|
12320
|
+
if (fHoriz) {
|
|
12321
|
+
return uniqPath([
|
|
12322
|
+
f,
|
|
12323
|
+
fs,
|
|
12324
|
+
{ x: ts.x, y: fs.y },
|
|
12325
|
+
ts,
|
|
12326
|
+
t
|
|
12327
|
+
]);
|
|
12328
|
+
}
|
|
12329
|
+
return uniqPath([
|
|
12330
|
+
f,
|
|
12331
|
+
fs,
|
|
12332
|
+
{ x: fs.x, y: ts.y },
|
|
12333
|
+
ts,
|
|
12334
|
+
t
|
|
12335
|
+
]);
|
|
12336
|
+
}
|
|
12337
|
+
function pickClearMidX(idealX, y1, y2, obstacles) {
|
|
12338
|
+
if (obstacles.length === 0) return idealX;
|
|
12339
|
+
const yMin = Math.min(y1, y2);
|
|
12340
|
+
const yMax = Math.max(y1, y2);
|
|
12341
|
+
let midX = idealX;
|
|
12342
|
+
for (let i = 0; i < 4; i++) {
|
|
12343
|
+
let shifted = false;
|
|
12344
|
+
for (const ob of obstacles) {
|
|
12345
|
+
if (ob.y + ob.height + DODGE_PADDING < yMin) continue;
|
|
12346
|
+
if (ob.y - DODGE_PADDING > yMax) continue;
|
|
12347
|
+
const left = ob.x - DODGE_PADDING;
|
|
12348
|
+
const right = ob.x + ob.width + DODGE_PADDING;
|
|
12349
|
+
if (midX > left && midX < right) {
|
|
12350
|
+
midX = Math.abs(midX - left) <= Math.abs(midX - right) ? left - 1 : right + 1;
|
|
12351
|
+
shifted = true;
|
|
12352
|
+
}
|
|
12353
|
+
}
|
|
12354
|
+
if (!shifted) break;
|
|
12355
|
+
}
|
|
12356
|
+
return midX;
|
|
12357
|
+
}
|
|
12358
|
+
function pickClearMidY(idealY, x1, x2, obstacles) {
|
|
12359
|
+
if (obstacles.length === 0) return idealY;
|
|
12360
|
+
const xMin = Math.min(x1, x2);
|
|
12361
|
+
const xMax = Math.max(x1, x2);
|
|
12362
|
+
let midY = idealY;
|
|
12363
|
+
for (let i = 0; i < 4; i++) {
|
|
12364
|
+
let shifted = false;
|
|
12365
|
+
for (const ob of obstacles) {
|
|
12366
|
+
if (ob.x + ob.width + DODGE_PADDING < xMin) continue;
|
|
12367
|
+
if (ob.x - DODGE_PADDING > xMax) continue;
|
|
12368
|
+
const top = ob.y - DODGE_PADDING;
|
|
12369
|
+
const bot = ob.y + ob.height + DODGE_PADDING;
|
|
12370
|
+
if (midY > top && midY < bot) {
|
|
12371
|
+
midY = Math.abs(midY - top) <= Math.abs(midY - bot) ? top - 1 : bot + 1;
|
|
12372
|
+
shifted = true;
|
|
12373
|
+
}
|
|
12374
|
+
}
|
|
12375
|
+
if (!shifted) break;
|
|
12376
|
+
}
|
|
12377
|
+
return midY;
|
|
12378
|
+
}
|
|
12379
|
+
function uniqPath(points) {
|
|
12380
|
+
const out = [];
|
|
12381
|
+
for (const p of points) {
|
|
12382
|
+
const last = out[out.length - 1];
|
|
12383
|
+
if (!last || Math.abs(last.x - p.x) > 0.5 || Math.abs(last.y - p.y) > 0.5) {
|
|
12384
|
+
out.push(p);
|
|
12385
|
+
}
|
|
12386
|
+
}
|
|
12387
|
+
return out;
|
|
12388
|
+
}
|
|
12389
|
+
function dodgeObstacles(path, obstacles, padding = DODGE_PADDING) {
|
|
12390
|
+
if (obstacles.length === 0 || path.length < 2) return path;
|
|
12391
|
+
let working = path.slice();
|
|
12392
|
+
for (let iter = 0; iter < MAX_DODGE_ITERATIONS; iter++) {
|
|
12393
|
+
let dodged = false;
|
|
12394
|
+
const result = [working[0]];
|
|
12395
|
+
for (let i = 1; i < working.length; i++) {
|
|
12396
|
+
const a = result[result.length - 1];
|
|
12397
|
+
const b = working[i];
|
|
12398
|
+
const detour = detourAround(a, b, obstacles, padding);
|
|
12399
|
+
if (detour.length === 0) {
|
|
12400
|
+
result.push(b);
|
|
12401
|
+
} else {
|
|
12402
|
+
for (const p of detour) result.push(p);
|
|
12403
|
+
result.push(b);
|
|
12404
|
+
dodged = true;
|
|
12405
|
+
}
|
|
12406
|
+
}
|
|
12407
|
+
working = uniqPath(result);
|
|
12408
|
+
if (!dodged) break;
|
|
12409
|
+
}
|
|
12410
|
+
return working;
|
|
12411
|
+
}
|
|
12412
|
+
function detourAround(a, b, obstacles, padding) {
|
|
12413
|
+
const isHorizontal = Math.abs(a.y - b.y) < 0.5;
|
|
12414
|
+
const isVertical = Math.abs(a.x - b.x) < 0.5;
|
|
12415
|
+
if (!isHorizontal && !isVertical) return [];
|
|
12416
|
+
let best = null;
|
|
12417
|
+
for (const r of obstacles) {
|
|
12418
|
+
const expanded = expandRect(r, padding);
|
|
12419
|
+
if (!segmentCrossesRect(a, b, expanded)) continue;
|
|
12420
|
+
let entry, exit;
|
|
12421
|
+
if (isHorizontal) {
|
|
12422
|
+
entry = b.x > a.x ? expanded.x : expanded.x + expanded.width;
|
|
12423
|
+
exit = b.x > a.x ? expanded.x + expanded.width : expanded.x;
|
|
12424
|
+
} else {
|
|
12425
|
+
entry = b.y > a.y ? expanded.y : expanded.y + expanded.height;
|
|
12426
|
+
exit = b.y > a.y ? expanded.y + expanded.height : expanded.y;
|
|
12427
|
+
}
|
|
12428
|
+
const dist = isHorizontal ? Math.abs(entry - a.x) : Math.abs(entry - a.y);
|
|
12429
|
+
if (!best || dist < (isHorizontal ? Math.abs(best.entry - a.x) : Math.abs(best.entry - a.y))) {
|
|
12430
|
+
best = { rect: expanded, entry, exit };
|
|
12431
|
+
}
|
|
12432
|
+
}
|
|
12433
|
+
if (!best) return [];
|
|
12434
|
+
if (isHorizontal) {
|
|
12435
|
+
const aboveY = best.rect.y - 1;
|
|
12436
|
+
const belowY = best.rect.y + best.rect.height + 1;
|
|
12437
|
+
const detourY = Math.abs(a.y - aboveY) <= Math.abs(a.y - belowY) ? aboveY : belowY;
|
|
12438
|
+
return [
|
|
12439
|
+
{ x: best.entry, y: a.y },
|
|
12440
|
+
{ x: best.entry, y: detourY },
|
|
12441
|
+
{ x: best.exit, y: detourY },
|
|
12442
|
+
{ x: best.exit, y: a.y }
|
|
12443
|
+
];
|
|
12444
|
+
} else {
|
|
12445
|
+
const leftX = best.rect.x - 1;
|
|
12446
|
+
const rightX = best.rect.x + best.rect.width + 1;
|
|
12447
|
+
const detourX = Math.abs(a.x - leftX) <= Math.abs(a.x - rightX) ? leftX : rightX;
|
|
12448
|
+
return [
|
|
12449
|
+
{ x: a.x, y: best.entry },
|
|
12450
|
+
{ x: detourX, y: best.entry },
|
|
12451
|
+
{ x: detourX, y: best.exit },
|
|
12452
|
+
{ x: a.x, y: best.exit }
|
|
12453
|
+
];
|
|
12454
|
+
}
|
|
12455
|
+
}
|
|
12456
|
+
function expandRect(r, padding) {
|
|
12457
|
+
return {
|
|
12458
|
+
x: r.x - padding,
|
|
12459
|
+
y: r.y - padding,
|
|
12460
|
+
width: r.width + padding * 2,
|
|
12461
|
+
height: r.height + padding * 2
|
|
12462
|
+
};
|
|
12463
|
+
}
|
|
12464
|
+
function segmentCrossesRect(a, b, rect) {
|
|
12465
|
+
const xMin = Math.min(a.x, b.x);
|
|
12466
|
+
const xMax = Math.max(a.x, b.x);
|
|
12467
|
+
const yMin = Math.min(a.y, b.y);
|
|
12468
|
+
const yMax = Math.max(a.y, b.y);
|
|
12469
|
+
if (xMax < rect.x) return false;
|
|
12470
|
+
if (xMin > rect.x + rect.width) return false;
|
|
12471
|
+
if (yMax < rect.y) return false;
|
|
12472
|
+
if (yMin > rect.y + rect.height) return false;
|
|
12473
|
+
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;
|
|
12474
|
+
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;
|
|
12475
|
+
return true;
|
|
12476
|
+
}
|
|
12477
|
+
function pathFromPoints(points, cornerRadius = 8) {
|
|
12478
|
+
if (points.length === 0) return "";
|
|
12479
|
+
if (points.length === 1) return `M${points[0].x},${points[0].y}`;
|
|
12480
|
+
if (points.length === 2 || cornerRadius <= 0) {
|
|
12481
|
+
return points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ");
|
|
12482
|
+
}
|
|
12483
|
+
let d = `M${points[0].x},${points[0].y}`;
|
|
12484
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
12485
|
+
const prev = points[i - 1];
|
|
12486
|
+
const curr = points[i];
|
|
12487
|
+
const next = points[i + 1];
|
|
12488
|
+
const r = Math.min(
|
|
12489
|
+
cornerRadius,
|
|
12490
|
+
distance(prev, curr) / 2,
|
|
12491
|
+
distance(curr, next) / 2
|
|
12492
|
+
);
|
|
12493
|
+
if (r < 1) {
|
|
12494
|
+
d += ` L${curr.x},${curr.y}`;
|
|
12495
|
+
continue;
|
|
12496
|
+
}
|
|
12497
|
+
const beforeX = curr.x + Math.sign(prev.x - curr.x) * r;
|
|
12498
|
+
const beforeY = curr.y + Math.sign(prev.y - curr.y) * r;
|
|
12499
|
+
const afterX = curr.x + Math.sign(next.x - curr.x) * r;
|
|
12500
|
+
const afterY = curr.y + Math.sign(next.y - curr.y) * r;
|
|
12501
|
+
d += ` L${beforeX},${beforeY} Q${curr.x},${curr.y} ${afterX},${afterY}`;
|
|
12502
|
+
}
|
|
12503
|
+
const last = points[points.length - 1];
|
|
12504
|
+
d += ` L${last.x},${last.y}`;
|
|
12505
|
+
return d;
|
|
12506
|
+
}
|
|
12507
|
+
function distance(a, b) {
|
|
12508
|
+
return Math.hypot(b.x - a.x, b.y - a.y);
|
|
12509
|
+
}
|
|
12510
|
+
function bezierPath2(from, to) {
|
|
12511
|
+
const fs = stubOut(from, Math.max(40, distance(from, to) * 0.3));
|
|
12512
|
+
const ts = stubOut(to, Math.max(40, distance(from, to) * 0.3));
|
|
12513
|
+
return `M${from.x},${from.y} C${fs.x},${fs.y} ${ts.x},${ts.y} ${to.x},${to.y}`;
|
|
12514
|
+
}
|
|
12515
|
+
function midPoint(points) {
|
|
12516
|
+
if (points.length === 0) return { x: 0, y: 0 };
|
|
12517
|
+
if (points.length === 1) return points[0];
|
|
12518
|
+
let total = 0;
|
|
12519
|
+
const segLens = [];
|
|
12520
|
+
for (let i = 1; i < points.length; i++) {
|
|
12521
|
+
const len = distance(points[i - 1], points[i]);
|
|
12522
|
+
segLens.push(len);
|
|
12523
|
+
total += len;
|
|
12524
|
+
}
|
|
12525
|
+
let target = total / 2;
|
|
12526
|
+
for (let i = 0; i < segLens.length; i++) {
|
|
12527
|
+
if (target <= segLens[i]) {
|
|
12528
|
+
const t = segLens[i] === 0 ? 0 : target / segLens[i];
|
|
12529
|
+
const a = points[i];
|
|
12530
|
+
const b = points[i + 1];
|
|
12531
|
+
return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
|
|
12532
|
+
}
|
|
12533
|
+
target -= segLens[i];
|
|
12534
|
+
}
|
|
12535
|
+
return points[points.length - 1];
|
|
12536
|
+
}
|
|
12537
|
+
function insetEndpoints(points, inset) {
|
|
12538
|
+
if (points.length < 2) return points;
|
|
12539
|
+
const result = points.slice();
|
|
12540
|
+
if (inset.from > 0) {
|
|
12541
|
+
const a = result[0];
|
|
12542
|
+
const b = result[1];
|
|
12543
|
+
const len = distance(a, b);
|
|
12544
|
+
if (len > inset.from) {
|
|
12545
|
+
const t = inset.from / len;
|
|
12546
|
+
result[0] = { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
|
|
12547
|
+
}
|
|
12548
|
+
}
|
|
12549
|
+
if (inset.to > 0) {
|
|
12550
|
+
const lastIdx = result.length - 1;
|
|
12551
|
+
const a = result[lastIdx];
|
|
12552
|
+
const b = result[lastIdx - 1];
|
|
12553
|
+
const len = distance(a, b);
|
|
12554
|
+
if (len > inset.to) {
|
|
12555
|
+
const t = inset.to / len;
|
|
12556
|
+
result[lastIdx] = { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
|
|
12557
|
+
}
|
|
12558
|
+
}
|
|
12559
|
+
return result;
|
|
12560
|
+
}
|
|
12561
|
+
var HEADER_HEIGHT = 36;
|
|
12562
|
+
var FIELD_HEIGHT = 29;
|
|
12563
|
+
var DEFAULT_COLOR2 = "#71717a";
|
|
12564
|
+
function markerDirection(side) {
|
|
12565
|
+
switch (side) {
|
|
12566
|
+
case "left":
|
|
12567
|
+
return "right";
|
|
12568
|
+
// entity body is to the right of a left-side anchor
|
|
12569
|
+
case "right":
|
|
12570
|
+
return "left";
|
|
12571
|
+
case "top":
|
|
12572
|
+
return "down";
|
|
12573
|
+
case "bottom":
|
|
12574
|
+
return "up";
|
|
12575
|
+
}
|
|
12576
|
+
}
|
|
12577
|
+
function strokeDashArray(style) {
|
|
12578
|
+
switch (style) {
|
|
12579
|
+
case "dashed":
|
|
12580
|
+
return "8 4";
|
|
12581
|
+
case "dotted":
|
|
12582
|
+
return "2 4";
|
|
12583
|
+
default:
|
|
12584
|
+
return void 0;
|
|
12585
|
+
}
|
|
12022
12586
|
}
|
|
12023
12587
|
function DiagramRelation({
|
|
12024
12588
|
from,
|
|
@@ -12026,6 +12590,12 @@ function DiagramRelation({
|
|
|
12026
12590
|
fromField: fromFieldProp,
|
|
12027
12591
|
toField: toFieldProp,
|
|
12028
12592
|
type,
|
|
12593
|
+
fromMarker: fromMarkerProp,
|
|
12594
|
+
toMarker: toMarkerProp,
|
|
12595
|
+
lineStyle: lineStyleProp,
|
|
12596
|
+
routing = "manhattan",
|
|
12597
|
+
color = DEFAULT_COLOR2,
|
|
12598
|
+
strokeWidth = 2,
|
|
12029
12599
|
label
|
|
12030
12600
|
}) {
|
|
12031
12601
|
const { nodeRects, registryVersion } = useCanvas();
|
|
@@ -12034,68 +12604,160 @@ function DiagramRelation({
|
|
|
12034
12604
|
const fromRect = nodeRects.get(from);
|
|
12035
12605
|
const toRect = nodeRects.get(to);
|
|
12036
12606
|
if (!fromRect || !toRect) return null;
|
|
12607
|
+
const defaults = defaultMarkersForType(type);
|
|
12608
|
+
const fromMarker = fromMarkerProp ?? defaults.fromMarker;
|
|
12609
|
+
const toMarker = toMarkerProp ?? defaults.toMarker;
|
|
12610
|
+
const lineStyle = lineStyleProp ?? defaults.lineStyle;
|
|
12037
12611
|
const fromEntity = schema.entities.find((e) => (e.id ?? e.name) === from);
|
|
12038
12612
|
const toEntity = schema.entities.find((e) => (e.id ?? e.name) === to);
|
|
12039
|
-
|
|
12040
|
-
|
|
12041
|
-
|
|
12042
|
-
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
|
|
12046
|
-
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
|
|
12050
|
-
|
|
12051
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
}
|
|
12057
|
-
const fromFieldY = fromFieldIdx >= 0 ? HEADER_HEIGHT + fromFieldIdx * FIELD_HEIGHT + FIELD_HEIGHT / 2 : fromRect.height / 2;
|
|
12058
|
-
const toFieldY = toFieldIdx >= 0 ? HEADER_HEIGHT + toFieldIdx * FIELD_HEIGHT + FIELD_HEIGHT / 2 : toRect.height / 2;
|
|
12059
|
-
const fromCx = fromRect.x + fromRect.width / 2;
|
|
12060
|
-
const toCx = toRect.x + toRect.width / 2;
|
|
12061
|
-
let fromPt, toPt;
|
|
12062
|
-
let fromDir;
|
|
12063
|
-
let toDir;
|
|
12064
|
-
if (fromCx <= toCx) {
|
|
12065
|
-
fromPt = { x: fromRect.x + fromRect.width, y: fromRect.y + fromFieldY };
|
|
12066
|
-
toPt = { x: toRect.x, y: toRect.y + toFieldY };
|
|
12067
|
-
fromDir = "right";
|
|
12068
|
-
toDir = "left";
|
|
12613
|
+
const fromFieldY = resolveFieldY(fromRect, fromEntity?.fields, fromFieldProp, true);
|
|
12614
|
+
const toFieldY = resolveFieldY(toRect, toEntity?.fields, toFieldProp, false, fromEntity?.name ?? from);
|
|
12615
|
+
const anchors = pickAnchors(fromRect, toRect, fromFieldY, toFieldY);
|
|
12616
|
+
if (anchors.from.side === "top" || anchors.from.side === "bottom") {
|
|
12617
|
+
anchors.from.x = fromRect.x + fromRect.width / 2;
|
|
12618
|
+
}
|
|
12619
|
+
if (anchors.to.side === "top" || anchors.to.side === "bottom") {
|
|
12620
|
+
anchors.to.x = toRect.x + toRect.width / 2;
|
|
12621
|
+
}
|
|
12622
|
+
let points;
|
|
12623
|
+
if (routing === "straight") {
|
|
12624
|
+
points = [
|
|
12625
|
+
{ x: anchors.from.x, y: anchors.from.y },
|
|
12626
|
+
{ x: anchors.to.x, y: anchors.to.y }
|
|
12627
|
+
];
|
|
12628
|
+
} else if (routing === "bezier") {
|
|
12629
|
+
points = [];
|
|
12069
12630
|
} else {
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
|
|
12075
|
-
|
|
12076
|
-
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
if (
|
|
12080
|
-
|
|
12081
|
-
|
|
12082
|
-
const
|
|
12083
|
-
const
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
|
|
12087
|
-
|
|
12088
|
-
const
|
|
12089
|
-
|
|
12090
|
-
|
|
12631
|
+
const obstacles = [];
|
|
12632
|
+
nodeRects.forEach((rect, id) => {
|
|
12633
|
+
if (id === from || id === to) return;
|
|
12634
|
+
obstacles.push(rect);
|
|
12635
|
+
});
|
|
12636
|
+
const initial = manhattanPath(anchors.from, anchors.to, obstacles);
|
|
12637
|
+
points = dodgeObstacles(initial, obstacles);
|
|
12638
|
+
}
|
|
12639
|
+
const insetAmount = { from: markerInset(fromMarker), to: markerInset(toMarker) };
|
|
12640
|
+
if (routing !== "bezier") {
|
|
12641
|
+
points = insetEndpoints(points, insetAmount);
|
|
12642
|
+
}
|
|
12643
|
+
const linePath = routing === "bezier" ? bezierPath2(anchors.from, anchors.to) : pathFromPoints(points);
|
|
12644
|
+
const fromMarkerRenderable = renderMarker(
|
|
12645
|
+
fromMarker,
|
|
12646
|
+
{ x: anchors.from.x, y: anchors.from.y },
|
|
12647
|
+
markerDirection(anchors.from.side)
|
|
12648
|
+
);
|
|
12649
|
+
const toMarkerRenderable = renderMarker(
|
|
12650
|
+
toMarker,
|
|
12651
|
+
{ x: anchors.to.x, y: anchors.to.y },
|
|
12652
|
+
markerDirection(anchors.to.side)
|
|
12653
|
+
);
|
|
12654
|
+
const mid = routing === "bezier" ? { x: (anchors.from.x + anchors.to.x) / 2, y: (anchors.from.y + anchors.to.y) / 2 } : midPoint(points);
|
|
12655
|
+
return {
|
|
12656
|
+
linePath,
|
|
12657
|
+
fromMarker: fromMarkerRenderable,
|
|
12658
|
+
toMarker: toMarkerRenderable,
|
|
12659
|
+
fromAnchor: anchors.from,
|
|
12660
|
+
toAnchor: anchors.to,
|
|
12661
|
+
mid,
|
|
12662
|
+
lineStyle
|
|
12663
|
+
};
|
|
12664
|
+
}, [
|
|
12665
|
+
from,
|
|
12666
|
+
to,
|
|
12667
|
+
fromFieldProp,
|
|
12668
|
+
toFieldProp,
|
|
12669
|
+
type,
|
|
12670
|
+
fromMarkerProp,
|
|
12671
|
+
toMarkerProp,
|
|
12672
|
+
lineStyleProp,
|
|
12673
|
+
routing,
|
|
12674
|
+
schema,
|
|
12675
|
+
nodeRects,
|
|
12676
|
+
registryVersion
|
|
12677
|
+
]);
|
|
12091
12678
|
if (!result) return null;
|
|
12679
|
+
const dashArray = strokeDashArray(result.lineStyle);
|
|
12092
12680
|
return /* @__PURE__ */ jsxs("g", { "data-react-fancy-diagram-relation": "", children: [
|
|
12093
|
-
/* @__PURE__ */ jsx(
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12681
|
+
/* @__PURE__ */ jsx(
|
|
12682
|
+
"path",
|
|
12683
|
+
{
|
|
12684
|
+
d: result.linePath,
|
|
12685
|
+
fill: "none",
|
|
12686
|
+
stroke: color,
|
|
12687
|
+
strokeWidth,
|
|
12688
|
+
strokeDasharray: dashArray,
|
|
12689
|
+
strokeLinecap: "round",
|
|
12690
|
+
strokeLinejoin: "round"
|
|
12691
|
+
}
|
|
12692
|
+
),
|
|
12693
|
+
result.fromMarker?.paths.map((shape, i) => /* @__PURE__ */ jsx(
|
|
12694
|
+
"path",
|
|
12695
|
+
{
|
|
12696
|
+
d: shape.d,
|
|
12697
|
+
fill: shape.fill === "stroke" ? color : shape.fill === "background" ? "#ffffff" : "none",
|
|
12698
|
+
stroke: color,
|
|
12699
|
+
strokeWidth,
|
|
12700
|
+
strokeLinecap: "round",
|
|
12701
|
+
strokeLinejoin: "round"
|
|
12702
|
+
},
|
|
12703
|
+
`fm-${i}`
|
|
12704
|
+
)),
|
|
12705
|
+
result.fromMarker?.text && /* @__PURE__ */ jsx(
|
|
12706
|
+
"text",
|
|
12707
|
+
{
|
|
12708
|
+
x: result.fromAnchor.x,
|
|
12709
|
+
y: result.fromAnchor.y,
|
|
12710
|
+
fontSize: 16,
|
|
12711
|
+
textAnchor: "middle",
|
|
12712
|
+
dominantBaseline: "middle",
|
|
12713
|
+
style: { userSelect: "none" },
|
|
12714
|
+
children: result.fromMarker.text
|
|
12715
|
+
}
|
|
12716
|
+
),
|
|
12717
|
+
result.toMarker?.text && /* @__PURE__ */ jsx(
|
|
12718
|
+
"text",
|
|
12719
|
+
{
|
|
12720
|
+
x: result.toAnchor.x,
|
|
12721
|
+
y: result.toAnchor.y,
|
|
12722
|
+
fontSize: 16,
|
|
12723
|
+
textAnchor: "middle",
|
|
12724
|
+
dominantBaseline: "middle",
|
|
12725
|
+
style: { userSelect: "none" },
|
|
12726
|
+
children: result.toMarker.text
|
|
12727
|
+
}
|
|
12728
|
+
),
|
|
12729
|
+
result.toMarker?.paths.map((shape, i) => /* @__PURE__ */ jsx(
|
|
12730
|
+
"path",
|
|
12731
|
+
{
|
|
12732
|
+
d: shape.d,
|
|
12733
|
+
fill: shape.fill === "stroke" ? color : shape.fill === "background" ? "#ffffff" : "none",
|
|
12734
|
+
stroke: color,
|
|
12735
|
+
strokeWidth,
|
|
12736
|
+
strokeLinecap: "round",
|
|
12737
|
+
strokeLinejoin: "round"
|
|
12738
|
+
},
|
|
12739
|
+
`tm-${i}`
|
|
12740
|
+
)),
|
|
12741
|
+
label && /* @__PURE__ */ jsx("foreignObject", { x: result.mid.x - 50, y: result.mid.y - 12, width: 100, height: 24, children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-xs text-zinc-500", children: /* @__PURE__ */ jsx("span", { className: "rounded bg-white/90 px-1.5 py-0.5 dark:bg-zinc-900/90", children: label }) }) })
|
|
12097
12742
|
] });
|
|
12098
12743
|
}
|
|
12744
|
+
function resolveFieldY(rect, fields, fieldProp, isFrom, fromName) {
|
|
12745
|
+
if (!fields || fields.length === 0) return void 0;
|
|
12746
|
+
let idx = -1;
|
|
12747
|
+
if (fieldProp) {
|
|
12748
|
+
idx = fields.findIndex((f) => f.name === fieldProp);
|
|
12749
|
+
} else if (isFrom) {
|
|
12750
|
+
idx = fields.findIndex((f) => f.primary);
|
|
12751
|
+
} else {
|
|
12752
|
+
const lower = (fromName ?? "").toLowerCase();
|
|
12753
|
+
idx = fields.findIndex(
|
|
12754
|
+
(f) => f.foreign && (f.name === `${lower}_id` || f.name === `${lower}Id`)
|
|
12755
|
+
);
|
|
12756
|
+
if (idx === -1) idx = fields.findIndex((f) => f.foreign);
|
|
12757
|
+
}
|
|
12758
|
+
if (idx < 0) return void 0;
|
|
12759
|
+
return HEADER_HEIGHT + idx * FIELD_HEIGHT + FIELD_HEIGHT / 2;
|
|
12760
|
+
}
|
|
12099
12761
|
DiagramRelation._isCanvasEdge = true;
|
|
12100
12762
|
DiagramRelation.displayName = "DiagramRelation";
|
|
12101
12763
|
var FORMAT_LABELS = {
|
|
@@ -12537,6 +13199,8 @@ function TreeNode({ node, depth }) {
|
|
|
12537
13199
|
dragState,
|
|
12538
13200
|
setDragState,
|
|
12539
13201
|
onNodeMove,
|
|
13202
|
+
acceptExternalDrops,
|
|
13203
|
+
onExternalDrop,
|
|
12540
13204
|
nodes,
|
|
12541
13205
|
expandNode
|
|
12542
13206
|
} = useTreeNav();
|
|
@@ -12579,14 +13243,18 @@ function TreeNode({ node, depth }) {
|
|
|
12579
13243
|
clearAutoExpand();
|
|
12580
13244
|
setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
|
|
12581
13245
|
}, [clearAutoExpand, setDragState]);
|
|
13246
|
+
const isExternalDrag = !dragState.draggedNodeId;
|
|
12582
13247
|
const handleDragOver = useCallback((e) => {
|
|
12583
|
-
if (
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
13248
|
+
if (isExternalDrag) {
|
|
13249
|
+
if (!acceptExternalDrops) return;
|
|
13250
|
+
} else {
|
|
13251
|
+
const sourceId = dragState.draggedNodeId;
|
|
13252
|
+
if (sourceId === node.id) return;
|
|
13253
|
+
if (isDescendantOf(nodes, sourceId, node.id)) return;
|
|
13254
|
+
}
|
|
12587
13255
|
e.preventDefault();
|
|
12588
13256
|
e.stopPropagation();
|
|
12589
|
-
e.dataTransfer.dropEffect = "move";
|
|
13257
|
+
e.dataTransfer.dropEffect = isExternalDrag ? "copy" : "move";
|
|
12590
13258
|
const position = computeDropPosition(e, !!isFolder);
|
|
12591
13259
|
if (isFolder && !isExpanded && position === "inside") {
|
|
12592
13260
|
if (!autoExpandTimer.current) {
|
|
@@ -12599,9 +13267,9 @@ function TreeNode({ node, depth }) {
|
|
|
12599
13267
|
clearAutoExpand();
|
|
12600
13268
|
}
|
|
12601
13269
|
if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {
|
|
12602
|
-
setDragState({ draggedNodeId:
|
|
13270
|
+
setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });
|
|
12603
13271
|
}
|
|
12604
|
-
}, [dragState, node.id, isFolder, isExpanded, nodes, setDragState, expandNode, clearAutoExpand]);
|
|
13272
|
+
}, [dragState, isExternalDrag, acceptExternalDrops, node.id, isFolder, isExpanded, nodes, setDragState, expandNode, clearAutoExpand]);
|
|
12605
13273
|
const handleDragLeave = useCallback((e) => {
|
|
12606
13274
|
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
12607
13275
|
clearAutoExpand();
|
|
@@ -12615,21 +13283,25 @@ function TreeNode({ node, depth }) {
|
|
|
12615
13283
|
e.stopPropagation();
|
|
12616
13284
|
clearAutoExpand();
|
|
12617
13285
|
const sourceId = dragState.draggedNodeId;
|
|
12618
|
-
const position = dragState.dropPosition;
|
|
12619
|
-
if (
|
|
12620
|
-
|
|
12621
|
-
|
|
12622
|
-
|
|
13286
|
+
const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);
|
|
13287
|
+
if (sourceId) {
|
|
13288
|
+
if (sourceId === node.id) return;
|
|
13289
|
+
if (isDescendantOf(nodes, sourceId, node.id)) return;
|
|
13290
|
+
onNodeMove?.(sourceId, node.id, position);
|
|
13291
|
+
} else if (acceptExternalDrops) {
|
|
13292
|
+
onExternalDrop?.(e, node, position);
|
|
13293
|
+
}
|
|
12623
13294
|
setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
|
|
12624
|
-
}, [dragState, node
|
|
13295
|
+
}, [dragState, node, isFolder, nodes, onNodeMove, acceptExternalDrops, onExternalDrop, setDragState, clearAutoExpand]);
|
|
12625
13296
|
const canDrag = draggable && !node.disabled;
|
|
13297
|
+
const dropEnabled = draggable || acceptExternalDrops;
|
|
12626
13298
|
return /* @__PURE__ */ jsxs(
|
|
12627
13299
|
"div",
|
|
12628
13300
|
{
|
|
12629
13301
|
"data-react-fancy-tree-node": "",
|
|
12630
|
-
onDragOver:
|
|
12631
|
-
onDragLeave:
|
|
12632
|
-
onDrop:
|
|
13302
|
+
onDragOver: dropEnabled ? handleDragOver : void 0,
|
|
13303
|
+
onDragLeave: dropEnabled ? handleDragLeave : void 0,
|
|
13304
|
+
onDrop: dropEnabled ? handleDrop : void 0,
|
|
12633
13305
|
children: [
|
|
12634
13306
|
isDropTarget && dropPosition === "before" && /* @__PURE__ */ jsx(
|
|
12635
13307
|
"div",
|
|
@@ -12702,6 +13374,8 @@ function TreeNavRoot({
|
|
|
12702
13374
|
onNodeContextMenu,
|
|
12703
13375
|
draggable = false,
|
|
12704
13376
|
onNodeMove,
|
|
13377
|
+
acceptExternalDrops = false,
|
|
13378
|
+
onExternalDrop,
|
|
12705
13379
|
expandedIds: controlledExpanded,
|
|
12706
13380
|
defaultExpandedIds,
|
|
12707
13381
|
onExpandedChange,
|
|
@@ -12755,6 +13429,8 @@ function TreeNavRoot({
|
|
|
12755
13429
|
dragState,
|
|
12756
13430
|
setDragState,
|
|
12757
13431
|
onNodeMove,
|
|
13432
|
+
acceptExternalDrops,
|
|
13433
|
+
onExternalDrop,
|
|
12758
13434
|
nodes,
|
|
12759
13435
|
expandNode
|
|
12760
13436
|
}),
|
|
@@ -12769,16 +13445,19 @@ function TreeNavRoot({
|
|
|
12769
13445
|
draggable,
|
|
12770
13446
|
dragState,
|
|
12771
13447
|
onNodeMove,
|
|
13448
|
+
acceptExternalDrops,
|
|
13449
|
+
onExternalDrop,
|
|
12772
13450
|
nodes,
|
|
12773
13451
|
expandNode
|
|
12774
13452
|
]
|
|
12775
13453
|
);
|
|
13454
|
+
const dropEnabled = draggable || acceptExternalDrops;
|
|
12776
13455
|
return /* @__PURE__ */ jsx(TreeNavContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
|
|
12777
13456
|
"nav",
|
|
12778
13457
|
{
|
|
12779
13458
|
"data-react-fancy-tree-nav": "",
|
|
12780
13459
|
className: cn("flex flex-col gap-0.5 py-1 text-sm", className),
|
|
12781
|
-
onDragEnd:
|
|
13460
|
+
onDragEnd: dropEnabled ? handleDragEnd : void 0,
|
|
12782
13461
|
children: nodes.map((node) => /* @__PURE__ */ jsx(TreeNode, { node, depth: 0 }, node.id))
|
|
12783
13462
|
}
|
|
12784
13463
|
) });
|