fixed bug with expanding parent, made adjustments to evaluateNodePosition
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
|||||||
type NodeChange,
|
type NodeChange,
|
||||||
type NodeDimensionChange,
|
type NodeDimensionChange,
|
||||||
type NodePositionChange,
|
type NodePositionChange,
|
||||||
handleParentExpand,
|
handleExpandParent,
|
||||||
evaluateNodePosition,
|
evaluateNodePosition,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
@@ -74,20 +74,32 @@ function ResizeControl({
|
|||||||
|
|
||||||
const node = nodeLookup.get(id);
|
const node = nodeLookup.get(id);
|
||||||
if (node && node.expandParent && node.parentId) {
|
if (node && node.expandParent && node.parentId) {
|
||||||
const nodeWithChange: Node = {
|
// We create a new node that will be used to handleExpandParent ...
|
||||||
...node,
|
const nodeWithChange = {
|
||||||
|
...(node as InternalNodeWithParentExpand),
|
||||||
position: {
|
position: {
|
||||||
x: change.x ?? node.position.x,
|
x: change.x ?? node.position.x,
|
||||||
y: change.y ?? node.position.y,
|
y: change.y ?? node.position.y,
|
||||||
},
|
},
|
||||||
|
measured: {
|
||||||
|
width: change.width,
|
||||||
|
height: change.height,
|
||||||
|
},
|
||||||
width: change.width,
|
width: change.width,
|
||||||
height: change.height,
|
height: change.height,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodeWith = evaluateNodePosition(nodeWithChange, nodeLookup) as InternalNodeWithParentExpand;
|
// ...determine its new absolute position...
|
||||||
|
nodeWithChange.internals = {
|
||||||
|
...nodeWithChange.internals,
|
||||||
|
positionAbsolute: evaluateNodePosition(nodeWithChange, nodeLookup),
|
||||||
|
};
|
||||||
|
|
||||||
const parentExpandChanges = handleParentExpand([nodeWith], nodeLookup, parentLookup);
|
// ... and use it to expand the parent
|
||||||
|
const parentExpandChanges = handleExpandParent([nodeWithChange], nodeLookup, parentLookup);
|
||||||
changes.push(...parentExpandChanges);
|
changes.push(...parentExpandChanges);
|
||||||
|
|
||||||
|
// when the parent was expanded by the child node, its position will be clamped at 0,0
|
||||||
newPosition.x = change.x ? Math.max(0, change.x) : undefined;
|
newPosition.x = change.x ? Math.max(0, change.x) : undefined;
|
||||||
newPosition.y = change.y ? Math.max(0, change.y) : undefined;
|
newPosition.y = change.y ? Math.max(0, change.y) : undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,22 +42,12 @@ export function NodeWrapper<NodeType extends Node>({
|
|||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
onError,
|
onError,
|
||||||
}: NodeWrapperProps<NodeType>) {
|
}: NodeWrapperProps<NodeType>) {
|
||||||
const { node, positionAbsoluteX, positionAbsoluteY, internals } = useStore((s) => {
|
const { node, internals } = useStore((s) => {
|
||||||
const node = s.nodeLookup.get(id)! as InternalNode<NodeType>;
|
const node = s.nodeLookup.get(id)! as InternalNode<NodeType>;
|
||||||
|
|
||||||
const positionAbsolute = nodeExtent
|
|
||||||
? clampPosition(node.internals.positionAbsolute, nodeExtent)
|
|
||||||
: node.internals.positionAbsolute || { x: 0, y: 0 };
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
node,
|
node,
|
||||||
// we are mutating positionAbsolute, z and isParent attributes for sub flows
|
|
||||||
// so we we need to force a re-render when some change
|
|
||||||
positionAbsoluteX: positionAbsolute.x,
|
|
||||||
positionAbsoluteY: positionAbsolute.y,
|
|
||||||
internals: node.internals,
|
internals: node.internals,
|
||||||
// zIndex: node.internals.z,
|
|
||||||
// isParent: node.internals.isParent,
|
|
||||||
};
|
};
|
||||||
}, shallow);
|
}, shallow);
|
||||||
|
|
||||||
@@ -141,10 +131,15 @@ export function NodeWrapper<NodeType extends Node>({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const positionAbsoluteOrigin = getPositionWithOrigin({
|
const positionAbsolute = nodeExtent
|
||||||
x: positionAbsoluteX,
|
? clampPosition(node.internals.positionAbsolute, nodeExtent)
|
||||||
y: positionAbsoluteY,
|
: node.internals.positionAbsolute || { x: 0, y: 0 };
|
||||||
...nodeDimensions,
|
|
||||||
|
const positionWithOrigin = getPositionWithOrigin({
|
||||||
|
x: positionAbsolute.x,
|
||||||
|
y: positionAbsolute.y,
|
||||||
|
width: nodeDimensions.width,
|
||||||
|
height: nodeDimensions.height,
|
||||||
origin: node.origin || nodeOrigin,
|
origin: node.origin || nodeOrigin,
|
||||||
});
|
});
|
||||||
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
|
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
|
||||||
@@ -191,7 +186,7 @@ export function NodeWrapper<NodeType extends Node>({
|
|||||||
store.setState({
|
store.setState({
|
||||||
ariaLiveMessage: `Moved selected node ${event.key
|
ariaLiveMessage: `Moved selected node ${event.key
|
||||||
.replace('Arrow', '')
|
.replace('Arrow', '')
|
||||||
.toLowerCase()}. New position, x: ${~~positionAbsoluteX}, y: ${~~positionAbsoluteY}`,
|
.toLowerCase()}. New position, x: ${~~positionAbsolute.x}, y: ${~~positionAbsolute.y}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
moveSelectedNodes({
|
moveSelectedNodes({
|
||||||
@@ -222,7 +217,7 @@ export function NodeWrapper<NodeType extends Node>({
|
|||||||
ref={nodeRef}
|
ref={nodeRef}
|
||||||
style={{
|
style={{
|
||||||
zIndex: internals.z,
|
zIndex: internals.z,
|
||||||
transform: `translate(${positionAbsoluteOrigin.x}px,${positionAbsoluteOrigin.y}px)`,
|
transform: `translate(${positionWithOrigin.x}px,${positionWithOrigin.y}px)`,
|
||||||
pointerEvents: hasPointerEvents ? 'all' : 'none',
|
pointerEvents: hasPointerEvents ? 'all' : 'none',
|
||||||
visibility: initialized ? 'visible' : 'hidden',
|
visibility: initialized ? 'visible' : 'hidden',
|
||||||
...node.style,
|
...node.style,
|
||||||
@@ -247,8 +242,8 @@ export function NodeWrapper<NodeType extends Node>({
|
|||||||
id={id}
|
id={id}
|
||||||
data={node.data}
|
data={node.data}
|
||||||
type={nodeType}
|
type={nodeType}
|
||||||
positionAbsoluteX={positionAbsoluteX}
|
positionAbsoluteX={positionAbsolute.x}
|
||||||
positionAbsoluteY={positionAbsoluteY}
|
positionAbsoluteY={positionAbsolute.y}
|
||||||
selected={node.selected}
|
selected={node.selected}
|
||||||
isConnectable={isConnectable}
|
isConnectable={isConnectable}
|
||||||
sourcePosition={node.sourcePosition}
|
sourcePosition={node.sourcePosition}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
evaluateNodePosition,
|
evaluateNodePosition,
|
||||||
getElementsToRemove,
|
getElementsToRemove,
|
||||||
getOverlappingArea,
|
getOverlappingArea,
|
||||||
|
isInternalNodeBase,
|
||||||
isRectObject,
|
isRectObject,
|
||||||
nodeToRect,
|
nodeToRect,
|
||||||
type Rect,
|
type Rect,
|
||||||
@@ -232,10 +233,18 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
|||||||
|
|
||||||
const getNodeRect = useCallback((node: NodeType | { id: string }): Rect | null => {
|
const getNodeRect = useCallback((node: NodeType | { id: string }): Rect | null => {
|
||||||
const { nodeLookup, nodeOrigin } = store.getState();
|
const { nodeLookup, nodeOrigin } = store.getState();
|
||||||
const nodeToUse = isNode(node) ? node : nodeLookup.get(node.id)!;
|
const nodeToUse = isNode<NodeType>(node) ? node : nodeLookup.get(node.id)!;
|
||||||
const nodeWithPos = evaluateNodePosition(nodeToUse, nodeLookup, nodeOrigin);
|
const positionAbsolute = evaluateNodePosition(nodeToUse, nodeLookup, nodeOrigin);
|
||||||
|
|
||||||
return nodeWithPos ? nodeToRect(nodeWithPos) : null;
|
const nodeWithPosition = {
|
||||||
|
id: nodeToUse.id,
|
||||||
|
position: positionAbsolute,
|
||||||
|
width: nodeToUse.measured?.width ?? nodeToUse.width,
|
||||||
|
height: nodeToUse.measured?.height ?? nodeToUse.height,
|
||||||
|
data: nodeToUse.data,
|
||||||
|
};
|
||||||
|
|
||||||
|
return nodeToRect(nodeWithPosition);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getIntersectingNodes = useCallback<Instance.GetIntersectingNodes<NodeType>>(
|
const getIntersectingNodes = useCallback<Instance.GetIntersectingNodes<NodeType>>(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
panBy as panBySystem,
|
panBy as panBySystem,
|
||||||
updateNodeInternals as updateNodeInternalsSystem,
|
updateNodeInternals as updateNodeInternalsSystem,
|
||||||
updateConnectionLookup,
|
updateConnectionLookup,
|
||||||
handleParentExpand,
|
handleExpandParent,
|
||||||
NodeChange,
|
NodeChange,
|
||||||
EdgeSelectionChange,
|
EdgeSelectionChange,
|
||||||
NodeSelectionChange,
|
NodeSelectionChange,
|
||||||
@@ -155,7 +155,7 @@ const createRFStore = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (expandParentNodes.length > 0) {
|
if (expandParentNodes.length > 0) {
|
||||||
const parentExpandChanges = handleParentExpand(expandParentNodes, nodeLookup, parentLookup);
|
const parentExpandChanges = handleExpandParent(expandParentNodes, nodeLookup, parentLookup);
|
||||||
changes.push(...parentExpandChanges);
|
changes.push(...parentExpandChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -234,14 +234,8 @@ export function evaluateNodePosition(
|
|||||||
node: NodeBase | InternalNodeBase,
|
node: NodeBase | InternalNodeBase,
|
||||||
nodeLookup: NodeLookup,
|
nodeLookup: NodeLookup,
|
||||||
nodeOrigin: NodeOrigin = [0, 0]
|
nodeOrigin: NodeOrigin = [0, 0]
|
||||||
): InternalNodeBase | null {
|
): XYPosition {
|
||||||
const internalNode = nodeLookup.get(node.id);
|
let parentId = node.parentId;
|
||||||
|
|
||||||
if (!internalNode) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let parentId = internalNode.parentId;
|
|
||||||
const positionAbsolute = { ...node.position };
|
const positionAbsolute = { ...node.position };
|
||||||
|
|
||||||
while (parentId) {
|
while (parentId) {
|
||||||
@@ -257,11 +251,5 @@ export function evaluateNodePosition(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return positionAbsolute;
|
||||||
...internalNode,
|
|
||||||
internals: {
|
|
||||||
...internalNode.internals,
|
|
||||||
positionAbsolute,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,36 +159,38 @@ function calculateXYZPosition<NodeType extends NodeBase>(
|
|||||||
|
|
||||||
type ExpandParentNode<NodeType> = NodeType & { parentId: string; expandParent: true };
|
type ExpandParentNode<NodeType> = NodeType & { parentId: string; expandParent: true };
|
||||||
|
|
||||||
export function handleParentExpand(
|
export function handleExpandParent(
|
||||||
nodes: ExpandParentNode<InternalNodeBase>[],
|
nodes: ExpandParentNode<InternalNodeBase>[],
|
||||||
nodeLookup: NodeLookup,
|
nodeLookup: NodeLookup,
|
||||||
parentLookup: ParentLookup
|
parentLookup: ParentLookup
|
||||||
): (NodeDimensionChange | NodePositionChange)[] {
|
): (NodeDimensionChange | NodePositionChange)[] {
|
||||||
const changes: (NodeDimensionChange | NodePositionChange)[] = [];
|
const changes: (NodeDimensionChange | NodePositionChange)[] = [];
|
||||||
const chilNodeRects = new Map<string, Rect>();
|
const childNodeRects = new Map<string, { expandedRect: Rect; parent: InternalNodeBase }>();
|
||||||
|
|
||||||
|
// determine the expanded rectangle the child nodes would take for each parent
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
const parentNode = nodeLookup.get(node.parentId);
|
const parent = nodeLookup.get(node.parentId);
|
||||||
if (!parentNode) {
|
if (!parent) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentRect = chilNodeRects.get(node.parentId) ?? nodeToRect(parentNode, node.origin);
|
const parentRect = childNodeRects.get(node.parentId)?.expandedRect ?? nodeToRect(parent, node.origin);
|
||||||
const expandedRect = getBoundsOfRects(parentRect, nodeToRect(node, node.origin));
|
const expandedRect = getBoundsOfRects(parentRect, nodeToRect(node, node.origin));
|
||||||
chilNodeRects.set(node.parentId, expandedRect);
|
childNodeRects.set(node.parentId, { expandedRect, parent });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chilNodeRects.size > 0) {
|
if (childNodeRects.size > 0) {
|
||||||
chilNodeRects.forEach((rect, id) => {
|
childNodeRects.forEach(({ expandedRect, parent }, parentId) => {
|
||||||
const origParent = nodeLookup.get(id)!;
|
// determine the position & dimensions of the parent
|
||||||
const { position } = getNodePositionWithOrigin(origParent, origParent.origin);
|
const { position } = getNodePositionWithOrigin(parent, parent.origin);
|
||||||
const dimensions = getNodeDimensions(origParent);
|
const dimensions = getNodeDimensions(parent);
|
||||||
|
|
||||||
let xChange = rect.x < position.x ? Math.round(Math.abs(position.x - rect.x)) : 0;
|
// determine how much the parent expands by moving the position
|
||||||
let yChange = rect.y < position.y ? Math.round(Math.abs(position.y - rect.y)) : 0;
|
let xChange = expandedRect.x < position.x ? Math.round(Math.abs(position.x - expandedRect.x)) : 0;
|
||||||
|
let yChange = expandedRect.y < position.y ? Math.round(Math.abs(position.y - expandedRect.y)) : 0;
|
||||||
if (xChange > 0 || yChange > 0) {
|
if (xChange > 0 || yChange > 0) {
|
||||||
changes.push({
|
changes.push({
|
||||||
id,
|
id: parentId,
|
||||||
type: 'position',
|
type: 'position',
|
||||||
position: {
|
position: {
|
||||||
x: position.x - xChange,
|
x: position.x - xChange,
|
||||||
@@ -196,7 +198,9 @@ export function handleParentExpand(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const childNodes = parentLookup.get(id);
|
// We move all child nodes in the oppsite direction
|
||||||
|
// so the x,y changes of the parent do not move the children
|
||||||
|
const childNodes = parentLookup.get(parentId);
|
||||||
childNodes?.forEach((childNode) => {
|
childNodes?.forEach((childNode) => {
|
||||||
if (!nodes.some((n) => n.id === childNode.id)) {
|
if (!nodes.some((n) => n.id === childNode.id)) {
|
||||||
changes.push({
|
changes.push({
|
||||||
@@ -211,14 +215,14 @@ export function handleParentExpand(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dimensions.width < rect.width || dimensions.height < rect.height) {
|
if (dimensions.width < expandedRect.width || dimensions.height < expandedRect.height) {
|
||||||
changes.push({
|
changes.push({
|
||||||
id,
|
id: parentId,
|
||||||
type: 'dimensions',
|
type: 'dimensions',
|
||||||
resizing: true,
|
resizing: true,
|
||||||
dimensions: {
|
dimensions: {
|
||||||
width: Math.max(dimensions.width, Math.round(rect.width)),
|
width: Math.max(dimensions.width, Math.round(expandedRect.width)),
|
||||||
height: Math.max(dimensions.height, Math.round(rect.height)),
|
height: Math.max(dimensions.height, Math.round(expandedRect.height)),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -301,7 +305,7 @@ export function updateNodeInternals<NodeType extends InternalNodeBase>(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (parentExpandNodes.length > 0) {
|
if (parentExpandNodes.length > 0) {
|
||||||
const parentExpandChanges = handleParentExpand(parentExpandNodes, nodeLookup, parentLookup);
|
const parentExpandChanges = handleExpandParent(parentExpandNodes, nodeLookup, parentLookup);
|
||||||
changes.push(...parentExpandChanges);
|
changes.push(...parentExpandChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user