merge next
This commit is contained in:
@@ -138,7 +138,7 @@ export type ColorMode = ColorModeClass | 'system';
|
||||
|
||||
export type ConnectionLookup = Map<string, Map<string, Connection>>;
|
||||
|
||||
export type OnBeforeDelete = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>({
|
||||
export type OnBeforeDeleteBase<NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase> = ({
|
||||
nodes,
|
||||
edges,
|
||||
}: {
|
||||
|
||||
@@ -114,11 +114,6 @@ function getHandle(bounds: HandleElement[], handleId?: string | null): HandleEle
|
||||
return null;
|
||||
}
|
||||
|
||||
if (bounds.length === 1 || !handleId) {
|
||||
return bounds[0];
|
||||
} else if (handleId) {
|
||||
return bounds.find((d) => d.id === handleId) || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
// if no handleId is given, we use the first handle, otherwise we check for the id
|
||||
return (!handleId ? bounds[0] : bounds.find((d) => d.id === handleId)) || null;
|
||||
}
|
||||
|
||||
@@ -198,3 +198,7 @@ export const getViewportForBounds = (
|
||||
};
|
||||
|
||||
export const isMacOs = () => typeof navigator !== 'undefined' && navigator?.userAgent?.indexOf('Mac') >= 0;
|
||||
|
||||
export function isCoordinateExtent(extent?: CoordinateExtent | 'parent'): extent is CoordinateExtent {
|
||||
return extent !== undefined && extent !== 'parent';
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import {
|
||||
clampPosition,
|
||||
getBoundsOfBoxes,
|
||||
getOverlappingArea,
|
||||
isNumeric,
|
||||
rectToBox,
|
||||
nodeToRect,
|
||||
pointToRendererPoint,
|
||||
getViewportForBounds,
|
||||
isCoordinateExtent,
|
||||
} from './general';
|
||||
import {
|
||||
type Transform,
|
||||
@@ -19,10 +19,10 @@ import {
|
||||
type EdgeBase,
|
||||
type FitViewParamsBase,
|
||||
type FitViewOptionsBase,
|
||||
NodeDragItem,
|
||||
CoordinateExtent,
|
||||
OnError,
|
||||
OnBeforeDelete,
|
||||
OnBeforeDeleteBase,
|
||||
NodeLookup,
|
||||
} from '../types';
|
||||
import { errorMessages } from '../constants';
|
||||
|
||||
@@ -102,11 +102,13 @@ export const getIncomers = <NodeType extends NodeBase = NodeBase, EdgeType exten
|
||||
export const getNodePositionWithOrigin = (
|
||||
node: NodeBase | undefined,
|
||||
nodeOrigin: NodeOrigin = [0, 0]
|
||||
): XYPosition & { positionAbsolute: XYPosition } => {
|
||||
): { position: XYPosition; positionAbsolute: XYPosition } => {
|
||||
if (!node) {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
positionAbsolute: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -123,7 +125,7 @@ export const getNodePositionWithOrigin = (
|
||||
};
|
||||
|
||||
return {
|
||||
...position,
|
||||
position,
|
||||
positionAbsolute: node.computed?.positionAbsolute
|
||||
? {
|
||||
x: node.computed.positionAbsolute.x - offsetX,
|
||||
@@ -133,27 +135,35 @@ export const getNodePositionWithOrigin = (
|
||||
};
|
||||
};
|
||||
|
||||
export type GetNodesBoundsParams = {
|
||||
nodeOrigin?: NodeOrigin;
|
||||
useRelativePosition?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines a bounding box that contains all given nodes in an array
|
||||
* @public
|
||||
* @remarks Useful when combined with {@link getViewportForBounds} to calculate the correct transform to fit the given nodes in a viewport.
|
||||
* @param nodes - Nodes to calculate the bounds for
|
||||
* @param nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center
|
||||
* @param params.nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center
|
||||
* @param params.useRelativePosition - Whether to use the relative or absolute node positions
|
||||
* @returns Bounding box enclosing all nodes
|
||||
*/
|
||||
export const getNodesBounds = (nodes: NodeBase[], nodeOrigin: NodeOrigin = [0, 0]): Rect => {
|
||||
export const getNodesBounds = (
|
||||
nodes: NodeBase[],
|
||||
params: GetNodesBoundsParams = { nodeOrigin: [0, 0], useRelativePosition: false }
|
||||
): Rect => {
|
||||
if (nodes.length === 0) {
|
||||
return { x: 0, y: 0, width: 0, height: 0 };
|
||||
}
|
||||
|
||||
const box = nodes.reduce(
|
||||
(currBox, node) => {
|
||||
const { x, y } = getNodePositionWithOrigin(node, node.origin || nodeOrigin);
|
||||
const nodePos = getNodePositionWithOrigin(node, node.origin || params.nodeOrigin);
|
||||
return getBoundsOfBoxes(
|
||||
currBox,
|
||||
rectToBox({
|
||||
x,
|
||||
y,
|
||||
...nodePos[params.useRelativePosition ? 'position' : 'positionAbsolute'],
|
||||
width: node.computed?.width ?? node.width ?? 0,
|
||||
height: node.computed?.height ?? node.height ?? 0,
|
||||
})
|
||||
@@ -239,7 +249,7 @@ export function fitView<Params extends FitViewParamsBase<NodeBase>, Options exte
|
||||
});
|
||||
|
||||
if (filteredNodes.length > 0) {
|
||||
const bounds = getNodesBounds(filteredNodes, nodeOrigin);
|
||||
const bounds = getNodesBounds(filteredNodes, { nodeOrigin });
|
||||
|
||||
const viewport = getViewportForBounds(
|
||||
bounds,
|
||||
@@ -258,69 +268,87 @@ export function fitView<Params extends FitViewParamsBase<NodeBase>, Options exte
|
||||
return false;
|
||||
}
|
||||
|
||||
function clampNodeExtent(node: NodeDragItem | NodeBase, extent?: CoordinateExtent | 'parent') {
|
||||
/**
|
||||
* This function clamps the passed extend by the node's width and height.
|
||||
* This is needed to prevent the node from being dragged outside of its extent.
|
||||
*
|
||||
* @param node
|
||||
* @param extent
|
||||
* @returns
|
||||
*/
|
||||
function clampNodeExtent<NodeType extends NodeBase>(
|
||||
node: NodeType,
|
||||
extent?: CoordinateExtent | 'parent'
|
||||
): CoordinateExtent | 'parent' | undefined {
|
||||
if (!extent || extent === 'parent') {
|
||||
return extent;
|
||||
}
|
||||
return [extent[0], [extent[1][0] - (node.computed?.width ?? 0), extent[1][1] - (node.computed?.height ?? 0)]];
|
||||
}
|
||||
|
||||
export function calcNextPosition<NodeType extends NodeBase>(
|
||||
node: NodeDragItem | NodeType,
|
||||
nextPosition: XYPosition,
|
||||
nodes: NodeType[],
|
||||
nodeExtent?: CoordinateExtent,
|
||||
nodeOrigin: NodeOrigin = [0, 0],
|
||||
onError?: OnError
|
||||
): { position: XYPosition; positionAbsolute: XYPosition } {
|
||||
const clampedNodeExtent = clampNodeExtent(node, node.extent || nodeExtent);
|
||||
let currentExtent = clampedNodeExtent;
|
||||
let parentNode: NodeType | null = null;
|
||||
let parentPos = { x: 0, y: 0 };
|
||||
|
||||
if (node.parentNode) {
|
||||
parentNode = nodes.find((n) => n.id === node.parentNode) || null;
|
||||
parentPos = parentNode
|
||||
? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute
|
||||
: parentPos;
|
||||
}
|
||||
/**
|
||||
* This function calculates the next position of a node, taking into account the node's extent, parent node, and origin.
|
||||
*
|
||||
* @internal
|
||||
* @returns position, positionAbsolute
|
||||
*/
|
||||
export function calculateNodePosition<NodeType extends NodeBase>({
|
||||
nodeId,
|
||||
nextPosition,
|
||||
nodeLookup,
|
||||
nodeOrigin = [0, 0],
|
||||
nodeExtent,
|
||||
onError,
|
||||
}: {
|
||||
nodeId: string;
|
||||
nextPosition: XYPosition;
|
||||
nodeLookup: NodeLookup<NodeType>;
|
||||
nodeOrigin?: NodeOrigin;
|
||||
nodeExtent?: CoordinateExtent;
|
||||
onError?: OnError;
|
||||
}): { position: XYPosition; positionAbsolute: XYPosition } {
|
||||
const node = nodeLookup.get(nodeId)!;
|
||||
const parentNode = node.parentNode ? nodeLookup.get(node.parentNode) : undefined;
|
||||
const { x: parentX, y: parentY } = parentNode
|
||||
? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute
|
||||
: { x: 0, y: 0 };
|
||||
let currentExtent = clampNodeExtent(node, node.extent || nodeExtent);
|
||||
|
||||
if (node.extent === 'parent' && !node.expandParent) {
|
||||
const nodeWidth = node.computed?.width;
|
||||
const nodeHeight = node.computed?.height;
|
||||
if (node.parentNode && nodeWidth && nodeHeight) {
|
||||
const currNodeOrigin = node.origin || nodeOrigin;
|
||||
|
||||
currentExtent =
|
||||
parentNode && isNumeric(parentNode.computed?.width) && isNumeric(parentNode.computed?.height)
|
||||
? [
|
||||
[parentPos.x + nodeWidth * currNodeOrigin[0], parentPos.y + nodeHeight * currNodeOrigin[1]],
|
||||
[
|
||||
parentPos.x + (parentNode.computed?.width ?? 0) - nodeWidth + nodeWidth * currNodeOrigin[0],
|
||||
parentPos.y + (parentNode.computed?.height ?? 0) - nodeHeight + nodeHeight * currNodeOrigin[1],
|
||||
],
|
||||
]
|
||||
: currentExtent;
|
||||
} else {
|
||||
if (!parentNode) {
|
||||
onError?.('005', errorMessages['error005']());
|
||||
currentExtent = clampedNodeExtent;
|
||||
} else {
|
||||
const nodeWidth = node.computed?.width;
|
||||
const nodeHeight = node.computed?.height;
|
||||
const parentWidth = parentNode?.computed?.width;
|
||||
const parentHeight = parentNode?.computed?.height;
|
||||
|
||||
if (nodeWidth && nodeHeight && parentWidth && parentHeight) {
|
||||
const currNodeOrigin = node.origin || nodeOrigin;
|
||||
const extentX = parentX + nodeWidth * currNodeOrigin[0];
|
||||
const extentY = parentY + nodeHeight * currNodeOrigin[1];
|
||||
|
||||
currentExtent = [
|
||||
[extentX, extentY],
|
||||
[extentX + parentWidth - nodeWidth, extentY + parentHeight - nodeHeight],
|
||||
];
|
||||
}
|
||||
}
|
||||
} else if (node.extent && node.parentNode && node.extent !== 'parent') {
|
||||
} else if (parentNode && isCoordinateExtent(node.extent)) {
|
||||
currentExtent = [
|
||||
[node.extent[0][0] + parentPos.x, node.extent[0][1] + parentPos.y],
|
||||
[node.extent[1][0] + parentPos.x, node.extent[1][1] + parentPos.y],
|
||||
[node.extent[0][0] + parentX, node.extent[0][1] + parentY],
|
||||
[node.extent[1][0] + parentX, node.extent[1][1] + parentY],
|
||||
];
|
||||
}
|
||||
|
||||
const positionAbsolute =
|
||||
currentExtent && currentExtent !== 'parent'
|
||||
? clampPosition(nextPosition, currentExtent as CoordinateExtent)
|
||||
: nextPosition;
|
||||
const positionAbsolute = isCoordinateExtent(currentExtent)
|
||||
? clampPosition(nextPosition, currentExtent)
|
||||
: nextPosition;
|
||||
|
||||
return {
|
||||
position: {
|
||||
x: positionAbsolute.x - parentPos.x,
|
||||
y: positionAbsolute.y - parentPos.y,
|
||||
x: positionAbsolute.x - parentX,
|
||||
y: positionAbsolute.y - parentY,
|
||||
},
|
||||
positionAbsolute,
|
||||
};
|
||||
@@ -347,7 +375,7 @@ export async function getElementsToRemove<NodeType extends NodeBase = NodeBase,
|
||||
edgesToRemove: Partial<EdgeType>[];
|
||||
nodes: NodeType[];
|
||||
edges: EdgeType[];
|
||||
onBeforeDelete?: OnBeforeDelete;
|
||||
onBeforeDelete?: OnBeforeDeleteBase<NodeType, EdgeType>;
|
||||
}): Promise<{
|
||||
nodes: NodeType[];
|
||||
edges: EdgeType[];
|
||||
|
||||
@@ -19,21 +19,32 @@ export function getMarkerId(marker: EdgeMarkerType | undefined, id?: string | nu
|
||||
|
||||
export function createMarkerIds(
|
||||
edges: EdgeBase[],
|
||||
{ id, defaultColor }: { id?: string | null; defaultColor?: string }
|
||||
{
|
||||
id,
|
||||
defaultColor,
|
||||
defaultMarkerStart,
|
||||
defaultMarkerEnd,
|
||||
}: {
|
||||
id?: string | null;
|
||||
defaultColor?: string;
|
||||
defaultMarkerStart?: EdgeMarkerType;
|
||||
defaultMarkerEnd?: EdgeMarkerType;
|
||||
}
|
||||
) {
|
||||
const ids: string[] = [];
|
||||
const ids = new Set<string>();
|
||||
|
||||
return edges
|
||||
.reduce<MarkerProps[]>((markers, edge) => {
|
||||
[edge.markerStart, edge.markerEnd].forEach((marker) => {
|
||||
[edge.markerStart || defaultMarkerStart, edge.markerEnd || defaultMarkerEnd].forEach((marker) => {
|
||||
if (marker && typeof marker === 'object') {
|
||||
const markerId = getMarkerId(marker, id);
|
||||
if (!ids.includes(markerId)) {
|
||||
if (!ids.has(markerId)) {
|
||||
markers.push({ id: markerId, color: marker.color || defaultColor, ...marker });
|
||||
ids.push(markerId);
|
||||
ids.add(markerId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return markers;
|
||||
}, [])
|
||||
.sort((a, b) => a.id.localeCompare(b.id));
|
||||
|
||||
@@ -136,7 +136,7 @@ function calculateXYZPosition<NodeType extends NodeBase>(
|
||||
}
|
||||
|
||||
const parentNode = nodeLookup.get(node.parentNode)!;
|
||||
const parentNodePosition = getNodePositionWithOrigin(parentNode, parentNode?.origin || nodeOrigin);
|
||||
const { position: parentNodePosition } = getNodePositionWithOrigin(parentNode, parentNode?.origin || nodeOrigin);
|
||||
|
||||
return calculateXYZPosition(
|
||||
parentNode,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
calcAutoPan,
|
||||
getEventPosition,
|
||||
getPointerPosition,
|
||||
calcNextPosition,
|
||||
calculateNodePosition,
|
||||
snapPosition,
|
||||
getNodesBounds,
|
||||
rectToBox,
|
||||
@@ -103,7 +103,6 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
||||
function update({ noDragClassName, handleSelector, domNode, isSelectable, nodeId }: DragUpdateParams) {
|
||||
function updateNodes({ x, y }: XYPosition) {
|
||||
const {
|
||||
nodes,
|
||||
nodeLookup,
|
||||
nodeExtent,
|
||||
snapGrid,
|
||||
@@ -121,7 +120,7 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
||||
let nodesBox: Box = { x: 0, y: 0, x2: 0, y2: 0 };
|
||||
|
||||
if (dragItems.length > 1 && nodeExtent) {
|
||||
const rect = getNodesBounds(dragItems as unknown as NodeBase[], nodeOrigin);
|
||||
const rect = getNodesBounds(dragItems as unknown as NodeBase[], { nodeOrigin });
|
||||
nodesBox = rectToBox(rect);
|
||||
}
|
||||
|
||||
@@ -149,13 +148,20 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
||||
n.computed.positionAbsolute.y + (n.computed?.height ?? 0) - nodesBox.y2 + nodeExtent[1][1];
|
||||
}
|
||||
|
||||
const updatedPos = calcNextPosition(n, nextPosition, nodes, adjustedNodeExtent, nodeOrigin, onError);
|
||||
const { position, positionAbsolute } = calculateNodePosition({
|
||||
nodeId: n.id,
|
||||
nextPosition,
|
||||
nodeLookup,
|
||||
nodeExtent: adjustedNodeExtent,
|
||||
nodeOrigin,
|
||||
onError,
|
||||
});
|
||||
|
||||
// we want to make sure that we only fire a change event when there is a change
|
||||
hasChange = hasChange || n.position.x !== updatedPos.position.x || n.position.y !== updatedPos.position.y;
|
||||
hasChange = hasChange || n.position.x !== position.x || n.position.y !== position.y;
|
||||
|
||||
n.position = updatedPos.position;
|
||||
n.computed.positionAbsolute = updatedPos.positionAbsolute;
|
||||
n.position = position;
|
||||
n.computed.positionAbsolute = positionAbsolute;
|
||||
|
||||
return n;
|
||||
});
|
||||
|
||||
@@ -260,9 +260,10 @@ function isValidHandle(
|
||||
}: IsValidParams
|
||||
) {
|
||||
const isTarget = fromType === 'target';
|
||||
const handleDomNode = doc.querySelector(
|
||||
`.${lib}-flow__handle[data-id="${flowId}-${handle?.nodeId}-${handle?.id}-${handle?.type}"]`
|
||||
);
|
||||
const handleDomNode = handle
|
||||
? doc.querySelector(`.${lib}-flow__handle[data-id="${flowId}-${handle?.nodeId}-${handle?.id}-${handle?.type}"]`)
|
||||
: null;
|
||||
|
||||
const { x, y } = getEventPosition(event);
|
||||
const handleBelow = doc.elementFromPoint(x, y);
|
||||
// we always want to prioritize the handle below the mouse cursor over the closest distance handle,
|
||||
|
||||
Reference in New Issue
Block a user