@delove/reaflow
Version:
Node-based Visualizations for React
1,379 lines (1,378 loc) • 85.5 kB
JavaScript
(function() {
"use strict";
try {
if (typeof document != "undefined") {
var elementStyle = document.createElement("style");
elementStyle.appendChild(document.createTextNode("._port_1r6fw_1 {\n stroke: #0d0e17;\n fill: #3e405a;\n stroke-width: 2px;\n shape-rendering: geometricPrecision;\n pointer-events: none;\n}\n\n._clicker_1r6fw_9 {\n opacity: 0;\n}\n\n._clicker_1r6fw_9:not(._disabled_1r6fw_12) {\n cursor: crosshair;\n }\n._text_fhkx6_1 {\n fill: #d6e7ff;\n pointer-events: none;\n font-size: 14px;\n text-rendering: geometricPrecision;\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n}\n._deleteX_nxq8k_1 {\n stroke: black;\n pointer-events: none;\n}\n\n._container_nxq8k_6 {\n will-change: transform, opacity;\n}\n\n._drop_nxq8k_10 {\n cursor: pointer;\n opacity: 0;\n}\n\n._rect_nxq8k_15 {\n shape-rendering: geometricPrecision;\n fill: #ff005d;\n border-radius: 2px;\n pointer-events: none;\n}\n._plus_1qsm8_1 {\n stroke: black;\n pointer-events: none;\n}\n\n._container_1qsm8_6 {\n will-change: transform, opacity;\n}\n\n._drop_1qsm8_10 {\n cursor: pointer;\n opacity: 0;\n}\n\n._rect_1qsm8_15 {\n shape-rendering: geometricPrecision;\n fill: #46FECB;\n border-radius: 2px;\n pointer-events: none;\n}\n._edge_v5z62_1._disabled_v5z62_2 {\n pointer-events: none;\n }\n ._edge_v5z62_1:not(._selectionDisabled_v5z62_6):not(._disabled_v5z62_2):hover ._path_v5z62_8 {\n stroke: #a5a9e2;\n }\n ._edge_v5z62_1:not(._selectionDisabled_v5z62_6):not(._disabled_v5z62_2):hover ._path_v5z62_8._active_v5z62_11 {\n stroke: #46fecb;\n }\n ._edge_v5z62_1:not(._selectionDisabled_v5z62_6):not(._disabled_v5z62_2):hover ._path_v5z62_8._deleteHovered_v5z62_15 {\n stroke: #ff005d;\n stroke-dasharray: 4 2;\n }\n ._edge_v5z62_1:not(._selectionDisabled_v5z62_6):not(._disabled_v5z62_2) ._clicker_v5z62_22 {\n cursor: pointer;\n }\n\n._path_v5z62_8 {\n fill: transparent;\n stroke: #485a74;\n pointer-events: none;\n shape-rendering: geometricPrecision;\n stroke-width: 1pt;\n}\n\n._clicker_v5z62_22 {\n fill: none;\n stroke: transparent;\n stroke-width: 15px;\n}\n\n._clicker_v5z62_22:focus {\n outline: none;\n }\n._rect_1b6xi_1 {\n fill: #2b2c3e;\n transition: stroke 100ms ease-in-out;\n stroke: #475872;\n shape-rendering: geometricPrecision;\n stroke-width: 1pt;\n}\n\n ._rect_1b6xi_1:not(._selectionDisabled_1b6xi_8):not(._disabled_1b6xi_8) {\n cursor: pointer;\n }\n\n ._rect_1b6xi_1:not(._selectionDisabled_1b6xi_8):not(._disabled_1b6xi_8):hover {\n stroke: #a5a9e2;\n }\n\n ._rect_1b6xi_1:not(._selectionDisabled_1b6xi_8):not(._disabled_1b6xi_8)._dragging_1b6xi_15 {\n stroke: #a5a9e2;\n }\n\n ._rect_1b6xi_1:not(._selectionDisabled_1b6xi_8):not(._disabled_1b6xi_8)._active_1b6xi_19 {\n stroke: #46fecb;\n }\n\n ._rect_1b6xi_1:not(._selectionDisabled_1b6xi_8):not(._disabled_1b6xi_8)._unlinkable_1b6xi_23 {\n stroke: #ff005d;\n }\n\n ._rect_1b6xi_1:not(._selectionDisabled_1b6xi_8):not(._disabled_1b6xi_8)._deleteHovered_1b6xi_27 {\n stroke: #ff005d !important;\n stroke-dasharray: 4 2;\n }\n\n ._rect_1b6xi_1:focus {\n outline: none;\n }\n\n ._rect_1b6xi_1._children_1b6xi_37 {\n fill: transparent;\n stroke: #475872;\n }\n._arrow_4r5xg_1 {\n pointer-events: none;\n shape-rendering: geometricPrecision;\n fill: #485a74;\n}\n._container_1ryvh_1._pannable_1ryvh_2 {\n overflow: auto;\n }\n ._container_1ryvh_1:focus {\n outline: none;\n }\n\n.dragging {\n -webkit-touch-callout: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n}\n\n._dragNode_1ryvh_20 {\n pointer-events: none;\n}\n\n._draggable_1ryvh_24 {\n scrollbar-width: none; /* Firefox */\n -ms-overflow-style: none; /* Internet Explorer 10+ */\n cursor: grab;\n}\n\n._draggable_1ryvh_24::-webkit-scrollbar {\n display: none; /* WebKit */\n }\n\n._draggable_1ryvh_24:active {\n cursor: grabbing;\n }\n._icon_6o39n_1 {\n pointer-events: none;\n}"));
document.head.appendChild(elementStyle);
}
} catch (e) {
console.error("vite-plugin-css-injected-by-js", e);
}
})();
import { jsx, jsxs, Fragment as Fragment$1 } from "react/jsx-runtime";
import React, { useRef, useState, useEffect, useCallback, useLayoutEffect, createContext, useContext, forwardRef, Fragment, useMemo, useImperativeHandle } from "react";
import { CloneElement, useId } from "reablocks";
import { useGesture, useDrag } from "react-use-gesture";
import { motion, useAnimation } from "motion/react";
import useDimensions from "react-cool-dimensions";
import isEqual from "react-fast-compare";
import ELK from "elkjs/lib/elk.bundled.js";
import PCancelable from "p-cancelable";
import calculateSize from "calculate-size";
import ellipsize from "ellipsize";
import { Point2D, Matrix2D } from "kld-affine";
import classNames from "classnames";
import { line, curveBundle } from "d3-shape";
import { useHotkeys } from "reakeys";
import Undoo from "undoo";
import { IntersectionQuery } from "kld-intersections";
var CanvasPosition = /* @__PURE__ */ ((CanvasPosition2) => {
CanvasPosition2["CENTER"] = "center";
CanvasPosition2["TOP"] = "top";
CanvasPosition2["LEFT"] = "left";
CanvasPosition2["RIGHT"] = "right";
CanvasPosition2["BOTTOM"] = "bottom";
return CanvasPosition2;
})(CanvasPosition || {});
const MAX_CHAR_COUNT = 35;
const MIN_NODE_WIDTH = 50;
const DEFAULT_NODE_HEIGHT = 50;
const NODE_PADDING = 30;
const ICON_PADDING = 10;
function measureText(text2) {
let result = { height: 0, width: 0 };
if (text2) {
const fn = typeof calculateSize === "function" ? calculateSize : calculateSize.default;
result = fn(text2, {
font: "Arial, sans-serif",
fontSize: "14px"
});
}
return result;
}
function parsePadding(padding) {
let top = 50;
let right = 50;
let bottom = 50;
let left = 50;
if (Array.isArray(padding)) {
if (padding.length === 2) {
top = padding[0];
bottom = padding[0];
left = padding[1];
right = padding[1];
} else if (padding.length === 4) {
top = padding[0];
right = padding[1];
bottom = padding[2];
left = padding[3];
}
} else if (padding !== void 0) {
top = padding;
right = padding;
bottom = padding;
left = padding;
}
return {
top,
right,
bottom,
left
};
}
function formatText(node) {
const text2 = node.text ? ellipsize(node.text, MAX_CHAR_COUNT) : node.text;
const labelDim = measureText(text2);
const nodePadding = parsePadding(node.nodePadding);
let width = node.width;
if (width === void 0) {
if (text2 && node.icon) {
width = labelDim.width + node.icon.width + NODE_PADDING + ICON_PADDING;
} else {
if (text2) {
width = labelDim.width + NODE_PADDING;
} else if (node.icon) {
width = node.icon.width + NODE_PADDING;
}
width = Math.max(width, MIN_NODE_WIDTH);
}
}
let height = node.height;
if (height === void 0) {
if (text2 && node.icon) {
height = labelDim.height + node.icon.height;
} else if (text2) {
height = labelDim.height + NODE_PADDING;
} else if (node.icon) {
height = node.icon.height + NODE_PADDING;
}
height = Math.max(height, DEFAULT_NODE_HEIGHT);
}
return {
text: text2,
originalText: node.text,
width,
height,
nodePadding,
labelHeight: labelDim.height,
labelWidth: labelDim.width
};
}
const findNode = (nodes, nodeId) => {
for (const node of nodes) {
if (node.id === nodeId) {
return node;
}
if (node.children) {
const foundNode = findNode(node.children, nodeId);
if (foundNode) {
return foundNode;
}
}
}
return void 0;
};
const getChildCount = (node) => {
var _a;
return ((_a = node.children) == null ? void 0 : _a.reduce((acc, child) => {
if (child.children) {
return acc + 1 + getChildCount(child);
}
return acc + 1;
}, 0)) ?? 0;
};
const calculateZoom = ({ nodes, viewportWidth, viewportHeight, maxViewportCoverage = 0.9, minViewportCoverage = 0.2 }) => {
const maxChildren = Math.max(
0,
nodes.map(getChildCount).reduce((acc, curr) => acc + curr, 0)
);
const boundingBox = getNodesBoundingBox(nodes);
const boundingBoxWidth = boundingBox.x1 - boundingBox.x0;
const boundingBoxHeight = boundingBox.y1 - boundingBox.y0;
const maxNodeWidth = Math.max(...nodes.map((node) => node.width));
const maxNodeHeight = Math.max(...nodes.map((node) => node.height));
const maxNodeZoomX = (0.2 + maxChildren * 0.1) * viewportWidth / maxNodeWidth;
const maxNodeZoomY = (0.2 + maxChildren * 0.1) * viewportHeight / maxNodeHeight;
const maxNodeZoom = Math.min(maxNodeZoomX, maxNodeZoomY);
const viewportCoverage = Math.max(Math.min(maxViewportCoverage, maxNodeZoom), minViewportCoverage);
const updatedHorizontalZoom = viewportCoverage * viewportWidth / boundingBoxWidth;
const updatedVerticalZoom = viewportCoverage * viewportHeight / boundingBoxHeight;
const updatedZoom = Math.min(updatedHorizontalZoom, updatedVerticalZoom, maxNodeZoom);
return updatedZoom;
};
const calculateScrollPosition = ({ nodes, viewportWidth, viewportHeight, canvasWidth, canvasHeight, chartWidth, chartHeight, zoom }) => {
const { x0, y0, x1, y1 } = getNodesBoundingBox(nodes);
const boundingBoxWidth = (x1 - x0) * zoom;
const boundingBoxHeight = (y1 - y0) * zoom;
const chartPosition = {
x: (canvasWidth - chartWidth * zoom) / 2,
y: (canvasHeight - chartHeight * zoom) / 2
};
const boxXPosition = chartPosition.x + x0 * zoom;
const boxYPosition = chartPosition.y + y0 * zoom;
const boxCenterXPosition = boxXPosition + boundingBoxWidth / 2;
const boxCenterYPosition = boxYPosition + boundingBoxHeight / 2;
const scrollX = boxCenterXPosition - viewportWidth / 2;
const scrollY = boxCenterYPosition - viewportHeight / 2;
return [scrollX, scrollY];
};
const getNodesBoundingBox = (nodes) => {
return nodes.reduce(
(acc, node) => ({
x0: Math.min(acc.x0, node.x),
y0: Math.min(acc.y0, node.y),
x1: Math.max(acc.x1, node.x + node.width),
y1: Math.max(acc.y1, node.y + node.height)
}),
{ x0: nodes[0].x, y0: nodes[0].y, x1: nodes[0].x + nodes[0].width, y1: nodes[0].y + nodes[0].height }
);
};
const defaultLayoutOptions = {
/**
* Hints for where node labels are to be placed; if empty, the node label’s position is not modified.
*
* @see https://www.eclipse.org/elk/reference/options/org-eclipse-elk-nodeLabels-placement.html
*/
"elk.nodeLabels.placement": "INSIDE V_CENTER H_RIGHT",
/**
* Select a specific layout algorithm.
*
* Uses "layered" strategy.
* It emphasizes the direction of edges by pointing as many edges as possible into the same direction.
* The nodes are arranged in layers, which are sometimes called “hierarchies”,
* and then reordered such that the number of edge crossings is minimized.
* Afterwards, concrete coordinates are computed for the nodes and edge bend points.
*
* @see https://www.eclipse.org/elk/reference/algorithms.html
* @see https://www.eclipse.org/elk/reference/options/org-eclipse-elk-algorithm.html
* @see https://www.eclipse.org/elk/reference/algorithms/org-eclipse-elk-layered.html
*/
"elk.algorithm": "org.eclipse.elk.layered",
/**
* Overall direction of edges: horizontal (right / left) or vertical (down / up).
*
* @see https://www.eclipse.org/elk/reference/options/org-eclipse-elk-direction.html
*/
"elk.direction": "DOWN",
/**
* The node order given by the model does not change to produce a better layout.
* E.g. if node A is before node B in the model this is not changed during crossing minimization.
* This assumes that the node model order is already respected before crossing minimization. This
* can be achieved by setting considerModelOrder.strategy to NODES_AND_EDGES.
*
* @see https://eclipse.dev/elk/reference/options/org-eclipse-elk-layered-crossingMinimization-forceNodeModelOrder.html
*/
"layered.crossingMinimization.forceNodeModelOrder": "true",
/**
* Strategy for node layering.
*
* @see https://www.eclipse.org/elk/reference/options/org-eclipse-elk-layered-layering-strategy.html
*/
"org.eclipse.elk.layered.layering.strategy": "INTERACTIVE",
/**
* What kind of edge routing style should be applied for the content of a parent node.
* Algorithms may also set this option to single edges in order to mark them as splines.
* The bend point list of edges with this option set to SPLINES
* must be interpreted as control points for a piecewise cubic spline.
*
* @see https://www.eclipse.org/elk/reference/options/org-eclipse-elk-edgeRouting.html
*/
"org.eclipse.elk.edgeRouting": "ORTHOGONAL",
/**
* Adds bend points even if an edge does not change direction.
* If true, each long edge dummy will contribute a bend point to its edges
* and hierarchy-crossing edges will always get a bend point where they cross hierarchy boundaries.
* By default, bend points are only added where an edge changes direction.
*
* @see https://www.eclipse.org/elk/reference/options/org-eclipse-elk-layered-unnecessaryBendpoints.html
*/
"elk.layered.unnecessaryBendpoints": "true",
/**
* The spacing to be preserved between nodes and edges that are routed next to the node’s layer.
* For the spacing between nodes and edges that cross the node’s layer ‘spacing.edgeNode’ is used.
*
* @see https://www.eclipse.org/elk/reference/options/org-eclipse-elk-layered-spacing-edgeNodeBetweenLayers.html
*/
"elk.layered.spacing.edgeNodeBetweenLayers": "50",
/**
* Tells the BK node placer to use a certain alignment (out of its four)
* instead of the one producing the smallest height, or the combination of all four.
*
* @see https://www.eclipse.org/elk/reference/options/org-eclipse-elk-layered-nodePlacement-bk-fixedAlignment.html
*/
"org.eclipse.elk.layered.nodePlacement.bk.fixedAlignment": "BALANCED",
/**
* Strategy for cycle breaking.
*
* Cycle breaking looks for cycles in the graph and determines which edges to reverse to break the cycles.
* Reversed edges will end up pointing to the opposite direction of regular edges
* (that is, reversed edges will point left if edges usually point right).
*
* @see https://www.eclipse.org/elk/reference/options/org-eclipse-elk-layered-cycleBreaking-strategy.html
*/
"org.eclipse.elk.layered.cycleBreaking.strategy": "DEPTH_FIRST",
/**
* Whether this node allows to route self loops inside of it instead of around it.
*
* If set to true, this will make the node a compound node if it isn’t already,
* and will require the layout algorithm to support compound nodes with hierarchical ports.
*
* @see https://www.eclipse.org/elk/reference/options/org-eclipse-elk-insideSelfLoops-activate.html
*/
"org.eclipse.elk.insideSelfLoops.activate": "true",
/**
* Whether each connected component should be processed separately.
*
* @see https://www.eclipse.org/elk/reference/options/org-eclipse-elk-separateConnectedComponents.html
*/
separateConnectedComponents: "false",
/**
* Spacing to be preserved between pairs of connected components.
* This option is only relevant if ‘separateConnectedComponents’ is activated.
*
* @see https://www.eclipse.org/elk/reference/options/org-eclipse-elk-spacing-componentComponent.html
*/
"spacing.componentComponent": "70",
/**
* TODO: Should be spacing.baseValue?
* An optional base value for all other layout options of the ‘spacing’ group.
* It can be used to conveniently alter the overall ‘spaciousness’ of the drawing.
* Whenever an explicit value is set for the other layout options, this base value will have no effect.
* The base value is not inherited, i.e. it must be set for each hierarchical node.
*
* @see https://www.eclipse.org/elk/reference/groups/org-eclipse-elk-layered-spacing.html
*/
spacing: "75",
/**
* The spacing to be preserved between any pair of nodes of two adjacent layers.
* Note that ‘spacing.nodeNode’ is used for the spacing between nodes within the layer itself.
*
* @see https://www.eclipse.org/elk/reference/options/org-eclipse-elk-layered-spacing-nodeNodeBetweenLayers.html
*/
"spacing.nodeNodeBetweenLayers": "70"
};
function mapNode(nodes, edges, node) {
const { text: text2, width, height, labelHeight, labelWidth, nodePadding, originalText } = formatText(node);
const children2 = nodes.filter((n) => n.parent === node.id).map((n) => mapNode(nodes, edges, n));
const childEdges = edges.filter((e) => e.parent === node.id).map((e) => mapEdge({ edge: e }));
const nodeLayoutOptions = {
"elk.padding": `[left=${nodePadding.left}, top=${nodePadding.top}, right=${nodePadding.right}, bottom=${nodePadding.bottom}]`,
portConstraints: "FIXED_ORDER",
...node.layoutOptions || {}
};
return {
id: node.id,
height,
width,
children: children2,
edges: childEdges,
ports: node.ports ? node.ports.map((port2) => ({
id: port2.id,
properties: {
...port2,
"port.side": port2.side,
"port.alignment": port2.alignment || "CENTER"
}
})) : [],
layoutOptions: nodeLayoutOptions,
properties: {
...node
},
labels: text2 ? [
{
width: labelWidth,
height: -(labelHeight / 2),
text: text2,
originalText
// layoutOptions: { 'elk.nodeLabels.placement': 'INSIDE V_CENTER H_CENTER' }
}
] : []
};
}
function mapEdge({ edge: { data, ...edge2 }, direction }) {
const labelDim = measureText(edge2.text);
const validEdgeData = data ? { data } : {};
let labelWidth = labelDim.width / 2;
if (direction === "LEFT" || direction === "RIGHT") {
labelWidth = labelDim.width;
}
return {
id: edge2.id,
source: edge2.from,
target: edge2.to,
properties: {
...edge2
},
...validEdgeData,
sourcePort: edge2.fromPort,
targetPort: edge2.toPort,
labels: edge2.text ? [
{
width: labelWidth,
height: -(labelDim.height / 2),
text: edge2.text,
layoutOptions: {
"elk.edgeLabels.placement": "INSIDE V_CENTER H_CENTER"
}
}
] : []
};
}
function mapInput({ nodes, edges, direction }) {
const children2 = [];
const mappedEdges = [];
for (const node of nodes) {
if (!node.parent) {
const mappedNode = mapNode(nodes, edges, node);
if (mappedNode !== null) {
children2.push(mappedNode);
}
}
}
for (const edge2 of edges) {
if (!edge2.parent) {
const mappedEdge = mapEdge({ edge: edge2, direction });
if (mappedEdge !== null) {
mappedEdges.push(mappedEdge);
}
}
}
return {
children: children2,
edges: mappedEdges
};
}
function postProcessNode(nodes) {
var _a;
for (const node of nodes) {
const hasLabels = ((_a = node.labels) == null ? void 0 : _a.length) > 0;
if (hasLabels && node.properties.icon) {
const [label] = node.labels;
label.x = node.properties.icon.width + 25;
node.properties.icon.x = 25;
node.properties.icon.y = node.height / 2;
} else if (hasLabels) {
const [label] = node.labels;
label.x = (node.width - label.width) / 2;
} else if (node.properties.icon) {
node.properties.icon.x = node.width / 2;
node.properties.icon.y = node.height / 2;
}
if (node.children) {
postProcessNode(node.children);
}
}
return nodes;
}
const elkLayout = (nodes, edges, options) => {
const graph = new ELK();
const layoutOptions = {
...defaultLayoutOptions,
...options
};
return new PCancelable((resolve, reject) => {
graph.layout(
{
id: "root",
...mapInput({ nodes, edges, direction: layoutOptions == null ? void 0 : layoutOptions["elk.direction"] })
},
{
layoutOptions
}
).then((data) => {
resolve({
...data,
children: postProcessNode(data.children)
});
}).catch(reject);
});
};
const useLayout = ({ maxWidth, maxHeight, nodes = [], edges = [], fit, pannable: pannable2, defaultPosition, direction, layoutOptions = {}, zoom, setZoom, onLayoutChange }) => {
const scrolled = useRef(false);
const ref = useRef();
const { observe, width, height } = useDimensions();
const [layout, setLayout] = useState(null);
const [xy, setXY] = useState([0, 0]);
const [scrollXY, setScrollXY] = useState([0, 0]);
const canvasHeight = pannable2 ? maxHeight : height;
const canvasWidth = pannable2 ? maxWidth : width;
const scrollToXY = (xy2, animated = false) => {
ref.current.scrollTo({ left: xy2[0], top: xy2[1], behavior: animated ? "smooth" : "auto" });
setScrollXY(xy2);
};
useEffect(() => {
const promise = elkLayout(nodes, edges, {
"elk.direction": direction,
...layoutOptions
});
promise.then((result) => {
if (!isEqual(layout, result)) {
setLayout(result);
onLayoutChange(result);
}
}).catch((err) => {
if (err.name !== "CancelError") {
console.error("Layout Error:", err);
}
});
return () => promise.cancel();
}, [nodes, edges]);
const positionVector = useCallback(
(position) => {
if (layout) {
const centerX = (canvasWidth - layout.width * zoom) / 2;
const centerY = (canvasHeight - layout.height * zoom) / 2;
switch (position) {
case CanvasPosition.CENTER:
setXY([centerX, centerY]);
break;
case CanvasPosition.TOP:
setXY([centerX, 0]);
break;
case CanvasPosition.LEFT:
setXY([0, centerY]);
break;
case CanvasPosition.RIGHT:
setXY([canvasWidth - layout.width * zoom, centerY]);
break;
case CanvasPosition.BOTTOM:
setXY([centerX, canvasHeight - layout.height * zoom]);
break;
}
}
},
[canvasWidth, canvasHeight, layout, zoom]
);
const positionScroll = useCallback(
(position, animated = false) => {
const scrollCenterX = (canvasWidth - width) / 2;
const scrollCenterY = (canvasHeight - height) / 2;
if (pannable2) {
switch (position) {
case CanvasPosition.CENTER:
scrollToXY([scrollCenterX, scrollCenterY], animated);
break;
case CanvasPosition.TOP:
scrollToXY([scrollCenterX, 0], animated);
break;
case CanvasPosition.LEFT:
scrollToXY([0, scrollCenterY], animated);
break;
case CanvasPosition.RIGHT:
scrollToXY([canvasWidth - width, scrollCenterY], animated);
break;
case CanvasPosition.BOTTOM:
scrollToXY([scrollCenterX, canvasHeight - height], animated);
break;
}
}
},
[canvasWidth, canvasHeight, width, height, pannable2]
);
const positionCanvas = useCallback(
(position, animated = false) => {
positionVector(position);
positionScroll(position, animated);
},
[positionScroll, positionVector]
);
useEffect(() => {
if (scrolled.current && defaultPosition) {
positionVector(defaultPosition);
}
}, [positionVector, zoom, defaultPosition]);
const fitCanvas = useCallback(
(animated = false) => {
if (layout) {
const heightZoom = height / layout.height;
const widthZoom = width / layout.width;
const scale = Math.min(heightZoom, widthZoom, 1);
setZoom(scale - 1);
positionCanvas(CanvasPosition.CENTER, animated);
}
},
[height, layout, width, setZoom, positionCanvas]
);
const fitNodes = useCallback(
(nodeIds, animated = true) => {
if (layout && layout.children) {
const nodes2 = Array.isArray(nodeIds) ? nodeIds.map((nodeId) => findNode(layout.children, nodeId)) : [findNode(layout.children, nodeIds)];
if (nodes2) {
positionVector(CanvasPosition.CENTER);
const updatedZoom = calculateZoom({ nodes: nodes2, viewportWidth: width, viewportHeight: height, maxViewportCoverage: 0.9, minViewportCoverage: 0.2 });
const scrollPosition = calculateScrollPosition({ nodes: nodes2, viewportWidth: width, viewportHeight: height, canvasWidth, canvasHeight, chartWidth: layout.width, chartHeight: layout.height, zoom: updatedZoom });
setZoom(updatedZoom - 1);
scrollToXY(scrollPosition, animated);
}
}
},
[canvasHeight, canvasWidth, height, layout, positionVector, setZoom, width]
);
useLayoutEffect(() => {
const scroller = ref.current;
if (scroller && !scrolled.current && layout && height && width) {
if (fit) {
fitCanvas();
} else if (defaultPosition) {
positionCanvas(defaultPosition);
}
scrolled.current = true;
}
}, [canvasWidth, pannable2, canvasHeight, layout, height, fit, width, defaultPosition, positionCanvas, fitCanvas, ref]);
useLayoutEffect(() => {
function onResize() {
if (fit) {
fitCanvas();
} else if (defaultPosition) {
positionCanvas(defaultPosition);
}
}
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, [fit, positionCanvas, defaultPosition, fitCanvas]);
return {
xy,
observe,
containerRef: ref,
canvasHeight,
canvasWidth,
containerWidth: width,
containerHeight: height,
layout,
scrollXY,
positionCanvas,
fitCanvas,
fitNodes,
setScrollXY: scrollToXY
};
};
const useEdgeDrag = ({
onNodeLink,
onNodeLinkCheck
}) => {
const [dragNode2, setDragNode] = useState(null);
const [dragPort, setDragPort] = useState(null);
const [dragType, setDragType] = useState(null);
const [enteredNode, setEnteredNode] = useState(null);
const [dragCoords, setDragCoords] = useState(null);
const [canLinkNode, setCanLinkNode] = useState(null);
const onDragStart = useCallback(
(state, _initial, node, port2) => {
setDragType(state.dragType);
setDragNode(node);
setDragPort(port2);
},
[]
);
const onDrag = useCallback(
({ memo: [matrix], xy: [x, y] }, [ix, iy]) => {
const endPoint = new Point2D(x, y).transform(matrix);
setDragCoords([
{
startPoint: {
x: ix,
y: iy
},
endPoint
}
]);
},
[]
);
const onDragEnd = useCallback(
(event) => {
if (dragNode2 && enteredNode && canLinkNode) {
onNodeLink(event, dragNode2, enteredNode, dragPort);
}
setDragNode(null);
setDragPort(null);
setEnteredNode(null);
setDragCoords(null);
},
[canLinkNode, dragNode2, dragPort, enteredNode, onNodeLink]
);
const onEnter = useCallback(
(event, node) => {
if (dragNode2 && node) {
setEnteredNode(node);
const canLink = onNodeLinkCheck(event, dragNode2, node, dragPort);
const result = (canLink === void 0 || canLink) && (dragNode2.parent === node.parent || dragType === "node");
setCanLinkNode(result);
}
},
[dragNode2, dragPort, dragType, onNodeLinkCheck]
);
const onLeave = useCallback(
(event, node) => {
if (dragNode2 && node) {
setEnteredNode(null);
setCanLinkNode(null);
}
},
[dragNode2]
);
return {
dragCoords,
canLinkNode,
dragNode: dragNode2,
dragPort,
enteredNode,
onDragStart,
onDrag,
onDragEnd,
onEnter,
onLeave
};
};
const limit = (scale, min, max) => scale < max ? scale > min ? scale : min : max;
const useZoom = ({ disabled: disabled2 = false, zoom = 1, minZoom = -0.5, maxZoom = 1, onZoomChange }) => {
const [factor, setFactor] = useState(zoom - 1);
const svgRef = useRef(null);
useGesture(
{
onPinch: ({ offset: [d], event }) => {
event.preventDefault();
const next = limit(d / 100, minZoom, maxZoom);
setFactor(next);
onZoomChange(next + 1);
}
},
{
enabled: !disabled2,
domTarget: svgRef,
eventOptions: { passive: false }
}
);
const setZoom = useCallback(
(f) => {
const next = limit(f, minZoom, maxZoom);
setFactor(next);
onZoomChange(next + 1);
},
[maxZoom, minZoom, onZoomChange]
);
const zoomIn = useCallback(
(zoomFactor = 0.1) => {
setZoom(factor + zoomFactor);
},
[factor, setZoom]
);
const zoomOut = useCallback(
(zoomFactor = -0.1) => {
setZoom(factor + zoomFactor);
},
[factor, setZoom]
);
return {
svgRef,
zoom: factor + 1,
setZoom,
zoomIn,
zoomOut
};
};
const CanvasContext = createContext({});
const CanvasProvider = ({
selections,
onNodeLink,
readonly,
children: children2,
nodes,
edges,
maxHeight,
fit,
maxWidth,
direction,
layoutOptions,
pannable: pannable2,
panType,
defaultPosition,
zoomable,
zoom,
minZoom,
maxZoom,
onNodeLinkCheck,
onLayoutChange,
onZoomChange
}) => {
const zoomProps = useZoom({
zoom,
minZoom,
maxZoom,
disabled: !zoomable,
onZoomChange
});
const layoutProps = useLayout({
nodes,
edges,
maxHeight,
maxWidth,
direction,
pannable: pannable2,
panType,
defaultPosition,
fit,
layoutOptions,
zoom: zoomProps.zoom,
setZoom: zoomProps.setZoom,
onLayoutChange
});
const dragProps = useEdgeDrag({
onNodeLink,
onNodeLinkCheck
});
return /* @__PURE__ */ jsx(
CanvasContext.Provider,
{
value: {
selections,
readonly,
pannable: pannable2,
panType,
...layoutProps,
...zoomProps,
...dragProps
},
children: children2
}
);
};
const useCanvas = () => {
const context = useContext(CanvasContext);
if (context === void 0) {
throw new Error(
"`useCanvas` hook must be used within a `CanvasContext` component"
);
}
return context;
};
function checkNodeLinkable(curNode, enteredNode, canLinkNode) {
if (canLinkNode === null || !enteredNode) {
return null;
}
if (!enteredNode || !curNode) {
return false;
}
return !(canLinkNode === false && enteredNode.id === curNode.id);
}
function getCoords({ zoom, layoutXY, containerRef }) {
const { top, left } = containerRef.current.getBoundingClientRect();
const tx = layoutXY[0] - containerRef.current.scrollLeft + left;
const ty = layoutXY[1] - containerRef.current.scrollTop + top;
return new Matrix2D().translate(tx, ty).scale(zoom).inverse();
}
function findNestedNode(nodeId, children2, parentId) {
if (!nodeId || !children2) {
return {};
}
const foundNode = children2.find((n) => n.id === nodeId);
if (foundNode) {
return foundNode;
}
if (parentId) {
const parentNode = children2.find((n) => n.id === parentId);
if (parentNode == null ? void 0 : parentNode.children) {
return findNestedNode(nodeId, parentNode.children, parentId);
}
}
const nodesWithChildren = children2.filter((n) => {
var _a;
return (_a = n.children) == null ? void 0 : _a.length;
});
for (const n of nodesWithChildren) {
const foundChild = findNestedNode(nodeId, n.children, parentId);
if (foundChild && Object.keys(foundChild).length) {
return foundChild;
}
}
return {};
}
function getDragNodeData(dragNode2, children2 = []) {
if (!dragNode2) {
return {};
}
const { parent } = dragNode2;
if (!parent) {
return (children2 == null ? void 0 : children2.find((n) => n.id === dragNode2.id)) || {};
}
return findNestedNode(dragNode2.id, children2, parent);
}
const useNodeDrag = ({
x,
y,
height,
width,
onDrag,
onDragEnd,
onDragStart,
node,
disabled: disabled2
}) => {
const initial = [width / 2 + x, height + y];
const targetRef = useRef(null);
const { zoom, xy, containerRef } = useCanvas();
const bind = useDrag(
(state) => {
if (state.event.type === "pointerdown") {
targetRef.current = state.event.currentTarget;
}
if (!state.intentional || !targetRef.current) {
return;
}
if (state.first) {
const matrix = getCoords({
containerRef,
zoom,
layoutXY: xy
});
const memo = [matrix];
onDragStart({ ...state, memo }, initial, node);
return memo;
}
onDrag(state, initial, node);
if (state.last) {
targetRef.current = null;
onDragEnd(state, initial, node);
}
},
{
enabled: !disabled2,
triggerAllEvents: true,
threshold: 5
}
);
return bind;
};
const port = "_port_1r6fw_1";
const clicker$1 = "_clicker_1r6fw_9";
const disabled$2 = "_disabled_1r6fw_12";
const css$8 = {
port,
clicker: clicker$1,
disabled: disabled$2
};
const Port = forwardRef(({ id, x, y, rx, ry, disabled: disabled2, style, children: children2, properties, offsetX, offsetY, className, active: active2, onDrag = () => void 0, onDragStart = () => void 0, onDragEnd = () => void 0, onEnter = () => void 0, onLeave = () => void 0, onClick = () => void 0 }, ref) => {
const { readonly } = useCanvas();
const [isDragging, setIsDragging] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const newX = x - properties.width / 2;
const newY = y - properties.height / 2;
const onDragStartInternal = (event, initial) => {
onDragStart(event, initial, properties);
setIsDragging(true);
};
const onDragEndInternal = (event, initial) => {
onDragEnd(event, initial, properties);
setIsDragging(false);
};
const bind = useNodeDrag({
x: newX + offsetX,
y: newY + offsetY,
height: properties.height,
width: properties.width,
disabled: disabled2 || readonly || (properties == null ? void 0 : properties.disabled),
node: properties,
onDrag,
onDragStart: onDragStartInternal,
onDragEnd: onDragEndInternal
});
if (properties.hidden) {
return null;
}
const isDisabled = properties.disabled || disabled2;
const portChildProps = {
port: properties,
isDragging,
isHovered,
isDisabled,
x,
y,
rx,
ry,
offsetX,
offsetY
};
return /* @__PURE__ */ jsxs("g", { id, children: [
/* @__PURE__ */ jsx(
"rect",
{
...bind(),
ref,
height: properties.height + 14,
width: properties.width + 14,
x: newX - 7,
y: newY - 7,
className: classNames(css$8.clicker, { [css$8.disabled]: isDisabled }),
onMouseEnter: (event) => {
event.stopPropagation();
if (!isDisabled) {
setIsHovered(true);
onEnter(event, properties);
}
},
onMouseLeave: (event) => {
event.stopPropagation();
if (!isDisabled) {
setIsHovered(false);
onLeave(event, properties);
}
},
onClick: (event) => {
event.stopPropagation();
if (!isDisabled) {
onClick(event, properties);
}
}
}
),
/* @__PURE__ */ jsx(
motion.rect,
{
style,
className: classNames(css$8.port, className, properties == null ? void 0 : properties.className),
height: properties.height,
width: properties.width,
rx,
ry,
initial: {
scale: 0,
opacity: 0,
x: newX,
y: newY
},
animate: {
x: newX,
y: newY,
scale: (isDragging || active2 || isHovered) && !isDisabled ? 1.5 : 1,
opacity: 1
}
},
`${x}-${y}`
),
children2 && /* @__PURE__ */ jsx(Fragment, { children: typeof children2 === "function" ? children2(portChildProps) : children2 })
] });
});
const text = "_text_fhkx6_1";
const css$7 = {
text
};
const Label = ({ text: text2, x, y, style, className, originalText }) => {
const isString = typeof originalText === "string";
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
isString && /* @__PURE__ */ jsx("title", { children: originalText }),
/* @__PURE__ */ jsx("g", { transform: `translate(${x}, ${y})`, children: /* @__PURE__ */ jsx("text", { className: classNames(css$7.text, className), style, children: text2 }) })
] });
};
const deleteX = "_deleteX_nxq8k_1";
const container$2 = "_container_nxq8k_6";
const drop$1 = "_drop_nxq8k_10";
const rect$2 = "_rect_nxq8k_15";
const css$6 = {
deleteX,
container: container$2,
drop: drop$1,
rect: rect$2
};
const Remove = ({ size = 15, className, hidden, x, y, onClick = () => void 0, onEnter = () => void 0, onLeave = () => void 0 }) => {
if (hidden) {
return null;
}
const half = size / 2;
const translateX = x - half;
const translateY = y - half;
return /* @__PURE__ */ jsxs(motion.g, { className: classNames(className, css$6.container), initial: { scale: 0, opacity: 0, translateX, translateY }, animate: { scale: 1, opacity: 1, translateX, translateY }, whileHover: { scale: 1.2 }, whileTap: { scale: 0.8 }, children: [
/* @__PURE__ */ jsx(
"rect",
{
height: size * 1.5,
width: size * 1.5,
className: css$6.drop,
onMouseEnter: onEnter,
onMouseLeave: onLeave,
onClick: (event) => {
event.preventDefault();
event.stopPropagation();
onClick(event);
}
}
),
/* @__PURE__ */ jsx("rect", { height: size, width: size, className: css$6.rect }),
/* @__PURE__ */ jsx("line", { x1: "2", y1: size - 2, x2: size - 2, y2: "2", className: css$6.deleteX, strokeWidth: "1" }),
/* @__PURE__ */ jsx("line", { x1: "2", y1: "2", x2: size - 2, y2: size - 2, className: css$6.deleteX, strokeWidth: "1" })
] });
};
function getBezierCenter({
sourceX,
sourceY,
targetX,
targetY
}) {
const xOffset = Math.abs(targetX - sourceX) / 2;
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
const yOffset = Math.abs(targetY - sourceY) / 2;
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
return [centerX, centerY, xOffset, yOffset];
}
function getBezierPath({
sourceX,
sourceY,
sourcePosition = "bottom",
targetX,
targetY,
targetPosition = "top"
}) {
const leftAndRight = ["left", "right"];
const [centerX, centerY] = getBezierCenter({
sourceX,
sourceY,
targetX,
targetY
});
let path2 = `M${sourceX},${sourceY} C${sourceX},${centerY} ${targetX},${centerY} ${targetX},${targetY}`;
if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) {
path2 = `M${sourceX},${sourceY} C${centerX},${sourceY} ${centerX},${targetY} ${targetX},${targetY}`;
} else if (leftAndRight.includes(targetPosition)) {
path2 = `M${sourceX},${sourceY} C${sourceX},${targetY} ${sourceX},${targetY} ${targetX},${targetY}`;
} else if (leftAndRight.includes(sourcePosition)) {
path2 = `M${sourceX},${sourceY} C${targetX},${sourceY} ${targetX},${sourceY} ${targetX},${targetY}`;
}
return path2;
}
function getCenter(pathElm) {
const pLength = pathElm.getTotalLength();
const pieceSize = pLength / 2;
const { x, y } = pathElm.getPointAtLength(pieceSize);
const angle = Math.atan2(x, y) * 180 / Math.PI;
return { x, y, angle };
}
function getAngle(source, target) {
const dx = source.x - target.x;
const dy = source.y - target.y;
let theta = Math.atan2(-dy, -dx);
theta *= 180 / Math.PI;
if (theta < 0) {
theta += 360;
}
return theta;
}
function getPathCenter(pathElm, firstPoint, lastPoint) {
if (!pathElm) {
return null;
}
const angle = getAngle(firstPoint, lastPoint);
const point = getCenter(pathElm);
return {
...point,
angle
};
}
const plus = "_plus_1qsm8_1";
const container$1 = "_container_1qsm8_6";
const drop = "_drop_1qsm8_10";
const rect$1 = "_rect_1qsm8_15";
const css$5 = {
plus,
container: container$1,
drop,
rect: rect$1
};
const Add = ({ x, y, className, size = 15, hidden = true, onEnter = () => void 0, onLeave = () => void 0, onClick = () => void 0 }) => {
if (hidden) {
return null;
}
const half = size / 2;
const translateX = x - half;
const translateY = y - half;
return /* @__PURE__ */ jsxs(motion.g, { className: classNames(className, css$5.container), initial: { scale: 0, opacity: 0, translateX, translateY }, animate: { scale: 1, opacity: 1, translateX, translateY }, whileHover: { scale: 1.2 }, whileTap: { scale: 0.8 }, children: [
/* @__PURE__ */ jsx(
"rect",
{
height: size * 2,
width: size * 2,
className: css$5.drop,
onClick: (event) => {
event.preventDefault();
event.stopPropagation();
onClick(event);
},
onMouseEnter: onEnter,
onMouseLeave: onLeave
}
),
/* @__PURE__ */ jsx("rect", { height: size, width: size, className: css$5.rect }),
/* @__PURE__ */ jsx("line", { x1: "2", x2: size - 2, y1: half, y2: half, className: css$5.plus, strokeWidth: "1" }),
/* @__PURE__ */ jsx("line", { x1: half, x2: half, y1: "2", y2: size - 2, className: css$5.plus, strokeWidth: "1" })
] });
};
const edge = "_edge_v5z62_1";
const disabled$1 = "_disabled_v5z62_2";
const selectionDisabled$1 = "_selectionDisabled_v5z62_6";
const path = "_path_v5z62_8";
const active$1 = "_active_v5z62_11";
const deleteHovered$1 = "_deleteHovered_v5z62_15";
const clicker = "_clicker_v5z62_22";
const css$4 = {
edge,
disabled: disabled$1,
selectionDisabled: selectionDisabled$1,
path,
active: active$1,
deleteHovered: deleteHovered$1,
clicker
};
const Edge = ({ sections, interpolation = "curved", properties, labels, className, containerClassName, disabled: disabled2, removable = true, selectable = true, upsertable = true, style, children: children2, add = /* @__PURE__ */ jsx(Add, {}), remove = /* @__PURE__ */ jsx(Remove, {}), label = /* @__PURE__ */ jsx(Label, {}), onClick = () => void 0, onKeyDown = () => void 0, onEnter = () => void 0, onLeave = () => void 0, onRemove = () => void 0, onAdd = () => void 0 }) => {
const pathRef = useRef(null);
const [deleteHovered2, setDeleteHovered] = useState(false);
const [center, setCenter] = useState(null);
const { selections, readonly } = useCanvas();
const isActive = (selections == null ? void 0 : selections.length) ? selections.includes(properties == null ? void 0 : properties.id) : false;
const isDisabled = disabled2 || (properties == null ? void 0 : properties.disabled);
const canSelect = selectable && !(properties == null ? void 0 : properties.selectionDisabled);
const d = useMemo(() => {
if (!(sections == null ? void 0 : sections.length)) {
return null;
}
if (sections[0].bendPoints) {
const points = sections ? [sections[0].startPoint, ...sections[0].bendPoints || [], sections[0].endPoint] : [];
let pathFn = line().x((d2) => d2.x).y((d2) => d2.y);
if (interpolation !== "linear") {
pathFn = interpolation === "curved" ? pathFn.curve(curveBundle.beta(1)) : interpolation;
}
return pathFn(points);
} else {
return getBezierPath({
sourceX: sections[0].startPoint.x,
sourceY: sections[0].startPoint.y,
targetX: sections[0].endPoint.x,
targetY: sections[0].endPoint.y
});
}
}, [interpolation, sections]);
useEffect(() => {
if ((sections == null ? void 0 : sections.length) > 0) {
setCenter(getPathCenter(pathRef.current, sections[0].startPoint, sections[0].endPoint));
}
}, [sections]);
const edgeChildProps = {
edge: properties,
center,
pathRef
};
return /* @__PURE__ */ jsxs(
"g",
{
className: classNames(css$4.edge, containerClassName, {
[css$4.disabled]: isDisabled,
[css$4.selectionDisabled]: !canSelect
}),
children: [
/* @__PURE__ */ jsx(
"path",
{
ref: pathRef,
style,
className: classNames(css$4.path, properties == null ? void 0 : properties.className, className, {
[css$4.active]: isActive,
[css$4.deleteHovered]: deleteHovered2
}),
d,
markerEnd: "url(#end-arrow)"
}
),
/* @__PURE__ */ jsx(
"path",
{
className: css$4.clicker,
d,
tabIndex: -1,
onClick: (event) => {
event.preventDefault();
event.stopPropagation();
if (!isDisabled && canSelect) {
onClick(event, properties);
}
},
onKeyDown: (event) => {
event.preventDefault();
event.stopPropagation();
if (!isDisabled) {
onKeyDown(event, properties);
}
},
onMouseEnter: (event) => {
event.stopPropagation();
if (!isDisabled) {
onEnter(event, properties);
}
},
onMouseLeave: (event) => {
event.stopPropagation();
if (!isDisabled) {
onLeave(event, properties);
}
}
}
),
children2 && /* @__PURE__ */ jsx(Fragment, { children: typeof children2 === "function" ? children2(edgeChildProps) : children2 }),
(labels == null ? void 0 : labels.length) > 0 && labels.map((l, index) => /* @__PURE__ */ jsx(CloneElement, { element: label, edgeChildProps, ...l }, index)),
!isDisabled && center && !readonly && remove && removable && /* @__PURE__ */ jsx(
CloneElement,
{
element: remove,
...center,
hidden: remove.props.hidden !== void 0 ? remove.props.hidden : !isActive,
onClick: (event) => {
event.preventDefault();
event.stopPropagation();
onRemove(event, properties);
setDeleteHovered(false);
},
onEnter: () => setDeleteHovered(true),
onLeave: () => setDeleteHovered(false)
}
),
!isDisabled && center && !readonly && add && upsertable && /* @__PURE__ */ jsx(
CloneElement,
{
element: add,
...center,
onClick: (event) => {
event.preventDefault();
event.stopPropagation();
onAdd(event, properties);
}
}
)
]
}
);
};
const rect = "_rect_1b6xi_1";
const selectionDisabled = "_selectionDisabled_1b6xi_8";
const disabled = "_disabled_1b6xi_8";
const dragging = "_dragging_1b6xi_15";
const active = "_active_1b6xi_19";
const unlinkable = "_unlinkable_1b6xi_23";
const deleteHovered = "_deleteHovered_1b6xi_27";
const children = "_children_1b6xi_37";
const css$3 = {
rect,
selectionDisabled,
disabled,
dragging,
active,
unlinkable,
deleteHovered,
children
};
const Node = ({ id, x, y, ports, labels, height, width, properties, animated, className, rx = 2, ry = 2, offsetX = 0, offsetY = 0, icon: icon2, disabled: disabled2, style, children: children2, nodes, edges, draggable: draggable2 = true, linkable = true, selectable = true, removable = true, dragType = "multiportOnly", dragCursor = "crosshair", childEdge = /* @__PURE__ */ jsx(Edge, {}), childNode = /* @__PURE__ */ jsx(Node, {}), remove = /* @__PURE__ */ jsx(Remove, {}), port: port2 = /* @__PURE__ */ jsx(Port, {}), label = /* @__PURE__ */ jsx(Label, {}), tooltip: Tooltip = React.Fragment, onRemove, onDrag, onDragStart, onDragEnd, onClick, onKeyDown, onEnter, onLeave }) => {
const nodeRef = useRef(null);
const controls = useAnimation();
const { canLinkNode, enteredNode, selections, readonly, ...canvas } = useCanvas();
const [deleteHovered2, setDeleteHovered] = useState(false);
const [dragging2, setDragging] = useState(false);
const [isLinkable, setIsLinkable] = useState(true);
const isActive = (selections == null ? void 0 : selections.length) ? selections.includes(properties.id) : null;
const isNodeDrag = id.includes("node-drag");
const newX = x + offsetX;
const newY = y + offsetY;
const isMultiPort = dragType === "multiportOnly" && (ports == null ? void 0 : ports.filter((p) => {
var _a;
return !((_a = p.properties) == null ? void 0 : _a.hidden);
}).length) > 1;
const isDisabled = disabled2 || (properties == null ? void 0 : properties.disabled);
const canDrag = ["port", "multiportOnly"].includes(dragType) ? linkable : draggable2;
const canSelect = selectable && !(properties == null ? void 0 : properties.selectionDisabled);
const getDragType = useCallback(
(hasPort) => {
let activeDragType = null;
if (!hasPort) {
if (dragType === "all" || dragType === "node") {
activeDragType = "node";
} else if (!isMultiPort) {
activeDragType = "port";
}
} else {
if (dragType === "all" || dragType === "port" || isMultiPort) {
activeDragType = "port";
}
}
return activeDragType;
},
[dragType, isMultiPort]
);
const setDragCursor = useCallback((dragType2) => {
if (dragType2) {
document.body.classList.add("dragging");
document.body.style.cursor = dragType2 === "node" ? "grab" : "crosshair";
} else {
document.body.classList.remove("dragging");
document.body.style.cursor = "auto";
}
}, []);
const bind = useNodeDrag({
x: newX,
y: newY,
height,
width,