Merge pull request #5550 from xyflow/feature/subflow-sort-order

Feature/subflow sort order
This commit is contained in:
Moritz Klack
2025-10-17 19:17:37 +02:00
committed by GitHub
4 changed files with 39 additions and 15 deletions

View File

@@ -0,0 +1,7 @@
---
'@xyflow/system': patch
'@xyflow/react': minor
'@xyflow/svelte': minor
---
Prevent child nodes of different parents from overlapping

View File

@@ -27,15 +27,15 @@ const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
const defaultViewport = { x: 0, y: 0, zoom: 1.5 }; const defaultViewport = { x: 0, y: 0, zoom: 1.5 };
const initialNodes: Node[] = [ const initialNodes: Node[] = [
{ // {
id: 'extent', // id: 'extent',
position: { x: 0, y: 0 }, // position: { x: 0, y: 0 },
width: 1000, // width: 1000,
height: 1000, // height: 1000,
data: { label: 'Extent' }, // data: { label: 'Extent' },
origin: [0, 0], // origin: [0, 0],
zIndex: -1, // zIndex: -1,
}, // },
{ {
id: '1', id: '1',
type: 'input', type: 'input',
@@ -96,12 +96,12 @@ const initialNodes: Node[] = [
}, },
{ {
id: '5', id: '5',
type: 'group', // type: 'group',
data: { label: 'Node 5' }, data: { label: 'Node 5' },
position: { x: 650, y: 250 }, position: { x: 650, y: 250 },
className: 'light', className: 'light',
style: { width: 100, height: 100 }, style: { width: 100, height: 100 },
zIndex: 1000, // zIndex: 1000,
}, },
{ {
id: '5a', id: '5a',

View File

@@ -95,6 +95,7 @@ export type InternalNodeBase<NodeType extends NodeBase = NodeBase> = Omit<NodeTy
internals: { internals: {
positionAbsolute: XYPosition; positionAbsolute: XYPosition;
z: number; z: number;
rootParentIndex?: number;
/** /**
* Holds a reference to the original node object provided by the user. * Holds a reference to the original node object provided by the user.
* Used as an optimization to avoid certain operations. * Used as an optimization to avoid certain operations.

View File

@@ -30,6 +30,9 @@ import {
import { getNodePositionWithOrigin } from './graph'; import { getNodePositionWithOrigin } from './graph';
import { ParentExpandChild } from './types'; import { ParentExpandChild } from './types';
const SELECTED_NODE_Z = 1000;
const ROOT_PARENT_Z_INCREMENT = 10;
const defaultOptions = { const defaultOptions = {
nodeOrigin: [0, 0] as NodeOrigin, nodeOrigin: [0, 0] as NodeOrigin,
nodeExtent: infiniteExtent, nodeExtent: infiniteExtent,
@@ -120,9 +123,10 @@ export function adoptUserNodes<NodeType extends NodeBase>(
): boolean { ): boolean {
const _options = mergeObjects(adoptUserNodesDefaultOptions, options); const _options = mergeObjects(adoptUserNodesDefaultOptions, options);
let rootParentIndex = { i: -1 };
let nodesInitialized = nodes.length > 0; let nodesInitialized = nodes.length > 0;
const tmpLookup = new Map(nodeLookup); const tmpLookup = new Map(nodeLookup);
const selectedNodeZ: number = _options?.elevateNodesOnSelect ? 1000 : 0; const selectedNodeZ: number = _options?.elevateNodesOnSelect ? SELECTED_NODE_Z : 0;
nodeLookup.clear(); nodeLookup.clear();
parentLookup.clear(); parentLookup.clear();
@@ -166,7 +170,7 @@ export function adoptUserNodes<NodeType extends NodeBase>(
} }
if (userNode.parentId) { if (userNode.parentId) {
updateChildNode(internalNode, nodeLookup, parentLookup, options); updateChildNode(internalNode, nodeLookup, parentLookup, options, rootParentIndex);
} }
} }
@@ -197,7 +201,8 @@ function updateChildNode<NodeType extends NodeBase>(
node: InternalNodeBase<NodeType>, node: InternalNodeBase<NodeType>,
nodeLookup: NodeLookup<InternalNodeBase<NodeType>>, nodeLookup: NodeLookup<InternalNodeBase<NodeType>>,
parentLookup: ParentLookup<InternalNodeBase<NodeType>>, parentLookup: ParentLookup<InternalNodeBase<NodeType>>,
options?: UpdateNodesOptions<NodeType> options?: UpdateNodesOptions<NodeType>,
rootParentIndex?: { i: number }
) { ) {
const { elevateNodesOnSelect, nodeOrigin, nodeExtent } = mergeObjects(defaultOptions, options); const { elevateNodesOnSelect, nodeOrigin, nodeExtent } = mergeObjects(defaultOptions, options);
const parentId = node.parentId!; const parentId = node.parentId!;
@@ -212,7 +217,18 @@ function updateChildNode<NodeType extends NodeBase>(
updateParentLookup(node, parentLookup); updateParentLookup(node, parentLookup);
const selectedNodeZ = elevateNodesOnSelect ? 1000 : 0; // We just want to set the rootParentIndex for the first child
if (rootParentIndex && !parentNode.parentId && parentNode.internals.rootParentIndex === undefined) {
parentNode.internals.rootParentIndex = ++rootParentIndex.i;
parentNode.internals.z = parentNode.internals.z + rootParentIndex.i * ROOT_PARENT_Z_INCREMENT;
}
// But we need to update rootParentIndex.i also when parent has not been updated
if (rootParentIndex && parentNode.internals.rootParentIndex !== undefined) {
rootParentIndex.i = parentNode.internals.rootParentIndex;
}
const selectedNodeZ = elevateNodesOnSelect ? SELECTED_NODE_Z : 0;
const { x, y, z } = calculateChildXYZ(node, parentNode, nodeOrigin, nodeExtent, selectedNodeZ); const { x, y, z } = calculateChildXYZ(node, parentNode, nodeOrigin, nodeExtent, selectedNodeZ);
const { positionAbsolute } = node.internals; const { positionAbsolute } = node.internals;
const positionChanged = x !== positionAbsolute.x || y !== positionAbsolute.y; const positionChanged = x !== positionAbsolute.x || y !== positionAbsolute.y;