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 initialNodes: Node[] = [
{
id: 'extent',
position: { x: 0, y: 0 },
width: 1000,
height: 1000,
data: { label: 'Extent' },
origin: [0, 0],
zIndex: -1,
},
// {
// id: 'extent',
// position: { x: 0, y: 0 },
// width: 1000,
// height: 1000,
// data: { label: 'Extent' },
// origin: [0, 0],
// zIndex: -1,
// },
{
id: '1',
type: 'input',
@@ -96,12 +96,12 @@ const initialNodes: Node[] = [
},
{
id: '5',
type: 'group',
// type: 'group',
data: { label: 'Node 5' },
position: { x: 650, y: 250 },
className: 'light',
style: { width: 100, height: 100 },
zIndex: 1000,
// zIndex: 1000,
},
{
id: '5a',

View File

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

View File

@@ -30,6 +30,9 @@ import {
import { getNodePositionWithOrigin } from './graph';
import { ParentExpandChild } from './types';
const SELECTED_NODE_Z = 1000;
const ROOT_PARENT_Z_INCREMENT = 10;
const defaultOptions = {
nodeOrigin: [0, 0] as NodeOrigin,
nodeExtent: infiniteExtent,
@@ -120,9 +123,10 @@ export function adoptUserNodes<NodeType extends NodeBase>(
): boolean {
const _options = mergeObjects(adoptUserNodesDefaultOptions, options);
let rootParentIndex = { i: -1 };
let nodesInitialized = nodes.length > 0;
const tmpLookup = new Map(nodeLookup);
const selectedNodeZ: number = _options?.elevateNodesOnSelect ? 1000 : 0;
const selectedNodeZ: number = _options?.elevateNodesOnSelect ? SELECTED_NODE_Z : 0;
nodeLookup.clear();
parentLookup.clear();
@@ -166,7 +170,7 @@ export function adoptUserNodes<NodeType extends NodeBase>(
}
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>,
nodeLookup: NodeLookup<InternalNodeBase<NodeType>>,
parentLookup: ParentLookup<InternalNodeBase<NodeType>>,
options?: UpdateNodesOptions<NodeType>
options?: UpdateNodesOptions<NodeType>,
rootParentIndex?: { i: number }
) {
const { elevateNodesOnSelect, nodeOrigin, nodeExtent } = mergeObjects(defaultOptions, options);
const parentId = node.parentId!;
@@ -212,7 +217,18 @@ function updateChildNode<NodeType extends NodeBase>(
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 { positionAbsolute } = node.internals;
const positionChanged = x !== positionAbsolute.x || y !== positionAbsolute.y;