@yworks/yfiles-layout-reactflow
Version:
yFiles Layouts for React Flow - A layout library for React Flow providing powerful yFiles layout algorithms and supporting components
894 lines (885 loc) • 29.5 kB
JavaScript
import {
getLayoutAlgorithm,
getLayoutData,
getRootNode,
initializeWebWorker,
registerWebWorker,
setWebWorkerLicense
} from "./chunk-MI7N5RQH.js";
// src/components/LabelText.tsx
import { useEffect, useRef, useState } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
function LabelText({
x,
y,
width,
height,
angle,
transform,
label,
labelStyle,
dataId,
isNodeLabel,
className,
children
}) {
const [textBox, setTextBox] = useState({ x: 0, y: 0, width: 0, height: 0 });
const hasLayout = typeof width !== "undefined" && typeof height !== "undefined";
const labelRef = useRef(null);
const containerRef = useRef(null);
useEffect(() => {
if (!labelRef || !labelRef.current) return;
if (!width || !height) {
const bbox = labelRef.current.getBoundingClientRect();
setTextBox({
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height
});
} else {
setTextBox({
x,
y,
width: width ?? 0,
height: height ?? 0
});
}
}, [x, y, width, height]);
const defaultStyle = { position: "absolute", whiteSpace: "nowrap", boxSizing: "content-box" };
let style = {
...defaultStyle,
...isNodeLabel && {
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)"
}
};
if (hasLayout) {
let hOffset = 0;
let vOffset = 0;
if (isNodeLabel) {
const { offset, wDiff, hDiff } = calculateOffsets(textBox.width, textBox.height, containerRef.current);
hOffset += wDiff * 0.5 + (textBox.x >= 0 ? offset.left : offset.right);
vOffset += hDiff * 0.5 + (textBox.y >= 0 ? offset.top : offset.bottom);
}
style = {
...defaultStyle,
width: textBox.width,
height: textBox.height,
transform: (isNodeLabel ? `translate(${textBox.x - hOffset}px,${textBox.y - vOffset}px) ` : "") + (transform ? ` ${transform}` : ""),
...!isNodeLabel && { transformOrigin: `0px 0px` }
};
}
return /* @__PURE__ */ jsxs("div", { "data-id": dataId, style, className, ref: containerRef, children: [
/* @__PURE__ */ jsx(
"div",
{
style: {
...labelStyle,
...shouldFlipText(angle, isNodeLabel ?? false) && { transform: `rotate(${Math.PI}rad)` }
},
ref: labelRef,
children: isLabelType(label) ? label.label : label
}
),
children
] });
}
function calculateOffsets(width, height, labelElement) {
if (labelElement) {
const parentElement = labelElement.parentElement;
if (parentElement) {
const parentWidth = parentElement.clientWidth;
const parentHeight = parentElement.clientHeight;
const parentPadding = calculatePadding(parentElement);
const labelPadding = calculatePadding(labelElement);
return {
offset: {
top: parentPadding.top + labelPadding.top,
bottom: parentPadding.bottom + labelPadding.bottom,
right: parentPadding.right + labelPadding.right,
left: parentPadding.left + labelPadding.left
},
wDiff: width - parentWidth,
hDiff: height - parentHeight
};
}
}
return { offset: { top: 0, right: 0, bottom: 0, left: 0 }, wDiff: -1, hDiff: -1 };
}
function calculatePadding(element) {
if (!element) {
return { top: 0, right: 0, bottom: 0, left: 0 };
}
const elementStyle = window.getComputedStyle(element);
return {
top: parseFloat(elementStyle.paddingTop) ?? 0,
right: parseFloat(elementStyle.paddingRight) ?? 0,
bottom: parseFloat(elementStyle.paddingBottom) ?? 0,
left: parseFloat(elementStyle.paddingLeft) ?? 0
};
}
function shouldFlipText(angle, isNodeLabel) {
return angle && (isNodeLabel ? angle >= -Math.PI && angle <= -Math.PI / 2 || angle >= Math.PI / 2 && angle <= Math.PI : angle < -Math.PI && angle > -Math.PI / 2 || angle < Math.PI / 2 && angle > Math.PI);
}
// src/components/Labels.tsx
import { jsx as jsx2 } from "react/jsx-runtime";
import { createElement } from "react";
function NodeLabel({ data, id }) {
if (!data?.label) {
return;
}
const dataId = `node-label-${id}-0`;
const labelBox = data?.yData?.labelBoxes?.find(
(layout) => layout.id === dataId
);
const nodeLabelProps = getTextProps(data?.label, labelBox, data?.labelStyle, data?.className);
return /* @__PURE__ */ jsx2(LabelText, { ...nodeLabelProps, dataId, isNodeLabel: true });
}
function EdgeLabels({ labels, ownerId, labelBoxes, labelStyle }) {
return /* @__PURE__ */ jsx2("div", { style: { position: "relative" }, children: labels.map((label, index) => {
if (!label) {
return;
}
const dataId = `edge-label-${ownerId}-${index}`;
const labelBox = labelBoxes.find(
(labelBox2) => labelBox2.id === dataId
);
const edgeTextProps = getTextProps(label, labelBox, labelStyle);
return /* @__PURE__ */ createElement(
LabelText,
{
...edgeTextProps,
dataId,
key: `${dataId}-text-${index}`,
isNodeLabel: false
}
);
}) });
}
function getTextProps(label, labelBox, labelStyle, className) {
if (isLabelType(label)) {
return {
x: labelBox?.x ?? 0,
y: labelBox?.y ?? 0,
width: labelBox?.width,
height: labelBox?.height,
angle: labelBox?.angle ?? 0,
transform: labelBox?.transform,
label: label.label,
labelStyle: label.labelStyle ?? labelStyle,
className: label?.className ?? className
};
}
return {
x: labelBox?.x ?? 0,
y: labelBox?.y ?? 0,
width: labelBox?.width,
height: labelBox?.height,
angle: labelBox?.angle ?? 0,
transform: labelBox?.transform,
label,
labelStyle,
className
};
}
function isLabelType(label) {
return label.label !== void 0;
}
// src/components/MultiHandleNode.tsx
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
import { useEffect as useEffect2, useRef as useRef2 } from "react";
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
function MultiHandleNode(props) {
return /* @__PURE__ */ jsxs2(Fragment, { children: [
/* @__PURE__ */ jsx3(NodeLabel, { ...props }),
/* @__PURE__ */ jsx3(MultiHandles, { ...props })
] });
}
function withMultiHandles(Component) {
return (props) => {
return /* @__PURE__ */ jsxs2(Fragment, { children: [
/* @__PURE__ */ jsx3(Component, { ...props }),
/* @__PURE__ */ jsx3(MultiHandles, { ...props })
] });
};
}
function MultiHandles(props) {
const { data, id } = props;
const updateNodeInternals = useUpdateNodeInternals();
const sourceHandlesRef = useRef2(null);
const targetHandlesRef = useRef2(null);
useEffect2(() => {
updateNodeInternals(id);
}, [data, id, updateNodeInternals]);
useEffect2(() => {
const { leftBorder, topBorder } = getParentBorders(sourceHandlesRef);
setHandlesPosition(sourceHandlesRef, leftBorder, topBorder);
setHandlesPosition(targetHandlesRef, leftBorder, topBorder);
}, [data, id]);
return /* @__PURE__ */ jsxs2(Fragment, { children: [
/* @__PURE__ */ jsxs2("div", { ref: sourceHandlesRef, className: "handles", style: { position: "absolute" }, children: [
/* @__PURE__ */ jsx3(
Handle,
{
type: "source",
position: Position.Top,
style: {
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
opacity: 0
}
}
),
" ",
data?.yData?.sourceHandles?.map(
(handle) => /* @__PURE__ */ jsx3(
Handle,
{
id: handle.id,
type: "source",
position: handle.position,
style: {
left: handle.location.x,
top: handle.location.y,
transform: "translate(-50%, -50%)"
}
},
handle.id
)
)
] }),
/* @__PURE__ */ jsxs2("div", { ref: targetHandlesRef, className: "handles", style: { position: "absolute" }, children: [
/* @__PURE__ */ jsx3(
Handle,
{
type: "target",
position: Position.Top,
style: {
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
opacity: 0
}
}
),
data?.yData?.targetHandles?.map(
(handle) => /* @__PURE__ */ jsx3(
Handle,
{
id: handle.id,
type: "target",
position: handle.position,
style: {
left: handle.location.x,
top: handle.location.y,
transform: "translate(-50%, -50%)"
}
},
handle.id
)
)
] })
] });
}
function getParentBorders(handlesRef) {
let leftBorderWidth = 1;
let topBorderWidth = 1;
if (handlesRef.current) {
const parent = handlesRef.current.parentElement;
if (parent) {
const computedStyle = getComputedStyle(parent);
leftBorderWidth = parseInt(computedStyle.getPropertyValue("border-left-width"));
topBorderWidth = parseInt(computedStyle.getPropertyValue("border-top-width"));
}
}
return { leftBorder: leftBorderWidth, topBorder: topBorderWidth };
}
function setHandlesPosition(handlesRef, leftBorder, topBorder) {
if (handlesRef && handlesRef.current) {
const handlesDiv = handlesRef.current;
handlesDiv.style.left = `-${leftBorder}px`;
handlesDiv.style.top = `-${topBorder}px`;
}
}
// src/components/PolylineEdge.tsx
import { BaseEdge, EdgeLabelRenderer } from "reactflow";
import { GeneralPath } from "@yfiles/yfiles";
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
function PolylineEdge(props) {
const [edgePath] = getPolylinePath({
sourceX: props.sourceX,
sourceY: props.sourceY,
targetX: props.targetX,
targetY: props.targetY,
bends: props.data?.yData?.bends ?? []
});
let labels = [];
if (props.label) {
labels.push(props.label);
}
if (props.data?.labels?.length > 0) {
labels = labels.concat(props.data?.labels);
}
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
/* @__PURE__ */ jsx4(BaseEdge, { path: edgePath, ...props }),
/* @__PURE__ */ jsx4(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx4(
EdgeLabels,
{
labels,
ownerId: props.id,
labelBoxes: props.data?.yData?.labelBoxes ?? [],
labelStyle: props.labelStyle
},
`${props.id}-edgeLabels`
) })
] });
}
function getPolylinePath(params) {
const { sourceX, sourceY, targetX, targetY, bends } = params;
const generalPath = new GeneralPath();
generalPath.moveTo(sourceX, sourceY);
bends.forEach((bend) => {
generalPath.lineTo(bend.x, bend.y);
});
generalPath.lineTo(targetX, targetY);
let centerX;
let centerY;
if (bends.length === 0) {
centerX = (sourceX + targetX) * 0.5;
centerY = (sourceY + targetY) * 0.5;
} else if (bends.length === 1) {
centerX = (sourceX + bends[0].x) * 0.5;
centerY = (sourceY + bends[0].y) * 0.5;
} else {
const bend0 = bends[Math.floor((bends.length - 1) / 2)];
const bend1 = bends[Math.floor((bends.length - 1) / 2) + 1];
centerX = (bend0.x + bend1.x) * 0.5;
centerY = (bend0.x + bend1.y) * 0.5;
}
return [
generalPath.createSvgPathData(),
centerX,
centerY,
Math.abs(centerX - sourceX),
Math.abs(centerY - sourceY)
];
}
// src/layout/layout-hooks.ts
import { useCallback, useEffect as useEffect3, useMemo, useState as useState2 } from "react";
import {
FreeEdgeLabelModel,
FreeNodeLabelModel,
Graph as Graph2,
GraphBuilder,
GroupingSupport,
Matrix,
OrientedRectangle,
Point,
Rect,
Size
} from "@yfiles/yfiles";
import {
Position as Position2,
useNodesInitialized,
useReactFlow
} from "reactflow";
// src/license/registerLicense.ts
import { Graph, License } from "@yfiles/yfiles";
function registerLicense(licenseKey) {
License.value = licenseKey;
setWebWorkerLicense(licenseKey);
}
function checkLicense() {
const g = new Graph();
g.createNode();
if (g.nodes.size !== 1) {
console.error(
"yFiles Layout Algorithms for React Flow requires a valid yFiles for HTML license. You can evaluate yFiles for 60 days free of charge on my.yworks.com."
);
return false;
}
return true;
}
// src/layout/LayoutSupport.ts
import { GraphComponent, LayoutExecutor, LayoutExecutorAsync } from "@yfiles/yfiles";
var LayoutSupport = class {
workerPromise = null;
executor = null;
constructor(layoutWorker) {
if (layoutWorker) {
this.workerPromise = registerWebWorker(layoutWorker);
}
}
async applyLayout(graph, layoutConfiguration, layoutDataProvider, reactFlowRef) {
const executor = this.workerPromise !== null ? await this.createLayoutExecutorAsync(
graph,
layoutConfiguration,
layoutDataProvider,
reactFlowRef
) : await this.createLayoutExecutor(
graph,
layoutConfiguration,
layoutDataProvider,
reactFlowRef
);
try {
await executor.start();
} catch (e) {
if (e.name === "AlgorithmAbortedError") {
console.error("Layout calculation was aborted because maximum duration time was exceeded.");
} else {
console.error("Something went wrong during the layout calculation");
console.error(e);
}
}
}
async createLayoutExecutor(graph, layoutConfiguration, layoutDataProvider, reactFlowRef) {
const layoutAlgorithm = await getLayoutAlgorithm(layoutConfiguration);
return Promise.resolve(
new LayoutExecutor({
graphComponent: new GraphComponent({ graph }),
layout: layoutAlgorithm,
layoutData: getLayoutData(layoutConfiguration.name, layoutDataProvider, reactFlowRef)
})
);
}
async createLayoutExecutorAsync(graph, layoutConfiguration, layoutDataProvider, reactFlowRef) {
if (this.executor) {
await this.executor.cancel();
}
const worker = await this.workerPromise;
const webWorkerMessageHandler = (data) => {
const thisRequest = data.token;
return new Promise((resolve, reject) => {
worker.onmessage = (e) => {
if (e.data && thisRequest === e.data.token) {
if (e.data.name === "AlgorithmAbortedError") {
reject(e.data);
} else {
resolve(e.data);
}
}
};
worker.postMessage(data);
});
};
this.executor = new LayoutExecutorAsync({
messageHandler: webWorkerMessageHandler,
graph,
layoutDescriptor: layoutConfiguration,
layoutData: getLayoutData(layoutConfiguration.name, layoutDataProvider, reactFlowRef)
});
return this.executor;
}
};
// src/layout/layout-hooks.ts
function useLayout(context) {
const { fitView, getZoom, getNodes, setNodes, getEdges, setEdges } = useReactFlow();
const [running, setRunning] = useState2(false);
const layoutSupport = useMemo(() => {
return new LayoutSupport(context?.layoutWorker);
}, [context?.layoutWorker]);
return {
runLayout: useCallback(
(configuration) => {
if (!checkLicense()) {
return;
}
setRunning(true);
const graph = buildGraph(getNodes(), getEdges(), getZoom(), context?.reactFlowRef);
const { name, layoutOptions, layoutData } = getConfiguration(
configuration
);
layoutSupport.applyLayout(
graph,
{ name, properties: layoutOptions },
layoutData,
context?.reactFlowRef
).then(() => {
const { arrangedNodes, arrangedEdges } = transferLayout(graph, context?.reactFlowRef);
setNodes(arrangedNodes);
setEdges(arrangedEdges);
setTimeout(() => fitView());
}).catch((e) => {
context?.onError?.(e);
}).finally(() => {
setRunning(false);
});
},
[getNodes, getEdges, getZoom, context, layoutSupport, setNodes, setEdges, fitView]
),
running
};
}
function useLayoutSupport(reactFlowRef) {
return {
buildGraph: useCallback(
(nodes, edges, zoom) => buildGraph(nodes, edges, zoom, reactFlowRef),
[reactFlowRef]
),
transferLayout: useCallback((graph) => transferLayout(graph, reactFlowRef), [reactFlowRef])
};
}
function useNodesMeasuredEffect(callback) {
const { getNodes } = useReactFlow();
const [shouldLayout, setShouldLayout] = useState2(true);
const nodesInitialized = useNodesInitialized({
includeHiddenNodes: false
});
return useEffect3(() => {
const nodes = getNodes();
if (nodesInitialized && shouldLayout && nodes.every((node) => typeof node.width !== "undefined" && typeof node.height !== "undefined")) {
setShouldLayout(false);
callback();
}
}, [getNodes, shouldLayout, callback, nodesInitialized]);
}
function buildGraph(nodes, edges, zoom, reactFlowRef) {
if (!checkLicense()) {
return new Graph2();
}
const builder = new GraphBuilder();
const nodesSource = builder.createNodesSource({
data: nodes,
id: (node) => node.id,
parentId: (node) => node.parentNode,
layout: (node) => Rect.from([node.position.x, node.position.y, node.width ?? 1, node.height ?? 1])
});
nodesSource.nodeCreator.createLabelsSource({
data: (node) => [node.data.label]
});
nodesSource.nodeCreator.defaults.labels.layoutParameter = FreeNodeLabelModel.CENTER;
const edgesSource = builder.createEdgesSource({
data: edges,
sourceId: (edge) => edge.source,
targetId: (edge) => edge.target
});
const edgeCreator = edgesSource.edgeCreator;
edgeCreator.bendsProvider = (edge) => edge.data?.yData?.bends?.map((bend) => Point.from(bend));
const labelsSource = edgeCreator.createLabelsSource({
data: (edge) => {
let labels = [];
if (edge.label) {
labels.push(edge.label);
}
if (edge.data?.labels) {
labels = labels.concat(edge.data.labels);
}
return labels;
}
});
labelsSource.labelCreator.tagProvider = (dataItem) => dataItem;
edgeCreator.defaults.labels.layoutParameter = FreeEdgeLabelModel.INSTANCE.createParameter();
const graph = builder.buildGraph();
const rootElement = getRootNode(reactFlowRef);
graph.nodes.forEach((node) => {
node.labels.forEach((label, index) => {
const preferredSize = measureLabel(`node-label-${node.tag.id}-${index}`, zoom, rootElement);
if (preferredSize.width > 0 && preferredSize.height > 0) {
graph.setLabelPreferredSize(label, preferredSize);
}
if (node.tag.data?.yData) {
const yData = node.tag.data?.yData;
const labelBoxes = yData?.labelBoxes;
if (labelBoxes && labelBoxes.length > 0) {
const labelBox = yData.labelBoxes[0];
if (labelBox) {
const labelRectangle = new OrientedRectangle(
labelBox.x + node.layout.center.x - labelBox.width * 0.5,
labelBox.y + node.layout.center.y + labelBox.height * 0.5,
labelBox.width,
labelBox.height
);
const center = labelRectangle.center;
labelRectangle.angle = labelBox.angle;
labelRectangle.center = center;
graph.setLabelLayoutParameter(
label,
FreeNodeLabelModel.INSTANCE.findBestParameter(label, labelRectangle)
);
}
}
}
});
});
graph.edges.forEach((edge) => {
edge.labels.forEach((label, index) => {
const preferredSize = measureLabel(`edge-label-${edge.tag.id}-${index}`, zoom, rootElement);
if (preferredSize.width > 0 && preferredSize.height > 0) {
graph.setLabelPreferredSize(label, preferredSize);
}
label.tag = {
ownerId: edge.tag.id,
labelIndex: index,
data: label.tag
};
if (edge.tag.data?.yData) {
const yData = edge.tag.data?.yData;
const labelBoxes = yData?.labelBoxes;
if (labelBoxes && labelBoxes.length > 0) {
const labelBox = yData.labelBoxes[index];
if (labelBox) {
const labelRectangle = new OrientedRectangle(
labelBox.x,
labelBox.y,
preferredSize.width,
preferredSize.height
);
labelRectangle.angle = labelBox.angle;
graph.setLabelLayoutParameter(
label,
FreeEdgeLabelModel.INSTANCE.findBestParameter(label, labelRectangle)
);
}
}
}
});
if (edge.tag.data?.yData?.sourcePoint && edge.tag.data?.yData?.targetPoint) {
const sourceNode = edge.sourceNode;
const targetNode = edge.targetNode;
graph.setEdgePorts(
edge,
graph.addPortAt(sourceNode, {
x: sourceNode.layout.x + edge.tag.data?.yData?.sourcePoint.x,
y: sourceNode.layout.y + edge.tag.data?.yData?.sourcePoint.y
}),
graph.addPortAt(targetNode, {
x: targetNode.layout.x + edge.tag.data?.yData?.targetPoint.x,
y: targetNode.layout.y + edge.tag.data?.yData?.targetPoint.y
})
);
}
});
return graph;
}
function transferLayout(graph, reactFlowRef) {
if (!checkLicense()) {
return { arrangedNodes: [], arrangedEdges: [] };
}
const nodeOffsets = /* @__PURE__ */ new Map();
const hierarchicNodes = new GroupingSupport(graph).getDescendantsBottomUp(null);
hierarchicNodes.forEach((node) => {
const parent = graph.getParent(node);
if (parent) {
nodeOffsets.set(node, {
dx: -parent.layout.x,
dy: -parent.layout.y
});
}
});
const rootElement = getRootNode(reactFlowRef);
const arrangedNodes = graph.nodes.map((node) => {
const offset = nodeOffsets.get(node) ?? { dx: 0, dy: 0 };
return {
...node.tag,
position: { x: node.layout.x + offset.dx, y: node.layout.y + offset.dy },
style: { ...node.tag.style, width: node.layout.width, height: node.layout.height },
data: {
...node.tag.data,
yData: {
sourceHandles: graph.outEdgesAt(node).map((edge) => ({
id: getSourceHandleId(edge),
location: {
x: edge.sourcePort.location.x - edge.sourceNode.layout.x,
y: edge.sourcePort.location.y - edge.sourceNode.layout.y
},
position: getHandlePosition(edge, true)
})).toArray(),
targetHandles: graph.inEdgesAt(node).map((edge) => ({
id: getTargetHandleId(edge),
location: {
x: edge.targetPort.location.x - edge.targetNode.layout.x,
y: edge.targetPort.location.y - edge.targetNode.layout.y
},
position: getHandlePosition(edge, false)
})).toArray(),
labelBoxes: node.labels.map((label, index) => {
const rect = label.layout;
const labelId = `node-label-${node.tag.id}-${index}`;
const padding = calculatePadding2(labelId, rootElement);
const angle = rect.angle;
return {
id: labelId,
x: rect.center.x - node.layout.center.x,
y: rect.center.y - node.layout.center.y,
width: rect.width - padding.right - padding.left,
height: rect.height - padding.top - padding.bottom,
angle,
transform: `rotate(${angle}rad)`
};
}).toArray()
}
}
};
}).toArray();
const arrangedEdges = graph.edges.map((edge) => {
return {
...edge.tag,
sourceHandle: getSourceHandleId(edge),
targetHandle: getTargetHandleId(edge),
data: {
...edge.tag.data,
yData: {
bends: edge.bends.map((bend) => ({ x: bend.location.x, y: bend.location.y })).toArray(),
sourcePoint: {
x: edge.sourcePort.location.x - edge.sourceNode.layout.x,
y: edge.sourcePort.location.y - edge.sourceNode.layout.y
},
targetPoint: {
x: edge.targetPort.location.x - edge.targetNode.layout.x,
y: edge.targetPort.location.y - edge.targetNode.layout.y
},
labelBoxes: edge.labels.map((label, index) => {
const rect = label.layout;
const labelId = `edge-label-${edge.tag.id}-${index}`;
const padding = calculatePadding2(labelId, rootElement);
return {
id: labelId,
x: rect.anchorX,
y: rect.anchorY,
width: rect.width - padding.left - padding.right,
height: rect.height - padding.top - padding.bottom,
angle: rect.angle,
transform: getTransformMatrix(rect).toCssTransform()
};
}).toArray()
}
}
};
}).toArray();
return { arrangedNodes, arrangedEdges };
}
function getSourceHandleId(edge) {
return `${edge.tag.id}-source`;
}
function getTargetHandleId(edge) {
return `${edge.tag.id}-target`;
}
function getHandlePosition(edge, isSourceHandle) {
const sourcePortLocation = edge.sourcePort.location.toPoint();
const targetPortLocation = edge.targetPort.location.toPoint();
const port = (isSourceHandle ? sourcePortLocation : targetPortLocation).toPoint();
const nodeRect = (isSourceHandle ? edge.sourceNode.layout : edge.targetNode.layout).toRect();
let side = getSide(nodeRect, port);
if (side === "center") {
const oppositePort = isSourceHandle ? targetPortLocation : sourcePortLocation;
const firstEdgePoint = edge.bends.size > 0 ? (isSourceHandle ? edge.bends.at(0).location : edge.bends.at(-1).location).toPoint() : oppositePort;
const intersectionPoint = nodeRect.findLineIntersection(port, firstEdgePoint);
if (intersectionPoint) {
side = getSide(nodeRect, intersectionPoint.toPoint());
}
}
return translateSide(side);
}
function getSide(rect, point) {
const EPSILON = 1e-3;
if (Math.abs(point.x - rect.topLeft.x) < EPSILON) {
return "left";
} else if (Math.abs(point.x - rect.bottomRight.x) < EPSILON) {
return "right";
}
if (Math.abs(point.y - rect.topLeft.y) < EPSILON) {
return "top";
} else if (Math.abs(point.y - rect.bottomLeft.y) < EPSILON) {
return "bottom";
}
if (Math.abs(point.x - rect.center.x) < EPSILON || Math.abs(point.y - rect.center.y) < EPSILON) {
return "center";
}
return "other";
}
function translateSide(side) {
switch (side) {
case "left":
return Position2.Right;
case "right":
return Position2.Left;
case "top":
return Position2.Bottom;
case "bottom":
return Position2.Top;
case "other":
default:
return Position2.Top;
}
}
function measureLabel(labelId, zoom, rootElement) {
const labelElement = rootElement.querySelector(`[data-id="${labelId}"]`);
if (labelElement) {
const oldTransform = labelElement.style.transform;
labelElement.style.transform = "";
const oldBoxSizing = labelElement.style.boxSizing;
labelElement.style.boxSizing = "content-box";
const computedStyle = window.getComputedStyle(labelElement);
const labelPadding = calculatePadding2(labelId, rootElement);
let width = parseFloat(computedStyle.width);
let height = parseFloat(computedStyle.height);
if (width <= 0 || height <= 0) {
const clientRect = labelElement.getBoundingClientRect();
width = width > 0 ? width : clientRect.width / zoom;
height = height > 0 ? height : clientRect.height / zoom;
}
labelElement.style.transform = oldTransform;
labelElement.style.boxSizing = oldBoxSizing;
return new Size(
width + labelPadding.left + labelPadding.right,
height + labelPadding.top + labelPadding.bottom
);
}
return Size.ZERO;
}
function calculatePadding2(labelId, rootElement) {
const element = rootElement.querySelector(`[data-id="${labelId}"]`);
if (element) {
const elementStyle = window.getComputedStyle(element);
return {
top: parseFloat(elementStyle.paddingTop) ?? 0,
right: parseFloat(elementStyle.paddingRight) ?? 0,
bottom: parseFloat(elementStyle.paddingBottom) ?? 0,
left: parseFloat(elementStyle.paddingLeft) ?? 0
};
}
return { top: 0, bottom: 0, left: 0, right: 0 };
}
function getTransformMatrix(layout) {
const projectedBaseline = new Point(-layout.upY, layout.upX);
const width = layout.width;
const height = layout.height;
const anchorX = layout.anchorX;
const anchorY = layout.anchorY;
const upY = layout.upY;
const upX = layout.upX;
if (layout.upY == -1) {
return new Matrix(1, 0, 0, 1, anchorX, anchorY - height);
}
if (projectedBaseline.x < 0) {
return new Matrix(upY, upX, -upX, upY, anchorX - upY * width, anchorY + upX * width);
}
return new Matrix(-upY, -upX, upX, -upY, anchorX + upX * height, anchorY + upY * height);
}
function getConfiguration(configuration) {
if (typeof configuration === "string") {
return { name: configuration };
}
return {
name: configuration.name,
layoutOptions: configuration.layoutOptions,
layoutData: configuration.layoutData ?? {}
};
}
export {
EdgeLabels,
MultiHandleNode,
NodeLabel,
PolylineEdge,
getPolylinePath,
initializeWebWorker,
registerLicense,
useLayout,
useLayoutSupport,
useNodesMeasuredEffect,
withMultiHandles
};