diff --git a/.changeset/real-ties-travel.md b/.changeset/real-ties-travel.md new file mode 100644 index 00000000..14e29173 --- /dev/null +++ b/.changeset/real-ties-travel.md @@ -0,0 +1,5 @@ +--- +'@xyflow/system': patch +--- + +Render edges above nodes when they are within a subflow diff --git a/examples/react/src/examples/Subflow/index.tsx b/examples/react/src/examples/Subflow/index.tsx index 0f59a97e..82d0d413 100644 --- a/examples/react/src/examples/Subflow/index.tsx +++ b/examples/react/src/examples/Subflow/index.tsx @@ -152,10 +152,10 @@ const initialEdges: Edge[] = [ }, }, { id: 'e1-3', source: '1', target: '3' }, - { id: 'e3-4', source: '3', target: '4', zIndex: 100 }, + { id: 'e3-4', source: '3', target: '4' }, { id: 'e3-4b', source: '3', target: '4b' }, { id: 'e4a-4b1', source: '4a', target: '4b1' }, - { id: 'e4a-4b2', source: '4a', target: '4b2', zIndex: 100 }, + { id: 'e4a-4b2', source: '4a', target: '4b2' }, { id: 'e4b1-4b2', source: '4b1', target: '4b2' }, ]; diff --git a/examples/react/src/generic-tests/edges/general.ts b/examples/react/src/generic-tests/edges/general.ts index d00c57cc..dbdaec24 100644 --- a/examples/react/src/generic-tests/edges/general.ts +++ b/examples/react/src/generic-tests/edges/general.ts @@ -62,6 +62,29 @@ export default { data: { label: '11' }, position: { x: 100, y: 500 }, }, + { + id: '12', + data: { label: '12' }, + position: { x: 100, y: 600 }, + width: 200, + height: 100, + }, + { + id: '12-a', + parentId: '12', + data: { label: '12-a' }, + position: { x: 10, y: 20 }, + width: 50, + height: 50, + }, + { + id: '12-b', + parentId: '12', + data: { label: '12-b' }, + position: { x: 140, y: 20 }, + width: 50, + height: 50, + }, // { // id: '12', // data: { label: '12' }, @@ -145,6 +168,16 @@ export default { markerEnd: { type: MarkerType.Arrow }, markerStart: { type: MarkerType.ArrowClosed }, }, + { + id: 'subflow-edge', + source: '11', + target: '12-a', + }, + { + id: 'subflow-edge-2', + source: '12-a', + target: '12-b', + }, // { // id: 'updatable', // source: '9', diff --git a/examples/svelte/src/generic-tests/edges/general.ts b/examples/svelte/src/generic-tests/edges/general.ts index e17da374..cebbccd8 100644 --- a/examples/svelte/src/generic-tests/edges/general.ts +++ b/examples/svelte/src/generic-tests/edges/general.ts @@ -61,6 +61,29 @@ export default { id: '11', data: { label: '11' }, position: { x: 100, y: 500 } + }, + { + id: '12', + data: { label: '12' }, + position: { x: 100, y: 600 }, + width: 200, + height: 100 + }, + { + id: '12-a', + parentId: '12', + data: { label: '12-a' }, + position: { x: 10, y: 20 }, + width: 50, + height: 50 + }, + { + id: '12-b', + parentId: '12', + data: { label: '12-b' }, + position: { x: 140, y: 20 }, + width: 50, + height: 50 } // { // id: '12', @@ -142,6 +165,16 @@ export default { label: 'markers', markerEnd: { type: MarkerType.Arrow }, markerStart: { type: MarkerType.ArrowClosed } + }, + { + id: 'subflow-edge', + source: '11', + target: '12-a' + }, + { + id: 'subflow-edge-2', + source: '12-a', + target: '12-b' } // { // id: 'updatable', diff --git a/packages/system/src/utils/edges/general.ts b/packages/system/src/utils/edges/general.ts index c094ab0b..c88c7272 100644 --- a/packages/system/src/utils/edges/general.ts +++ b/packages/system/src/utils/edges/general.ts @@ -30,21 +30,30 @@ export type GetEdgeZIndexParams = { elevateOnSelect?: boolean; }; +/** + * Returns the z-index for an edge based on the node it connects and whether it is selected. + * By default, edges are rendered below nodes. This behaviour is different for edges that are + * connected to nodes with a parent, as they are rendered above the parent node. + */ export function getElevatedEdgeZIndex({ sourceNode, targetNode, selected = false, - zIndex = 0, + zIndex, elevateOnSelect = false, }: GetEdgeZIndexParams): number { - if (!elevateOnSelect) { + if (zIndex !== undefined) { return zIndex; } - const edgeOrConnectedNodeSelected = selected || targetNode.selected || sourceNode.selected; - const selectedZIndex = Math.max(sourceNode.internals.z || 0, targetNode.internals.z || 0, 1000); + const edgeZ = elevateOnSelect && selected ? 1000 : 0; - return zIndex + (edgeOrConnectedNodeSelected ? selectedZIndex : 0); + const nodeZ = Math.max( + sourceNode.parentId ? sourceNode.internals.z : 0, + targetNode.parentId ? targetNode.internals.z : 0 + ); + + return edgeZ + nodeZ; } type IsEdgeVisibleParams = { diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index d94a7dff..1bfd029d 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -232,7 +232,7 @@ function calculateChildXYZ( return { x: absolutePosition.x, y: absolutePosition.y, - z: parentZ > childZ ? parentZ : childZ, + z: parentZ >= childZ ? parentZ + 1 : childZ, }; } diff --git a/tests/playwright/e2e/edges.spec.ts b/tests/playwright/e2e/edges.spec.ts index 768f056c..6e26c8ca 100644 --- a/tests/playwright/e2e/edges.spec.ts +++ b/tests/playwright/e2e/edges.spec.ts @@ -153,5 +153,26 @@ test.describe('Edges', () => { await expect(edge).toHaveAttribute('marker-start', "url('#1__type=arrowclosed')"); await expect(edge).toHaveAttribute('marker-end', "url('#1__type=arrow')"); }); + + test('z-index', async ({ page }) => { + const svg = page.locator('svg', { has: page.locator('[data-id="edge-with-class"]') }); + + await expect(svg).toBeAttached(); + await expect(svg).toHaveCSS('z-index', '0'); + }); + + test('sub flow: normal node to child node, z-index', async ({ page }) => { + const svg = page.locator('svg', { has: page.locator('[data-id="subflow-edge"]') }); + + await expect(svg).toBeAttached(); + await expect(svg).toHaveCSS('z-index', '1'); + }); + + test('sub flow: child node to child node, z-index', async ({ page }) => { + const svg = page.locator('svg', { has: page.locator('[data-id="subflow-edge-2"]') }); + + await expect(svg).toBeAttached(); + await expect(svg).toHaveCSS('z-index', '1'); + }); }); });