@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.cjs
CHANGED
|
@@ -11771,7 +11771,7 @@ function useCanvas() {
|
|
|
11771
11771
|
return ctx;
|
|
11772
11772
|
}
|
|
11773
11773
|
function CanvasNode({ children, id, x, y, draggable, onPositionChange, className, style }) {
|
|
11774
|
-
const { registerNode, unregisterNode, viewport } = useCanvas();
|
|
11774
|
+
const { registerNode, unregisterNode, viewport, gridSize, snapToGrid } = useCanvas();
|
|
11775
11775
|
const nodeRef = react.useRef(null);
|
|
11776
11776
|
const isDragging = react.useRef(false);
|
|
11777
11777
|
const dragStart = react.useRef({ mouseX: 0, mouseY: 0, nodeX: 0, nodeY: 0 });
|
|
@@ -11804,9 +11804,15 @@ function CanvasNode({ children, id, x, y, draggable, onPositionChange, className
|
|
|
11804
11804
|
if (!isDragging.current) return;
|
|
11805
11805
|
const dx = (e.clientX - dragStart.current.mouseX) / viewport.zoom;
|
|
11806
11806
|
const dy = (e.clientY - dragStart.current.mouseY) / viewport.zoom;
|
|
11807
|
-
|
|
11807
|
+
let nx = dragStart.current.nodeX + dx;
|
|
11808
|
+
let ny = dragStart.current.nodeY + dy;
|
|
11809
|
+
if (snapToGrid && gridSize > 0) {
|
|
11810
|
+
nx = Math.round(nx / gridSize) * gridSize;
|
|
11811
|
+
ny = Math.round(ny / gridSize) * gridSize;
|
|
11812
|
+
}
|
|
11813
|
+
onPositionChange?.(nx, ny);
|
|
11808
11814
|
},
|
|
11809
|
-
[viewport.zoom, onPositionChange]
|
|
11815
|
+
[viewport.zoom, onPositionChange, snapToGrid, gridSize]
|
|
11810
11816
|
);
|
|
11811
11817
|
const handlePointerUp = react.useCallback(() => {
|
|
11812
11818
|
isDragging.current = false;
|
|
@@ -12054,6 +12060,10 @@ function CanvasRoot({
|
|
|
12054
12060
|
pannable = true,
|
|
12055
12061
|
zoomable = true,
|
|
12056
12062
|
showGrid = false,
|
|
12063
|
+
gridStyle = "dots",
|
|
12064
|
+
gridSize = 20,
|
|
12065
|
+
gridColor = "rgb(161 161 170 / 0.3)",
|
|
12066
|
+
snapToGrid = false,
|
|
12057
12067
|
fitOnMount = false,
|
|
12058
12068
|
className,
|
|
12059
12069
|
style
|
|
@@ -12071,8 +12081,8 @@ function CanvasRoot({
|
|
|
12071
12081
|
containerRef
|
|
12072
12082
|
});
|
|
12073
12083
|
const ctx = react.useMemo(
|
|
12074
|
-
() => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef }),
|
|
12075
|
-
[viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion]
|
|
12084
|
+
() => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef, gridSize, snapToGrid }),
|
|
12085
|
+
[viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, gridSize, snapToGrid]
|
|
12076
12086
|
);
|
|
12077
12087
|
const hasFitted = react.useRef(false);
|
|
12078
12088
|
react.useEffect(() => {
|
|
@@ -12128,9 +12138,13 @@ function CanvasRoot({
|
|
|
12128
12138
|
{
|
|
12129
12139
|
"data-canvas-bg": "",
|
|
12130
12140
|
className: "absolute inset-0",
|
|
12131
|
-
style: showGrid ? {
|
|
12132
|
-
backgroundImage: `
|
|
12133
|
-
backgroundSize: `${
|
|
12141
|
+
style: showGrid && gridStyle !== "none" ? gridStyle === "lines" ? {
|
|
12142
|
+
backgroundImage: `linear-gradient(to right, ${gridColor} 1px, transparent 1px), linear-gradient(to bottom, ${gridColor} 1px, transparent 1px)`,
|
|
12143
|
+
backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
|
|
12144
|
+
backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
|
|
12145
|
+
} : {
|
|
12146
|
+
backgroundImage: `radial-gradient(circle, ${gridColor} 1px, transparent 1px)`,
|
|
12147
|
+
backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
|
|
12134
12148
|
backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
|
|
12135
12149
|
} : void 0
|
|
12136
12150
|
}
|
|
@@ -12270,65 +12284,615 @@ function DiagramEntity({
|
|
|
12270
12284
|
) });
|
|
12271
12285
|
}
|
|
12272
12286
|
DiagramEntity.displayName = "DiagramEntity";
|
|
12273
|
-
|
|
12274
|
-
|
|
12275
|
-
|
|
12276
|
-
|
|
12277
|
-
|
|
12287
|
+
|
|
12288
|
+
// src/components/Diagram/diagram.markers.ts
|
|
12289
|
+
function defaultMarkersForType(type) {
|
|
12290
|
+
switch (type) {
|
|
12291
|
+
case "one-to-one":
|
|
12292
|
+
return { fromMarker: "one", toMarker: "one", lineStyle: "solid" };
|
|
12293
|
+
case "one-to-many":
|
|
12294
|
+
return { fromMarker: "one", toMarker: "many", lineStyle: "solid" };
|
|
12295
|
+
case "many-to-one":
|
|
12296
|
+
return { fromMarker: "many", toMarker: "one", lineStyle: "solid" };
|
|
12297
|
+
case "many-to-many":
|
|
12298
|
+
return { fromMarker: "many", toMarker: "many", lineStyle: "solid" };
|
|
12299
|
+
case "association":
|
|
12300
|
+
return { fromMarker: "none", toMarker: "arrow", lineStyle: "solid" };
|
|
12301
|
+
case "aggregation":
|
|
12302
|
+
return { fromMarker: "diamond-open", toMarker: "none", lineStyle: "solid" };
|
|
12303
|
+
case "composition":
|
|
12304
|
+
return { fromMarker: "diamond", toMarker: "none", lineStyle: "solid" };
|
|
12305
|
+
case "inheritance":
|
|
12306
|
+
return { fromMarker: "none", toMarker: "triangle-open", lineStyle: "solid" };
|
|
12307
|
+
case "implementation":
|
|
12308
|
+
return { fromMarker: "none", toMarker: "triangle-open", lineStyle: "dashed" };
|
|
12309
|
+
case "dependency":
|
|
12310
|
+
return { fromMarker: "none", toMarker: "arrow", lineStyle: "dashed" };
|
|
12311
|
+
default:
|
|
12312
|
+
return { fromMarker: "none", toMarker: "none", lineStyle: "solid" };
|
|
12313
|
+
}
|
|
12314
|
+
}
|
|
12315
|
+
var SIZE = 12;
|
|
12316
|
+
function renderMarker(marker, pt, direction) {
|
|
12317
|
+
if (typeof marker === "string" && marker.startsWith("emoji:")) {
|
|
12318
|
+
return { paths: [], text: marker.slice(6) };
|
|
12319
|
+
}
|
|
12320
|
+
switch (marker) {
|
|
12321
|
+
case "none":
|
|
12322
|
+
return null;
|
|
12323
|
+
case "arrow":
|
|
12324
|
+
return { paths: [{ d: arrowPath(pt, direction), fill: "stroke" }] };
|
|
12325
|
+
case "arrow-open":
|
|
12326
|
+
return { paths: [{ d: arrowPath(pt, direction), fill: "none" }] };
|
|
12327
|
+
case "circle":
|
|
12328
|
+
return { paths: [{ d: circlePath(pt), fill: "stroke" }] };
|
|
12329
|
+
case "circle-open":
|
|
12330
|
+
return { paths: [{ d: circlePath(pt), fill: "background" }] };
|
|
12331
|
+
case "square":
|
|
12332
|
+
return { paths: [{ d: squarePath(pt, direction), fill: "stroke" }] };
|
|
12333
|
+
case "square-open":
|
|
12334
|
+
return { paths: [{ d: squarePath(pt, direction), fill: "background" }] };
|
|
12335
|
+
case "diamond":
|
|
12336
|
+
return { paths: [{ d: diamondPath(pt, direction), fill: "stroke" }] };
|
|
12337
|
+
case "diamond-open":
|
|
12338
|
+
return { paths: [{ d: diamondPath(pt, direction), fill: "background" }] };
|
|
12339
|
+
case "triangle":
|
|
12340
|
+
return { paths: [{ d: trianglePath(pt, direction), fill: "stroke" }] };
|
|
12341
|
+
case "triangle-open":
|
|
12342
|
+
return { paths: [{ d: trianglePath(pt, direction), fill: "background" }] };
|
|
12343
|
+
case "cross":
|
|
12344
|
+
return { paths: [{ d: crossPath(pt, direction), fill: "none" }] };
|
|
12345
|
+
case "one":
|
|
12346
|
+
return { paths: [{ d: oneSymbol(pt, direction), fill: "none" }] };
|
|
12347
|
+
case "many":
|
|
12348
|
+
return { paths: [{ d: crowFootSymbol(pt, direction), fill: "none" }] };
|
|
12349
|
+
case "optional-one":
|
|
12350
|
+
return {
|
|
12351
|
+
paths: [
|
|
12352
|
+
{ d: circleOuter(pt, direction), fill: "background" },
|
|
12353
|
+
{ d: oneSymbolOffset(pt, direction), fill: "none" }
|
|
12354
|
+
]
|
|
12355
|
+
};
|
|
12356
|
+
case "optional-many":
|
|
12357
|
+
return {
|
|
12358
|
+
paths: [
|
|
12359
|
+
{ d: circleOuter(pt, direction), fill: "background" },
|
|
12360
|
+
{ d: crowFootSymbolOffset(pt, direction), fill: "none" }
|
|
12361
|
+
]
|
|
12362
|
+
};
|
|
12363
|
+
default:
|
|
12364
|
+
if (typeof marker === "string" && marker !== "") {
|
|
12365
|
+
return { paths: [], text: marker };
|
|
12366
|
+
}
|
|
12367
|
+
return null;
|
|
12368
|
+
}
|
|
12369
|
+
}
|
|
12370
|
+
function markerInset(marker) {
|
|
12371
|
+
if (marker === "none" || marker === void 0) return 0;
|
|
12372
|
+
if (typeof marker === "string" && (marker.startsWith("emoji:") || !KNOWN_MARKERS.has(marker))) {
|
|
12373
|
+
return SIZE;
|
|
12374
|
+
}
|
|
12375
|
+
switch (marker) {
|
|
12376
|
+
case "circle":
|
|
12377
|
+
case "circle-open":
|
|
12378
|
+
return SIZE * 0.6;
|
|
12379
|
+
case "one":
|
|
12380
|
+
return 0;
|
|
12381
|
+
// bar sits AT endpoint
|
|
12382
|
+
case "many":
|
|
12383
|
+
return SIZE;
|
|
12384
|
+
case "optional-one":
|
|
12385
|
+
return SIZE * 1.2;
|
|
12386
|
+
case "optional-many":
|
|
12387
|
+
return SIZE * 1.8;
|
|
12388
|
+
default:
|
|
12389
|
+
return SIZE;
|
|
12390
|
+
}
|
|
12391
|
+
}
|
|
12392
|
+
var KNOWN_MARKERS = /* @__PURE__ */ new Set([
|
|
12393
|
+
"none",
|
|
12394
|
+
"arrow",
|
|
12395
|
+
"arrow-open",
|
|
12396
|
+
"circle",
|
|
12397
|
+
"circle-open",
|
|
12398
|
+
"square",
|
|
12399
|
+
"square-open",
|
|
12400
|
+
"diamond",
|
|
12401
|
+
"diamond-open",
|
|
12402
|
+
"triangle",
|
|
12403
|
+
"triangle-open",
|
|
12404
|
+
"one",
|
|
12405
|
+
"many",
|
|
12406
|
+
"optional-one",
|
|
12407
|
+
"optional-many",
|
|
12408
|
+
"cross"
|
|
12409
|
+
]);
|
|
12410
|
+
function dirVec(direction) {
|
|
12278
12411
|
switch (direction) {
|
|
12279
12412
|
case "left":
|
|
12413
|
+
return [-1, 0];
|
|
12280
12414
|
case "right":
|
|
12281
|
-
return
|
|
12415
|
+
return [1, 0];
|
|
12282
12416
|
case "up":
|
|
12417
|
+
return [0, -1];
|
|
12283
12418
|
case "down":
|
|
12284
|
-
return
|
|
12419
|
+
return [0, 1];
|
|
12285
12420
|
}
|
|
12286
12421
|
}
|
|
12287
|
-
function
|
|
12288
|
-
const s = SYMBOL_SIZE;
|
|
12289
|
-
const spread = s * 0.8;
|
|
12290
|
-
let tip;
|
|
12422
|
+
function perpVec(direction) {
|
|
12291
12423
|
switch (direction) {
|
|
12292
|
-
case "right":
|
|
12293
|
-
tip = { x: pt.x - s, y: pt.y };
|
|
12294
|
-
return [
|
|
12295
|
-
`M${pt.x},${pt.y - spread} L${tip.x},${tip.y}`,
|
|
12296
|
-
`M${pt.x},${pt.y} L${tip.x},${tip.y}`,
|
|
12297
|
-
`M${pt.x},${pt.y + spread} L${tip.x},${tip.y}`,
|
|
12298
|
-
// bar at entity edge
|
|
12299
|
-
`M${pt.x},${pt.y - spread} L${pt.x},${pt.y + spread}`
|
|
12300
|
-
].join(" ");
|
|
12301
12424
|
case "left":
|
|
12302
|
-
|
|
12303
|
-
return [
|
|
12304
|
-
`M${pt.x},${pt.y - spread} L${tip.x},${tip.y}`,
|
|
12305
|
-
`M${pt.x},${pt.y} L${tip.x},${tip.y}`,
|
|
12306
|
-
`M${pt.x},${pt.y + spread} L${tip.x},${tip.y}`,
|
|
12307
|
-
`M${pt.x},${pt.y - spread} L${pt.x},${pt.y + spread}`
|
|
12308
|
-
].join(" ");
|
|
12309
|
-
case "down":
|
|
12310
|
-
tip = { x: pt.x, y: pt.y - s };
|
|
12311
|
-
return [
|
|
12312
|
-
`M${pt.x - spread},${pt.y} L${tip.x},${tip.y}`,
|
|
12313
|
-
`M${pt.x},${pt.y} L${tip.x},${tip.y}`,
|
|
12314
|
-
`M${pt.x + spread},${pt.y} L${tip.x},${tip.y}`,
|
|
12315
|
-
`M${pt.x - spread},${pt.y} L${pt.x + spread},${pt.y}`
|
|
12316
|
-
].join(" ");
|
|
12425
|
+
case "right":
|
|
12426
|
+
return [0, 1];
|
|
12317
12427
|
case "up":
|
|
12318
|
-
|
|
12319
|
-
return [
|
|
12320
|
-
|
|
12321
|
-
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
const
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
return
|
|
12428
|
+
case "down":
|
|
12429
|
+
return [1, 0];
|
|
12430
|
+
}
|
|
12431
|
+
}
|
|
12432
|
+
function arrowPath(pt, direction, _filled) {
|
|
12433
|
+
const [dx, dy] = dirVec(direction);
|
|
12434
|
+
const [px, py] = perpVec(direction);
|
|
12435
|
+
const tipX = pt.x + dx * SIZE;
|
|
12436
|
+
const tipY = pt.y + dy * SIZE;
|
|
12437
|
+
const baseAX = pt.x + px * (SIZE * 0.55);
|
|
12438
|
+
const baseAY = pt.y + py * (SIZE * 0.55);
|
|
12439
|
+
const baseBX = pt.x - px * (SIZE * 0.55);
|
|
12440
|
+
const baseBY = pt.y - py * (SIZE * 0.55);
|
|
12441
|
+
return `M${baseAX},${baseAY} L${tipX},${tipY} L${baseBX},${baseBY} Z`;
|
|
12442
|
+
}
|
|
12443
|
+
function circlePath(pt, _filled) {
|
|
12444
|
+
const r = SIZE * 0.45;
|
|
12445
|
+
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`;
|
|
12446
|
+
}
|
|
12447
|
+
function squarePath(pt, direction, _filled) {
|
|
12448
|
+
const [dx, dy] = dirVec(direction);
|
|
12449
|
+
const [px, py] = perpVec(direction);
|
|
12450
|
+
const half = SIZE * 0.5;
|
|
12451
|
+
const cx = pt.x + dx * half;
|
|
12452
|
+
const cy = pt.y + dy * half;
|
|
12453
|
+
const tlX = cx - px * half - dx * half;
|
|
12454
|
+
const tlY = cy - py * half - dy * half;
|
|
12455
|
+
const trX = cx + px * half - dx * half;
|
|
12456
|
+
const trY = cy + py * half - dy * half;
|
|
12457
|
+
const brX = cx + px * half + dx * half;
|
|
12458
|
+
const brY = cy + py * half + dy * half;
|
|
12459
|
+
const blX = cx - px * half + dx * half;
|
|
12460
|
+
const blY = cy - py * half + dy * half;
|
|
12461
|
+
return `M${tlX},${tlY} L${trX},${trY} L${brX},${brY} L${blX},${blY} Z`;
|
|
12462
|
+
}
|
|
12463
|
+
function diamondPath(pt, direction, _filled) {
|
|
12464
|
+
const [dx, dy] = dirVec(direction);
|
|
12465
|
+
const [px, py] = perpVec(direction);
|
|
12466
|
+
const len = SIZE * 1.2;
|
|
12467
|
+
const cx = pt.x + dx * (len / 2);
|
|
12468
|
+
const cy = pt.y + dy * (len / 2);
|
|
12469
|
+
const aX = pt.x;
|
|
12470
|
+
const aY = pt.y;
|
|
12471
|
+
const bX = cx + px * (SIZE * 0.45);
|
|
12472
|
+
const bY = cy + py * (SIZE * 0.45);
|
|
12473
|
+
const tX = pt.x + dx * len;
|
|
12474
|
+
const tY = pt.y + dy * len;
|
|
12475
|
+
const dX = cx - px * (SIZE * 0.45);
|
|
12476
|
+
const dY = cy - py * (SIZE * 0.45);
|
|
12477
|
+
return `M${aX},${aY} L${bX},${bY} L${tX},${tY} L${dX},${dY} Z`;
|
|
12478
|
+
}
|
|
12479
|
+
function trianglePath(pt, direction, _filled) {
|
|
12480
|
+
const [dx, dy] = dirVec(direction);
|
|
12481
|
+
const [px, py] = perpVec(direction);
|
|
12482
|
+
const len = SIZE * 1.1;
|
|
12483
|
+
const baseCX = pt.x + dx * len;
|
|
12484
|
+
const baseCY = pt.y + dy * len;
|
|
12485
|
+
const baseAX = baseCX + px * (SIZE * 0.6);
|
|
12486
|
+
const baseAY = baseCY + py * (SIZE * 0.6);
|
|
12487
|
+
const baseBX = baseCX - px * (SIZE * 0.6);
|
|
12488
|
+
const baseBY = baseCY - py * (SIZE * 0.6);
|
|
12489
|
+
return `M${pt.x},${pt.y} L${baseAX},${baseAY} L${baseBX},${baseBY} Z`;
|
|
12490
|
+
}
|
|
12491
|
+
function crossPath(pt, direction) {
|
|
12492
|
+
const [dx, dy] = dirVec(direction);
|
|
12493
|
+
const [px, py] = perpVec(direction);
|
|
12494
|
+
const s = SIZE * 0.5;
|
|
12495
|
+
const cx = pt.x + dx * (SIZE * 0.5);
|
|
12496
|
+
const cy = pt.y + dy * (SIZE * 0.5);
|
|
12497
|
+
return [
|
|
12498
|
+
`M${cx - s * (px + dx)},${cy - s * (py + dy)} L${cx + s * (px + dx)},${cy + s * (py + dy)}`,
|
|
12499
|
+
`M${cx - s * (px - dx)},${cy - s * (py - dy)} L${cx + s * (px - dx)},${cy + s * (py - dy)}`
|
|
12500
|
+
].join(" ");
|
|
12501
|
+
}
|
|
12502
|
+
function oneSymbol(pt, direction) {
|
|
12503
|
+
const [, py] = perpVec(direction);
|
|
12504
|
+
const [px] = perpVec(direction);
|
|
12505
|
+
const half = SIZE * 0.6;
|
|
12506
|
+
return `M${pt.x - px * half},${pt.y - py * half} L${pt.x + px * half},${pt.y + py * half}`;
|
|
12507
|
+
}
|
|
12508
|
+
function crowFootSymbol(pt, direction) {
|
|
12509
|
+
const [dx, dy] = dirVec(direction);
|
|
12510
|
+
const [px, py] = perpVec(direction);
|
|
12511
|
+
const tipX = pt.x + dx * SIZE;
|
|
12512
|
+
const tipY = pt.y + dy * SIZE;
|
|
12513
|
+
const spread = SIZE * 0.8;
|
|
12514
|
+
const aX = pt.x + px * spread, aY = pt.y + py * spread;
|
|
12515
|
+
const cX = pt.x - px * spread, cY = pt.y - py * spread;
|
|
12516
|
+
return [
|
|
12517
|
+
`M${aX},${aY} L${tipX},${tipY}`,
|
|
12518
|
+
`M${pt.x},${pt.y} L${tipX},${tipY}`,
|
|
12519
|
+
`M${cX},${cY} L${tipX},${tipY}`,
|
|
12520
|
+
`M${aX},${aY} L${cX},${cY}`
|
|
12521
|
+
].join(" ");
|
|
12522
|
+
}
|
|
12523
|
+
function circleOuter(pt, direction) {
|
|
12524
|
+
const [dx, dy] = dirVec(direction);
|
|
12525
|
+
const r = SIZE * 0.4;
|
|
12526
|
+
const cx = pt.x + dx * (SIZE * 0.4 + r);
|
|
12527
|
+
const cy = pt.y + dy * (SIZE * 0.4 + r);
|
|
12528
|
+
return `M${cx - r},${cy} a${r},${r} 0 1 0 ${r * 2},0 a${r},${r} 0 1 0 ${-r * 2},0 Z`;
|
|
12529
|
+
}
|
|
12530
|
+
function oneSymbolOffset(pt, direction) {
|
|
12531
|
+
const [px, py] = perpVec(direction);
|
|
12532
|
+
const half = SIZE * 0.6;
|
|
12533
|
+
return `M${pt.x - px * half},${pt.y - py * half} L${pt.x + px * half},${pt.y + py * half}`;
|
|
12534
|
+
}
|
|
12535
|
+
function crowFootSymbolOffset(pt, direction) {
|
|
12536
|
+
const [dx, dy] = dirVec(direction);
|
|
12537
|
+
const [px, py] = perpVec(direction);
|
|
12538
|
+
const inset = SIZE * 0.8;
|
|
12539
|
+
const startX = pt.x + dx * inset;
|
|
12540
|
+
const startY = pt.y + dy * inset;
|
|
12541
|
+
const tipX = pt.x + dx * (inset + SIZE);
|
|
12542
|
+
const tipY = pt.y + dy * (inset + SIZE);
|
|
12543
|
+
const spread = SIZE * 0.8;
|
|
12544
|
+
const aX = startX + px * spread, aY = startY + py * spread;
|
|
12545
|
+
const cX = startX - px * spread, cY = startY - py * spread;
|
|
12546
|
+
return [
|
|
12547
|
+
`M${aX},${aY} L${tipX},${tipY}`,
|
|
12548
|
+
`M${startX},${startY} L${tipX},${tipY}`,
|
|
12549
|
+
`M${cX},${cY} L${tipX},${tipY}`
|
|
12550
|
+
].join(" ");
|
|
12551
|
+
}
|
|
12552
|
+
|
|
12553
|
+
// src/components/Diagram/diagram.routing.ts
|
|
12554
|
+
var STUB = 24;
|
|
12555
|
+
var DODGE_PADDING = 16;
|
|
12556
|
+
var MAX_DODGE_ITERATIONS = 6;
|
|
12557
|
+
function pickAnchors(from, to, fromY, toY) {
|
|
12558
|
+
const fcx = from.x + from.width / 2;
|
|
12559
|
+
const fcy = from.y + from.height / 2;
|
|
12560
|
+
const tcx = to.x + to.width / 2;
|
|
12561
|
+
const tcy = to.y + to.height / 2;
|
|
12562
|
+
const dx = tcx - fcx;
|
|
12563
|
+
const dy = tcy - fcy;
|
|
12564
|
+
let fromSide, toSide;
|
|
12565
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
12566
|
+
fromSide = dx >= 0 ? "right" : "left";
|
|
12567
|
+
toSide = dx >= 0 ? "left" : "right";
|
|
12568
|
+
} else {
|
|
12569
|
+
fromSide = dy >= 0 ? "bottom" : "top";
|
|
12570
|
+
toSide = dy >= 0 ? "top" : "bottom";
|
|
12571
|
+
}
|
|
12572
|
+
return {
|
|
12573
|
+
from: anchorOnSide(from, fromSide, fromY),
|
|
12574
|
+
to: anchorOnSide(to, toSide, toY)
|
|
12575
|
+
};
|
|
12576
|
+
}
|
|
12577
|
+
function anchorOnSide(box, side, fieldY) {
|
|
12578
|
+
switch (side) {
|
|
12579
|
+
case "right":
|
|
12580
|
+
return { side, x: box.x + box.width, y: box.y + (fieldY ?? box.height / 2) };
|
|
12581
|
+
case "left":
|
|
12582
|
+
return { side, x: box.x, y: box.y + (fieldY ?? box.height / 2) };
|
|
12583
|
+
case "top":
|
|
12584
|
+
return { side, x: box.x + box.width / 2, y: box.y };
|
|
12585
|
+
case "bottom":
|
|
12586
|
+
return { side, x: box.x + box.width / 2, y: box.y + box.height };
|
|
12587
|
+
}
|
|
12588
|
+
}
|
|
12589
|
+
function stubOut(a, distance2 = STUB) {
|
|
12590
|
+
switch (a.side) {
|
|
12591
|
+
case "right":
|
|
12592
|
+
return { x: a.x + distance2, y: a.y };
|
|
12593
|
+
case "left":
|
|
12594
|
+
return { x: a.x - distance2, y: a.y };
|
|
12595
|
+
case "top":
|
|
12596
|
+
return { x: a.x, y: a.y - distance2 };
|
|
12597
|
+
case "bottom":
|
|
12598
|
+
return { x: a.x, y: a.y + distance2 };
|
|
12599
|
+
}
|
|
12600
|
+
}
|
|
12601
|
+
function manhattanPath(from, to, obstacles = []) {
|
|
12602
|
+
const f = { x: from.x, y: from.y };
|
|
12603
|
+
const t = { x: to.x, y: to.y };
|
|
12604
|
+
const fs = stubOut(from);
|
|
12605
|
+
const ts = stubOut(to);
|
|
12606
|
+
const fHoriz = from.side === "left" || from.side === "right";
|
|
12607
|
+
const tHoriz = to.side === "left" || to.side === "right";
|
|
12608
|
+
if (fHoriz && tHoriz) {
|
|
12609
|
+
const midX = pickClearMidX((fs.x + ts.x) / 2, fs.y, ts.y, obstacles);
|
|
12610
|
+
return uniqPath([
|
|
12611
|
+
f,
|
|
12612
|
+
fs,
|
|
12613
|
+
{ x: midX, y: fs.y },
|
|
12614
|
+
{ x: midX, y: ts.y },
|
|
12615
|
+
ts,
|
|
12616
|
+
t
|
|
12617
|
+
]);
|
|
12618
|
+
}
|
|
12619
|
+
if (!fHoriz && !tHoriz) {
|
|
12620
|
+
const midY = pickClearMidY((fs.y + ts.y) / 2, fs.x, ts.x, obstacles);
|
|
12621
|
+
return uniqPath([
|
|
12622
|
+
f,
|
|
12623
|
+
fs,
|
|
12624
|
+
{ x: fs.x, y: midY },
|
|
12625
|
+
{ x: ts.x, y: midY },
|
|
12626
|
+
ts,
|
|
12627
|
+
t
|
|
12628
|
+
]);
|
|
12629
|
+
}
|
|
12630
|
+
if (fHoriz) {
|
|
12631
|
+
return uniqPath([
|
|
12632
|
+
f,
|
|
12633
|
+
fs,
|
|
12634
|
+
{ x: ts.x, y: fs.y },
|
|
12635
|
+
ts,
|
|
12636
|
+
t
|
|
12637
|
+
]);
|
|
12638
|
+
}
|
|
12639
|
+
return uniqPath([
|
|
12640
|
+
f,
|
|
12641
|
+
fs,
|
|
12642
|
+
{ x: fs.x, y: ts.y },
|
|
12643
|
+
ts,
|
|
12644
|
+
t
|
|
12645
|
+
]);
|
|
12646
|
+
}
|
|
12647
|
+
function pickClearMidX(idealX, y1, y2, obstacles) {
|
|
12648
|
+
if (obstacles.length === 0) return idealX;
|
|
12649
|
+
const yMin = Math.min(y1, y2);
|
|
12650
|
+
const yMax = Math.max(y1, y2);
|
|
12651
|
+
let midX = idealX;
|
|
12652
|
+
for (let i = 0; i < 4; i++) {
|
|
12653
|
+
let shifted = false;
|
|
12654
|
+
for (const ob of obstacles) {
|
|
12655
|
+
if (ob.y + ob.height + DODGE_PADDING < yMin) continue;
|
|
12656
|
+
if (ob.y - DODGE_PADDING > yMax) continue;
|
|
12657
|
+
const left = ob.x - DODGE_PADDING;
|
|
12658
|
+
const right = ob.x + ob.width + DODGE_PADDING;
|
|
12659
|
+
if (midX > left && midX < right) {
|
|
12660
|
+
midX = Math.abs(midX - left) <= Math.abs(midX - right) ? left - 1 : right + 1;
|
|
12661
|
+
shifted = true;
|
|
12662
|
+
}
|
|
12663
|
+
}
|
|
12664
|
+
if (!shifted) break;
|
|
12665
|
+
}
|
|
12666
|
+
return midX;
|
|
12667
|
+
}
|
|
12668
|
+
function pickClearMidY(idealY, x1, x2, obstacles) {
|
|
12669
|
+
if (obstacles.length === 0) return idealY;
|
|
12670
|
+
const xMin = Math.min(x1, x2);
|
|
12671
|
+
const xMax = Math.max(x1, x2);
|
|
12672
|
+
let midY = idealY;
|
|
12673
|
+
for (let i = 0; i < 4; i++) {
|
|
12674
|
+
let shifted = false;
|
|
12675
|
+
for (const ob of obstacles) {
|
|
12676
|
+
if (ob.x + ob.width + DODGE_PADDING < xMin) continue;
|
|
12677
|
+
if (ob.x - DODGE_PADDING > xMax) continue;
|
|
12678
|
+
const top = ob.y - DODGE_PADDING;
|
|
12679
|
+
const bot = ob.y + ob.height + DODGE_PADDING;
|
|
12680
|
+
if (midY > top && midY < bot) {
|
|
12681
|
+
midY = Math.abs(midY - top) <= Math.abs(midY - bot) ? top - 1 : bot + 1;
|
|
12682
|
+
shifted = true;
|
|
12683
|
+
}
|
|
12684
|
+
}
|
|
12685
|
+
if (!shifted) break;
|
|
12686
|
+
}
|
|
12687
|
+
return midY;
|
|
12688
|
+
}
|
|
12689
|
+
function uniqPath(points) {
|
|
12690
|
+
const out = [];
|
|
12691
|
+
for (const p of points) {
|
|
12692
|
+
const last = out[out.length - 1];
|
|
12693
|
+
if (!last || Math.abs(last.x - p.x) > 0.5 || Math.abs(last.y - p.y) > 0.5) {
|
|
12694
|
+
out.push(p);
|
|
12695
|
+
}
|
|
12696
|
+
}
|
|
12697
|
+
return out;
|
|
12698
|
+
}
|
|
12699
|
+
function dodgeObstacles(path, obstacles, padding = DODGE_PADDING) {
|
|
12700
|
+
if (obstacles.length === 0 || path.length < 2) return path;
|
|
12701
|
+
let working = path.slice();
|
|
12702
|
+
for (let iter = 0; iter < MAX_DODGE_ITERATIONS; iter++) {
|
|
12703
|
+
let dodged = false;
|
|
12704
|
+
const result = [working[0]];
|
|
12705
|
+
for (let i = 1; i < working.length; i++) {
|
|
12706
|
+
const a = result[result.length - 1];
|
|
12707
|
+
const b = working[i];
|
|
12708
|
+
const detour = detourAround(a, b, obstacles, padding);
|
|
12709
|
+
if (detour.length === 0) {
|
|
12710
|
+
result.push(b);
|
|
12711
|
+
} else {
|
|
12712
|
+
for (const p of detour) result.push(p);
|
|
12713
|
+
result.push(b);
|
|
12714
|
+
dodged = true;
|
|
12715
|
+
}
|
|
12716
|
+
}
|
|
12717
|
+
working = uniqPath(result);
|
|
12718
|
+
if (!dodged) break;
|
|
12719
|
+
}
|
|
12720
|
+
return working;
|
|
12721
|
+
}
|
|
12722
|
+
function detourAround(a, b, obstacles, padding) {
|
|
12723
|
+
const isHorizontal = Math.abs(a.y - b.y) < 0.5;
|
|
12724
|
+
const isVertical = Math.abs(a.x - b.x) < 0.5;
|
|
12725
|
+
if (!isHorizontal && !isVertical) return [];
|
|
12726
|
+
let best = null;
|
|
12727
|
+
for (const r of obstacles) {
|
|
12728
|
+
const expanded = expandRect(r, padding);
|
|
12729
|
+
if (!segmentCrossesRect(a, b, expanded)) continue;
|
|
12730
|
+
let entry, exit;
|
|
12731
|
+
if (isHorizontal) {
|
|
12732
|
+
entry = b.x > a.x ? expanded.x : expanded.x + expanded.width;
|
|
12733
|
+
exit = b.x > a.x ? expanded.x + expanded.width : expanded.x;
|
|
12734
|
+
} else {
|
|
12735
|
+
entry = b.y > a.y ? expanded.y : expanded.y + expanded.height;
|
|
12736
|
+
exit = b.y > a.y ? expanded.y + expanded.height : expanded.y;
|
|
12737
|
+
}
|
|
12738
|
+
const dist = isHorizontal ? Math.abs(entry - a.x) : Math.abs(entry - a.y);
|
|
12739
|
+
if (!best || dist < (isHorizontal ? Math.abs(best.entry - a.x) : Math.abs(best.entry - a.y))) {
|
|
12740
|
+
best = { rect: expanded, entry, exit };
|
|
12741
|
+
}
|
|
12742
|
+
}
|
|
12743
|
+
if (!best) return [];
|
|
12744
|
+
if (isHorizontal) {
|
|
12745
|
+
const aboveY = best.rect.y - 1;
|
|
12746
|
+
const belowY = best.rect.y + best.rect.height + 1;
|
|
12747
|
+
const detourY = Math.abs(a.y - aboveY) <= Math.abs(a.y - belowY) ? aboveY : belowY;
|
|
12748
|
+
return [
|
|
12749
|
+
{ x: best.entry, y: a.y },
|
|
12750
|
+
{ x: best.entry, y: detourY },
|
|
12751
|
+
{ x: best.exit, y: detourY },
|
|
12752
|
+
{ x: best.exit, y: a.y }
|
|
12753
|
+
];
|
|
12754
|
+
} else {
|
|
12755
|
+
const leftX = best.rect.x - 1;
|
|
12756
|
+
const rightX = best.rect.x + best.rect.width + 1;
|
|
12757
|
+
const detourX = Math.abs(a.x - leftX) <= Math.abs(a.x - rightX) ? leftX : rightX;
|
|
12758
|
+
return [
|
|
12759
|
+
{ x: a.x, y: best.entry },
|
|
12760
|
+
{ x: detourX, y: best.entry },
|
|
12761
|
+
{ x: detourX, y: best.exit },
|
|
12762
|
+
{ x: a.x, y: best.exit }
|
|
12763
|
+
];
|
|
12764
|
+
}
|
|
12765
|
+
}
|
|
12766
|
+
function expandRect(r, padding) {
|
|
12767
|
+
return {
|
|
12768
|
+
x: r.x - padding,
|
|
12769
|
+
y: r.y - padding,
|
|
12770
|
+
width: r.width + padding * 2,
|
|
12771
|
+
height: r.height + padding * 2
|
|
12772
|
+
};
|
|
12773
|
+
}
|
|
12774
|
+
function segmentCrossesRect(a, b, rect) {
|
|
12775
|
+
const xMin = Math.min(a.x, b.x);
|
|
12776
|
+
const xMax = Math.max(a.x, b.x);
|
|
12777
|
+
const yMin = Math.min(a.y, b.y);
|
|
12778
|
+
const yMax = Math.max(a.y, b.y);
|
|
12779
|
+
if (xMax < rect.x) return false;
|
|
12780
|
+
if (xMin > rect.x + rect.width) return false;
|
|
12781
|
+
if (yMax < rect.y) return false;
|
|
12782
|
+
if (yMin > rect.y + rect.height) return false;
|
|
12783
|
+
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;
|
|
12784
|
+
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;
|
|
12785
|
+
return true;
|
|
12786
|
+
}
|
|
12787
|
+
function pathFromPoints(points, cornerRadius = 8) {
|
|
12788
|
+
if (points.length === 0) return "";
|
|
12789
|
+
if (points.length === 1) return `M${points[0].x},${points[0].y}`;
|
|
12790
|
+
if (points.length === 2 || cornerRadius <= 0) {
|
|
12791
|
+
return points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ");
|
|
12792
|
+
}
|
|
12793
|
+
let d = `M${points[0].x},${points[0].y}`;
|
|
12794
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
12795
|
+
const prev = points[i - 1];
|
|
12796
|
+
const curr = points[i];
|
|
12797
|
+
const next = points[i + 1];
|
|
12798
|
+
const r = Math.min(
|
|
12799
|
+
cornerRadius,
|
|
12800
|
+
distance(prev, curr) / 2,
|
|
12801
|
+
distance(curr, next) / 2
|
|
12802
|
+
);
|
|
12803
|
+
if (r < 1) {
|
|
12804
|
+
d += ` L${curr.x},${curr.y}`;
|
|
12805
|
+
continue;
|
|
12806
|
+
}
|
|
12807
|
+
const beforeX = curr.x + Math.sign(prev.x - curr.x) * r;
|
|
12808
|
+
const beforeY = curr.y + Math.sign(prev.y - curr.y) * r;
|
|
12809
|
+
const afterX = curr.x + Math.sign(next.x - curr.x) * r;
|
|
12810
|
+
const afterY = curr.y + Math.sign(next.y - curr.y) * r;
|
|
12811
|
+
d += ` L${beforeX},${beforeY} Q${curr.x},${curr.y} ${afterX},${afterY}`;
|
|
12812
|
+
}
|
|
12813
|
+
const last = points[points.length - 1];
|
|
12814
|
+
d += ` L${last.x},${last.y}`;
|
|
12815
|
+
return d;
|
|
12816
|
+
}
|
|
12817
|
+
function distance(a, b) {
|
|
12818
|
+
return Math.hypot(b.x - a.x, b.y - a.y);
|
|
12819
|
+
}
|
|
12820
|
+
function bezierPath2(from, to) {
|
|
12821
|
+
const fs = stubOut(from, Math.max(40, distance(from, to) * 0.3));
|
|
12822
|
+
const ts = stubOut(to, Math.max(40, distance(from, to) * 0.3));
|
|
12823
|
+
return `M${from.x},${from.y} C${fs.x},${fs.y} ${ts.x},${ts.y} ${to.x},${to.y}`;
|
|
12824
|
+
}
|
|
12825
|
+
function midPoint(points) {
|
|
12826
|
+
if (points.length === 0) return { x: 0, y: 0 };
|
|
12827
|
+
if (points.length === 1) return points[0];
|
|
12828
|
+
let total = 0;
|
|
12829
|
+
const segLens = [];
|
|
12830
|
+
for (let i = 1; i < points.length; i++) {
|
|
12831
|
+
const len = distance(points[i - 1], points[i]);
|
|
12832
|
+
segLens.push(len);
|
|
12833
|
+
total += len;
|
|
12834
|
+
}
|
|
12835
|
+
let target = total / 2;
|
|
12836
|
+
for (let i = 0; i < segLens.length; i++) {
|
|
12837
|
+
if (target <= segLens[i]) {
|
|
12838
|
+
const t = segLens[i] === 0 ? 0 : target / segLens[i];
|
|
12839
|
+
const a = points[i];
|
|
12840
|
+
const b = points[i + 1];
|
|
12841
|
+
return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
|
|
12842
|
+
}
|
|
12843
|
+
target -= segLens[i];
|
|
12844
|
+
}
|
|
12845
|
+
return points[points.length - 1];
|
|
12846
|
+
}
|
|
12847
|
+
function insetEndpoints(points, inset) {
|
|
12848
|
+
if (points.length < 2) return points;
|
|
12849
|
+
const result = points.slice();
|
|
12850
|
+
if (inset.from > 0) {
|
|
12851
|
+
const a = result[0];
|
|
12852
|
+
const b = result[1];
|
|
12853
|
+
const len = distance(a, b);
|
|
12854
|
+
if (len > inset.from) {
|
|
12855
|
+
const t = inset.from / len;
|
|
12856
|
+
result[0] = { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
|
|
12857
|
+
}
|
|
12858
|
+
}
|
|
12859
|
+
if (inset.to > 0) {
|
|
12860
|
+
const lastIdx = result.length - 1;
|
|
12861
|
+
const a = result[lastIdx];
|
|
12862
|
+
const b = result[lastIdx - 1];
|
|
12863
|
+
const len = distance(a, b);
|
|
12864
|
+
if (len > inset.to) {
|
|
12865
|
+
const t = inset.to / len;
|
|
12866
|
+
result[lastIdx] = { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
|
|
12867
|
+
}
|
|
12868
|
+
}
|
|
12869
|
+
return result;
|
|
12870
|
+
}
|
|
12871
|
+
var HEADER_HEIGHT = 36;
|
|
12872
|
+
var FIELD_HEIGHT = 29;
|
|
12873
|
+
var DEFAULT_COLOR2 = "#71717a";
|
|
12874
|
+
function markerDirection(side) {
|
|
12875
|
+
switch (side) {
|
|
12876
|
+
case "left":
|
|
12877
|
+
return "right";
|
|
12878
|
+
// entity body is to the right of a left-side anchor
|
|
12879
|
+
case "right":
|
|
12880
|
+
return "left";
|
|
12881
|
+
case "top":
|
|
12882
|
+
return "down";
|
|
12883
|
+
case "bottom":
|
|
12884
|
+
return "up";
|
|
12885
|
+
}
|
|
12886
|
+
}
|
|
12887
|
+
function strokeDashArray(style) {
|
|
12888
|
+
switch (style) {
|
|
12889
|
+
case "dashed":
|
|
12890
|
+
return "8 4";
|
|
12891
|
+
case "dotted":
|
|
12892
|
+
return "2 4";
|
|
12893
|
+
default:
|
|
12894
|
+
return void 0;
|
|
12895
|
+
}
|
|
12332
12896
|
}
|
|
12333
12897
|
function DiagramRelation({
|
|
12334
12898
|
from,
|
|
@@ -12336,6 +12900,12 @@ function DiagramRelation({
|
|
|
12336
12900
|
fromField: fromFieldProp,
|
|
12337
12901
|
toField: toFieldProp,
|
|
12338
12902
|
type,
|
|
12903
|
+
fromMarker: fromMarkerProp,
|
|
12904
|
+
toMarker: toMarkerProp,
|
|
12905
|
+
lineStyle: lineStyleProp,
|
|
12906
|
+
routing = "manhattan",
|
|
12907
|
+
color = DEFAULT_COLOR2,
|
|
12908
|
+
strokeWidth = 2,
|
|
12339
12909
|
label
|
|
12340
12910
|
}) {
|
|
12341
12911
|
const { nodeRects, registryVersion } = useCanvas();
|
|
@@ -12344,68 +12914,160 @@ function DiagramRelation({
|
|
|
12344
12914
|
const fromRect = nodeRects.get(from);
|
|
12345
12915
|
const toRect = nodeRects.get(to);
|
|
12346
12916
|
if (!fromRect || !toRect) return null;
|
|
12917
|
+
const defaults = defaultMarkersForType(type);
|
|
12918
|
+
const fromMarker = fromMarkerProp ?? defaults.fromMarker;
|
|
12919
|
+
const toMarker = toMarkerProp ?? defaults.toMarker;
|
|
12920
|
+
const lineStyle = lineStyleProp ?? defaults.lineStyle;
|
|
12347
12921
|
const fromEntity = schema.entities.find((e) => (e.id ?? e.name) === from);
|
|
12348
12922
|
const toEntity = schema.entities.find((e) => (e.id ?? e.name) === to);
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
}
|
|
12367
|
-
const fromFieldY = fromFieldIdx >= 0 ? HEADER_HEIGHT + fromFieldIdx * FIELD_HEIGHT + FIELD_HEIGHT / 2 : fromRect.height / 2;
|
|
12368
|
-
const toFieldY = toFieldIdx >= 0 ? HEADER_HEIGHT + toFieldIdx * FIELD_HEIGHT + FIELD_HEIGHT / 2 : toRect.height / 2;
|
|
12369
|
-
const fromCx = fromRect.x + fromRect.width / 2;
|
|
12370
|
-
const toCx = toRect.x + toRect.width / 2;
|
|
12371
|
-
let fromPt, toPt;
|
|
12372
|
-
let fromDir;
|
|
12373
|
-
let toDir;
|
|
12374
|
-
if (fromCx <= toCx) {
|
|
12375
|
-
fromPt = { x: fromRect.x + fromRect.width, y: fromRect.y + fromFieldY };
|
|
12376
|
-
toPt = { x: toRect.x, y: toRect.y + toFieldY };
|
|
12377
|
-
fromDir = "right";
|
|
12378
|
-
toDir = "left";
|
|
12923
|
+
const fromFieldY = resolveFieldY(fromRect, fromEntity?.fields, fromFieldProp, true);
|
|
12924
|
+
const toFieldY = resolveFieldY(toRect, toEntity?.fields, toFieldProp, false, fromEntity?.name ?? from);
|
|
12925
|
+
const anchors = pickAnchors(fromRect, toRect, fromFieldY, toFieldY);
|
|
12926
|
+
if (anchors.from.side === "top" || anchors.from.side === "bottom") {
|
|
12927
|
+
anchors.from.x = fromRect.x + fromRect.width / 2;
|
|
12928
|
+
}
|
|
12929
|
+
if (anchors.to.side === "top" || anchors.to.side === "bottom") {
|
|
12930
|
+
anchors.to.x = toRect.x + toRect.width / 2;
|
|
12931
|
+
}
|
|
12932
|
+
let points;
|
|
12933
|
+
if (routing === "straight") {
|
|
12934
|
+
points = [
|
|
12935
|
+
{ x: anchors.from.x, y: anchors.from.y },
|
|
12936
|
+
{ x: anchors.to.x, y: anchors.to.y }
|
|
12937
|
+
];
|
|
12938
|
+
} else if (routing === "bezier") {
|
|
12939
|
+
points = [];
|
|
12379
12940
|
} else {
|
|
12380
|
-
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
if (
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
const
|
|
12393
|
-
const
|
|
12394
|
-
|
|
12395
|
-
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
const
|
|
12399
|
-
|
|
12400
|
-
|
|
12941
|
+
const obstacles = [];
|
|
12942
|
+
nodeRects.forEach((rect, id) => {
|
|
12943
|
+
if (id === from || id === to) return;
|
|
12944
|
+
obstacles.push(rect);
|
|
12945
|
+
});
|
|
12946
|
+
const initial = manhattanPath(anchors.from, anchors.to, obstacles);
|
|
12947
|
+
points = dodgeObstacles(initial, obstacles);
|
|
12948
|
+
}
|
|
12949
|
+
const insetAmount = { from: markerInset(fromMarker), to: markerInset(toMarker) };
|
|
12950
|
+
if (routing !== "bezier") {
|
|
12951
|
+
points = insetEndpoints(points, insetAmount);
|
|
12952
|
+
}
|
|
12953
|
+
const linePath = routing === "bezier" ? bezierPath2(anchors.from, anchors.to) : pathFromPoints(points);
|
|
12954
|
+
const fromMarkerRenderable = renderMarker(
|
|
12955
|
+
fromMarker,
|
|
12956
|
+
{ x: anchors.from.x, y: anchors.from.y },
|
|
12957
|
+
markerDirection(anchors.from.side)
|
|
12958
|
+
);
|
|
12959
|
+
const toMarkerRenderable = renderMarker(
|
|
12960
|
+
toMarker,
|
|
12961
|
+
{ x: anchors.to.x, y: anchors.to.y },
|
|
12962
|
+
markerDirection(anchors.to.side)
|
|
12963
|
+
);
|
|
12964
|
+
const mid = routing === "bezier" ? { x: (anchors.from.x + anchors.to.x) / 2, y: (anchors.from.y + anchors.to.y) / 2 } : midPoint(points);
|
|
12965
|
+
return {
|
|
12966
|
+
linePath,
|
|
12967
|
+
fromMarker: fromMarkerRenderable,
|
|
12968
|
+
toMarker: toMarkerRenderable,
|
|
12969
|
+
fromAnchor: anchors.from,
|
|
12970
|
+
toAnchor: anchors.to,
|
|
12971
|
+
mid,
|
|
12972
|
+
lineStyle
|
|
12973
|
+
};
|
|
12974
|
+
}, [
|
|
12975
|
+
from,
|
|
12976
|
+
to,
|
|
12977
|
+
fromFieldProp,
|
|
12978
|
+
toFieldProp,
|
|
12979
|
+
type,
|
|
12980
|
+
fromMarkerProp,
|
|
12981
|
+
toMarkerProp,
|
|
12982
|
+
lineStyleProp,
|
|
12983
|
+
routing,
|
|
12984
|
+
schema,
|
|
12985
|
+
nodeRects,
|
|
12986
|
+
registryVersion
|
|
12987
|
+
]);
|
|
12401
12988
|
if (!result) return null;
|
|
12989
|
+
const dashArray = strokeDashArray(result.lineStyle);
|
|
12402
12990
|
return /* @__PURE__ */ jsxRuntime.jsxs("g", { "data-react-fancy-diagram-relation": "", children: [
|
|
12403
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12991
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12992
|
+
"path",
|
|
12993
|
+
{
|
|
12994
|
+
d: result.linePath,
|
|
12995
|
+
fill: "none",
|
|
12996
|
+
stroke: color,
|
|
12997
|
+
strokeWidth,
|
|
12998
|
+
strokeDasharray: dashArray,
|
|
12999
|
+
strokeLinecap: "round",
|
|
13000
|
+
strokeLinejoin: "round"
|
|
13001
|
+
}
|
|
13002
|
+
),
|
|
13003
|
+
result.fromMarker?.paths.map((shape, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
13004
|
+
"path",
|
|
13005
|
+
{
|
|
13006
|
+
d: shape.d,
|
|
13007
|
+
fill: shape.fill === "stroke" ? color : shape.fill === "background" ? "#ffffff" : "none",
|
|
13008
|
+
stroke: color,
|
|
13009
|
+
strokeWidth,
|
|
13010
|
+
strokeLinecap: "round",
|
|
13011
|
+
strokeLinejoin: "round"
|
|
13012
|
+
},
|
|
13013
|
+
`fm-${i}`
|
|
13014
|
+
)),
|
|
13015
|
+
result.fromMarker?.text && /* @__PURE__ */ jsxRuntime.jsx(
|
|
13016
|
+
"text",
|
|
13017
|
+
{
|
|
13018
|
+
x: result.fromAnchor.x,
|
|
13019
|
+
y: result.fromAnchor.y,
|
|
13020
|
+
fontSize: 16,
|
|
13021
|
+
textAnchor: "middle",
|
|
13022
|
+
dominantBaseline: "middle",
|
|
13023
|
+
style: { userSelect: "none" },
|
|
13024
|
+
children: result.fromMarker.text
|
|
13025
|
+
}
|
|
13026
|
+
),
|
|
13027
|
+
result.toMarker?.text && /* @__PURE__ */ jsxRuntime.jsx(
|
|
13028
|
+
"text",
|
|
13029
|
+
{
|
|
13030
|
+
x: result.toAnchor.x,
|
|
13031
|
+
y: result.toAnchor.y,
|
|
13032
|
+
fontSize: 16,
|
|
13033
|
+
textAnchor: "middle",
|
|
13034
|
+
dominantBaseline: "middle",
|
|
13035
|
+
style: { userSelect: "none" },
|
|
13036
|
+
children: result.toMarker.text
|
|
13037
|
+
}
|
|
13038
|
+
),
|
|
13039
|
+
result.toMarker?.paths.map((shape, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
13040
|
+
"path",
|
|
13041
|
+
{
|
|
13042
|
+
d: shape.d,
|
|
13043
|
+
fill: shape.fill === "stroke" ? color : shape.fill === "background" ? "#ffffff" : "none",
|
|
13044
|
+
stroke: color,
|
|
13045
|
+
strokeWidth,
|
|
13046
|
+
strokeLinecap: "round",
|
|
13047
|
+
strokeLinejoin: "round"
|
|
13048
|
+
},
|
|
13049
|
+
`tm-${i}`
|
|
13050
|
+
)),
|
|
13051
|
+
label && /* @__PURE__ */ jsxRuntime.jsx("foreignObject", { x: result.mid.x - 50, y: result.mid.y - 12, width: 100, height: 24, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center text-xs text-zinc-500", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded bg-white/90 px-1.5 py-0.5 dark:bg-zinc-900/90", children: label }) }) })
|
|
12407
13052
|
] });
|
|
12408
13053
|
}
|
|
13054
|
+
function resolveFieldY(rect, fields, fieldProp, isFrom, fromName) {
|
|
13055
|
+
if (!fields || fields.length === 0) return void 0;
|
|
13056
|
+
let idx = -1;
|
|
13057
|
+
if (fieldProp) {
|
|
13058
|
+
idx = fields.findIndex((f) => f.name === fieldProp);
|
|
13059
|
+
} else if (isFrom) {
|
|
13060
|
+
idx = fields.findIndex((f) => f.primary);
|
|
13061
|
+
} else {
|
|
13062
|
+
const lower = (fromName ?? "").toLowerCase();
|
|
13063
|
+
idx = fields.findIndex(
|
|
13064
|
+
(f) => f.foreign && (f.name === `${lower}_id` || f.name === `${lower}Id`)
|
|
13065
|
+
);
|
|
13066
|
+
if (idx === -1) idx = fields.findIndex((f) => f.foreign);
|
|
13067
|
+
}
|
|
13068
|
+
if (idx < 0) return void 0;
|
|
13069
|
+
return HEADER_HEIGHT + idx * FIELD_HEIGHT + FIELD_HEIGHT / 2;
|
|
13070
|
+
}
|
|
12409
13071
|
DiagramRelation._isCanvasEdge = true;
|
|
12410
13072
|
DiagramRelation.displayName = "DiagramRelation";
|
|
12411
13073
|
var FORMAT_LABELS = {
|
|
@@ -12847,6 +13509,8 @@ function TreeNode({ node, depth }) {
|
|
|
12847
13509
|
dragState,
|
|
12848
13510
|
setDragState,
|
|
12849
13511
|
onNodeMove,
|
|
13512
|
+
acceptExternalDrops,
|
|
13513
|
+
onExternalDrop,
|
|
12850
13514
|
nodes,
|
|
12851
13515
|
expandNode
|
|
12852
13516
|
} = useTreeNav();
|
|
@@ -12889,14 +13553,18 @@ function TreeNode({ node, depth }) {
|
|
|
12889
13553
|
clearAutoExpand();
|
|
12890
13554
|
setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
|
|
12891
13555
|
}, [clearAutoExpand, setDragState]);
|
|
13556
|
+
const isExternalDrag = !dragState.draggedNodeId;
|
|
12892
13557
|
const handleDragOver = react.useCallback((e) => {
|
|
12893
|
-
if (
|
|
12894
|
-
|
|
12895
|
-
|
|
12896
|
-
|
|
13558
|
+
if (isExternalDrag) {
|
|
13559
|
+
if (!acceptExternalDrops) return;
|
|
13560
|
+
} else {
|
|
13561
|
+
const sourceId = dragState.draggedNodeId;
|
|
13562
|
+
if (sourceId === node.id) return;
|
|
13563
|
+
if (isDescendantOf(nodes, sourceId, node.id)) return;
|
|
13564
|
+
}
|
|
12897
13565
|
e.preventDefault();
|
|
12898
13566
|
e.stopPropagation();
|
|
12899
|
-
e.dataTransfer.dropEffect = "move";
|
|
13567
|
+
e.dataTransfer.dropEffect = isExternalDrag ? "copy" : "move";
|
|
12900
13568
|
const position = computeDropPosition(e, !!isFolder);
|
|
12901
13569
|
if (isFolder && !isExpanded && position === "inside") {
|
|
12902
13570
|
if (!autoExpandTimer.current) {
|
|
@@ -12909,9 +13577,9 @@ function TreeNode({ node, depth }) {
|
|
|
12909
13577
|
clearAutoExpand();
|
|
12910
13578
|
}
|
|
12911
13579
|
if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {
|
|
12912
|
-
setDragState({ draggedNodeId:
|
|
13580
|
+
setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });
|
|
12913
13581
|
}
|
|
12914
|
-
}, [dragState, node.id, isFolder, isExpanded, nodes, setDragState, expandNode, clearAutoExpand]);
|
|
13582
|
+
}, [dragState, isExternalDrag, acceptExternalDrops, node.id, isFolder, isExpanded, nodes, setDragState, expandNode, clearAutoExpand]);
|
|
12915
13583
|
const handleDragLeave = react.useCallback((e) => {
|
|
12916
13584
|
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
12917
13585
|
clearAutoExpand();
|
|
@@ -12925,21 +13593,25 @@ function TreeNode({ node, depth }) {
|
|
|
12925
13593
|
e.stopPropagation();
|
|
12926
13594
|
clearAutoExpand();
|
|
12927
13595
|
const sourceId = dragState.draggedNodeId;
|
|
12928
|
-
const position = dragState.dropPosition;
|
|
12929
|
-
if (
|
|
12930
|
-
|
|
12931
|
-
|
|
12932
|
-
|
|
13596
|
+
const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);
|
|
13597
|
+
if (sourceId) {
|
|
13598
|
+
if (sourceId === node.id) return;
|
|
13599
|
+
if (isDescendantOf(nodes, sourceId, node.id)) return;
|
|
13600
|
+
onNodeMove?.(sourceId, node.id, position);
|
|
13601
|
+
} else if (acceptExternalDrops) {
|
|
13602
|
+
onExternalDrop?.(e, node, position);
|
|
13603
|
+
}
|
|
12933
13604
|
setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
|
|
12934
|
-
}, [dragState, node
|
|
13605
|
+
}, [dragState, node, isFolder, nodes, onNodeMove, acceptExternalDrops, onExternalDrop, setDragState, clearAutoExpand]);
|
|
12935
13606
|
const canDrag = draggable && !node.disabled;
|
|
13607
|
+
const dropEnabled = draggable || acceptExternalDrops;
|
|
12936
13608
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
12937
13609
|
"div",
|
|
12938
13610
|
{
|
|
12939
13611
|
"data-react-fancy-tree-node": "",
|
|
12940
|
-
onDragOver:
|
|
12941
|
-
onDragLeave:
|
|
12942
|
-
onDrop:
|
|
13612
|
+
onDragOver: dropEnabled ? handleDragOver : void 0,
|
|
13613
|
+
onDragLeave: dropEnabled ? handleDragLeave : void 0,
|
|
13614
|
+
onDrop: dropEnabled ? handleDrop : void 0,
|
|
12943
13615
|
children: [
|
|
12944
13616
|
isDropTarget && dropPosition === "before" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
12945
13617
|
"div",
|
|
@@ -13012,6 +13684,8 @@ function TreeNavRoot({
|
|
|
13012
13684
|
onNodeContextMenu,
|
|
13013
13685
|
draggable = false,
|
|
13014
13686
|
onNodeMove,
|
|
13687
|
+
acceptExternalDrops = false,
|
|
13688
|
+
onExternalDrop,
|
|
13015
13689
|
expandedIds: controlledExpanded,
|
|
13016
13690
|
defaultExpandedIds,
|
|
13017
13691
|
onExpandedChange,
|
|
@@ -13065,6 +13739,8 @@ function TreeNavRoot({
|
|
|
13065
13739
|
dragState,
|
|
13066
13740
|
setDragState,
|
|
13067
13741
|
onNodeMove,
|
|
13742
|
+
acceptExternalDrops,
|
|
13743
|
+
onExternalDrop,
|
|
13068
13744
|
nodes,
|
|
13069
13745
|
expandNode
|
|
13070
13746
|
}),
|
|
@@ -13079,16 +13755,19 @@ function TreeNavRoot({
|
|
|
13079
13755
|
draggable,
|
|
13080
13756
|
dragState,
|
|
13081
13757
|
onNodeMove,
|
|
13758
|
+
acceptExternalDrops,
|
|
13759
|
+
onExternalDrop,
|
|
13082
13760
|
nodes,
|
|
13083
13761
|
expandNode
|
|
13084
13762
|
]
|
|
13085
13763
|
);
|
|
13764
|
+
const dropEnabled = draggable || acceptExternalDrops;
|
|
13086
13765
|
return /* @__PURE__ */ jsxRuntime.jsx(TreeNavContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
13087
13766
|
"nav",
|
|
13088
13767
|
{
|
|
13089
13768
|
"data-react-fancy-tree-nav": "",
|
|
13090
13769
|
className: cn("flex flex-col gap-0.5 py-1 text-sm", className),
|
|
13091
|
-
onDragEnd:
|
|
13770
|
+
onDragEnd: dropEnabled ? handleDragEnd : void 0,
|
|
13092
13771
|
children: nodes.map((node) => /* @__PURE__ */ jsxRuntime.jsx(TreeNode, { node, depth: 0 }, node.id))
|
|
13093
13772
|
}
|
|
13094
13773
|
) });
|