diff --git a/packages/react/src/components/Edges/wrapEdge.tsx b/packages/react/src/components/Edges/wrapEdge.tsx index b0a876b0..afdeead5 100644 --- a/packages/react/src/components/Edges/wrapEdge.tsx +++ b/packages/react/src/components/Edges/wrapEdge.tsx @@ -26,6 +26,7 @@ export default (EdgeComponent: ComponentType) => { labelBgPadding, labelBgBorderRadius, style, + zIndex, source, target, isSelectable, @@ -201,86 +202,88 @@ export default (EdgeComponent: ComponentType) => { }; return ( - - {!updating && ( - - )} - {isUpdatable && ( - <> - {(isUpdatable === 'source' || isUpdatable === true) && ( - - )} - {(isUpdatable === 'target' || isUpdatable === true) && ( - - )} - - )} - + + + {!updating && ( + + )} + {isUpdatable && ( + <> + {(isUpdatable === 'source' || isUpdatable === true) && ( + + )} + {(isUpdatable === 'target' || isUpdatable === true) && ( + + )} + + )} + + ); }; diff --git a/packages/react/src/container/EdgeRenderer/index.tsx b/packages/react/src/container/EdgeRenderer/index.tsx index 24825fe6..35137619 100644 --- a/packages/react/src/container/EdgeRenderer/index.tsx +++ b/packages/react/src/container/EdgeRenderer/index.tsx @@ -63,82 +63,76 @@ const EdgeRenderer = ({ children, }: EdgeRendererProps) => { const { edgesFocusable, edgesUpdatable, elementsSelectable, onError } = useStore(selector, shallow); - // we are grouping edges by zIndex here in order to be able to render them in the correct order - // each zIndex gets its own svg element - const edgeTree = useVisibleEdges(onlyRenderVisibleElements, elevateEdgesOnSelect); + const edges = useVisibleEdges(onlyRenderVisibleElements, elevateEdgesOnSelect); return ( - <> - {edgeTree.map(({ level, edges, isMaxLevel }) => ( - - {isMaxLevel && } - <> - {edges.map((edge) => { - let edgeType = edge.type || 'default'; +
+ + + - if (!edgeTypes[edgeType]) { - onError?.('011', errorMessages['error011'](edgeType)); - edgeType = 'default'; - } + {edges.map((edge) => { + let edgeType = edge.type || 'default'; - const EdgeComponent = edgeTypes[edgeType]; - const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined')); - const isUpdatable = - typeof onEdgeUpdate !== 'undefined' && - (edge.updatable || (edgesUpdatable && typeof edge.updatable === 'undefined')); - const isSelectable = !!( - edge.selectable || - (elementsSelectable && typeof edge.selectable === 'undefined') - ); + if (!edgeTypes[edgeType]) { + onError?.('011', errorMessages['error011'](edgeType)); + edgeType = 'default'; + } + + const EdgeComponent = edgeTypes[edgeType]; + const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined')); + const isUpdatable = + typeof onEdgeUpdate !== 'undefined' && + (edge.updatable || (edgesUpdatable && typeof edge.updatable === 'undefined')); + const isSelectable = !!(edge.selectable || (elementsSelectable && typeof edge.selectable === 'undefined')); + + return ( +
); }; diff --git a/packages/react/src/hooks/useVisibleEdges.ts b/packages/react/src/hooks/useVisibleEdges.ts index 67d02c92..ca035fbb 100644 --- a/packages/react/src/hooks/useVisibleEdges.ts +++ b/packages/react/src/hooks/useVisibleEdges.ts @@ -1,11 +1,11 @@ import { useCallback } from 'react'; -import { GroupedEdges, groupEdgesByZLevel, isEdgeVisible } from '@xyflow/system'; +import { shallow } from 'zustand/shallow'; +import { adjustEdgeZIndex, isEdgeVisible } from '@xyflow/system'; import { useStore } from '../hooks/useStore'; -import { Edge, type ReactFlowState } from '../types'; -import { shallow } from 'zustand/shallow'; +import { type Edge, type ReactFlowState } from '../types'; -function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boolean): GroupedEdges[] { +function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boolean): Edge[] { const edges = useStore( useCallback( (s: ReactFlowState) => { @@ -29,20 +29,11 @@ function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boole }) : s.edges; - return groupEdgesByZLevel(visibleEdges, s.nodeLookup, elevateEdgesOnSelect); + return visibleEdges.map((edge) => adjustEdgeZIndex(edge, s.nodeLookup, elevateEdgesOnSelect)); }, [onlyRenderVisible, elevateEdgesOnSelect] ), - (groupA, groupB) => { - const unEqual = groupA.some( - (item, index) => - item.isMaxLevel !== groupB[index].isMaxLevel || - item.level !== groupB[index].level || - !shallow(item.edges, groupB[index].edges) - ); - - return !unEqual; - } + shallow ); return edges; diff --git a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte index 7a8df68c..763c6efb 100644 --- a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte +++ b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte @@ -18,6 +18,7 @@ export let target: $$Props['target'] = ''; export let data: $$Props['data'] = {}; export let style: $$Props['style'] = undefined; + export let zIndex: $$Props['zIndex'] = undefined; export let animated: $$Props['animated'] = false; export let selected: $$Props['selected'] = false; @@ -95,42 +96,44 @@ {#if !hidden} - - - + + + + + {/if} diff --git a/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte b/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte index 27bc7d72..6dcc16fa 100644 --- a/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte +++ b/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte @@ -9,7 +9,7 @@ const { elementsSelectable, - edgeTree, + visibleEdges, edges: { setDefaultOptions } } = useStore(); @@ -18,57 +18,47 @@ }); -{#each $edgeTree as group (group.level)} - - {#if group.isMaxLevel} {/if} - - {#each group.edges as edge (edge.id)} - {@const edgeType = edge.type || 'default'} - {@const selectable = !!( - edge.selectable || - ($elementsSelectable && typeof edge.selectable === 'undefined') - )} - - - {/each} - +
+ + -{/each} - + {#each $visibleEdges as edge (edge.id)} + {@const edgeType = edge.type || 'default'} + {@const selectable = !!( + edge.selectable || + ($elementsSelectable && typeof edge.selectable === 'undefined') + )} + +
diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts index 625191e0..a16844d2 100644 --- a/packages/svelte/src/lib/store/index.ts +++ b/packages/svelte/src/lib/store/index.ts @@ -24,7 +24,7 @@ import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions, ConnectionData } import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store'; import type { SvelteFlowStore } from './types'; import { syncNodeStores, syncEdgeStores, syncViewportStores } from './utils'; -import { getEdgeTree } from './edge-tree'; +import { getVisibleEdges } from './visible-edges'; import { getVisibleNodes } from './visible-nodes'; import { getDerivedConnectionProps } from './derived-connection-props'; @@ -349,8 +349,8 @@ export function createStore({ ...store, // derived state - edgeTree: getEdgeTree(store), connection: getDerivedConnectionProps(store, currentConnection), + visibleEdges: getVisibleEdges(store), visibleNodes: getVisibleNodes(store), markers: derived( [store.edges, store.defaultMarkerColor, store.flowId], diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index d6b621d9..3821023a 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -91,7 +91,7 @@ export const getInitialStore = ({ nodeLookup: readable>(nodeLookup), visibleNodes: readable([]), edges: createEdgesStore(edges), - edgeTree: readable[]>([]), + visibleEdges: readable([]), height: writable(500), width: writable(500), minZoom: writable(0.5), diff --git a/packages/svelte/src/lib/store/edge-tree.ts b/packages/svelte/src/lib/store/visible-edges.ts similarity index 78% rename from packages/svelte/src/lib/store/edge-tree.ts rename to packages/svelte/src/lib/store/visible-edges.ts index cd5448ab..d9903261 100644 --- a/packages/svelte/src/lib/store/edge-tree.ts +++ b/packages/svelte/src/lib/store/visible-edges.ts @@ -1,10 +1,10 @@ import { derived } from 'svelte/store'; -import { groupEdgesByZLevel, isEdgeVisible, getEdgePosition } from '@xyflow/system'; +import { isEdgeVisible, getEdgePosition, adjustEdgeZIndex } from '@xyflow/system'; import type { EdgeLayouted } from '$lib/types'; import type { SvelteFlowStoreState } from './types'; -export function getEdgeTree(store: SvelteFlowStoreState) { +export function getVisibleEdges(store: SvelteFlowStoreState) { const visibleEdges = derived( [ store.edges, @@ -41,8 +41,15 @@ export function getEdgeTree(store: SvelteFlowStoreState) { ); return derived( - [visibleEdges, store.nodes, store.nodeLookup, store.connectionMode, store.onerror], - ([visibleEdges, , nodeLookup, connectionMode, onerror]) => { + [ + visibleEdges, + store.nodes, + store.nodeLookup, + store.connectionMode, + store.onlyRenderVisibleElements, + store.onerror + ], + ([visibleEdges, , nodeLookup, connectionMode, onlyRenderVisibleElements, onerror]) => { const layoutedEdges = visibleEdges.reduce((res, edge) => { const sourceNode = nodeLookup.get(edge.source); const targetNode = nodeLookup.get(edge.target); @@ -63,7 +70,7 @@ export function getEdgeTree(store: SvelteFlowStoreState) { if (edgePosition) { res.push({ - ...edge, + ...adjustEdgeZIndex(edge, nodeLookup, onlyRenderVisibleElements), ...edgePosition }); } @@ -71,9 +78,7 @@ export function getEdgeTree(store: SvelteFlowStoreState) { return res; }, []); - const groupedEdges = groupEdgesByZLevel(layoutedEdges, nodeLookup, false); - - return groupedEdges; + return layoutedEdges; } ); } diff --git a/packages/svelte/src/lib/types/edges.ts b/packages/svelte/src/lib/types/edges.ts index 33f0d908..4ae172e6 100644 --- a/packages/svelte/src/lib/types/edges.ts +++ b/packages/svelte/src/lib/types/edges.ts @@ -70,6 +70,7 @@ export type EdgeLayouted = Pick< | 'ariaLabel' | 'hidden' | 'class' + | 'zIndex' > & EdgePosition & { sourceNode?: Node; diff --git a/packages/system/src/styles/init.css b/packages/system/src/styles/init.css index 9fed0d7b..26530e9b 100644 --- a/packages/system/src/styles/init.css +++ b/packages/system/src/styles/init.css @@ -57,11 +57,6 @@ outline: none; } -.xy-flow .xy-flow__edges { - pointer-events: none; - overflow: visible; -} - .xy-flow__edge-path { stroke: var(--edge-stroke, var(--edge-stroke-default)); stroke-width: var(--edge-stroke-width, var(--edge-stroke-width-default)); diff --git a/packages/system/src/styles/style.css b/packages/system/src/styles/style.css index 99fa8876..2e6a95c8 100644 --- a/packages/system/src/styles/style.css +++ b/packages/system/src/styles/style.css @@ -20,6 +20,12 @@ --controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08); } +.xy-flow__edgewrapper { + overflow: visible; + position: absolute; + pointer-events: none; +} + .xy-flow__edge { &.updating { .xy-flow__edge-path { diff --git a/packages/system/src/utils/edges/general.ts b/packages/system/src/utils/edges/general.ts index bdffbbd8..ca1dd390 100644 --- a/packages/system/src/utils/edges/general.ts +++ b/packages/system/src/utils/edges/general.ts @@ -23,63 +23,32 @@ export function getEdgeCenter({ return [centerX, centerY, xOffset, yOffset]; } -const defaultEdgeTree = [{ level: 0, isMaxLevel: true, edges: [] }]; - export type GroupedEdges = { edges: EdgeType[]; level: number; isMaxLevel: boolean; }; -export function groupEdgesByZLevel( - edges: EdgeType[], - nodeLookup: Map, - elevateEdgesOnSelect = false -): GroupedEdges[] { - let maxLevel = -1; +export function adjustEdgeZIndex(edge: EdgeBase, nodeLookup: Map, elevateEdgesOnSelect: boolean) { + const hasZIndex = isNumeric(edge.zIndex); + let z = hasZIndex ? edge.zIndex! : 0; - const levelLookup = edges.reduce>((tree, edge) => { - const hasZIndex = isNumeric(edge.zIndex); - let z = hasZIndex ? edge.zIndex! : 0; + if (elevateEdgesOnSelect) { + const targetNode = nodeLookup.get(edge.target)!; + const sourceNode = nodeLookup.get(edge.source)!; + const edgeOrConnectedNodeSelected = edge.selected || targetNode?.selected || sourceNode?.selected; + const selectedZIndex = Math.max(sourceNode?.[internalsSymbol]?.z || 0, targetNode?.[internalsSymbol]?.z || 0, 1000); + z = (hasZIndex ? edge.zIndex! : 0) + (edgeOrConnectedNodeSelected ? selectedZIndex : 0); - if (elevateEdgesOnSelect) { - const targetNode = nodeLookup.get(edge.target); - const sourceNode = nodeLookup.get(edge.source); - const edgeOrConnectedNodeSelected = edge.selected || targetNode?.selected || sourceNode?.selected; - const selectedZIndex = Math.max( - sourceNode?.[internalsSymbol]?.z || 0, - targetNode?.[internalsSymbol]?.z || 0, - 1000 - ); - z = (hasZIndex ? edge.zIndex! : 0) + (edgeOrConnectedNodeSelected ? selectedZIndex : 0); + if (edge.zIndex !== z) { + return { + ...edge, + zIndex: z, + }; } - - if (tree[z]) { - tree[z].push(edge); - } else { - tree[z] = [edge]; - } - - maxLevel = z > maxLevel ? z : maxLevel; - - return tree; - }, {}); - - const edgeTree = Object.entries(levelLookup).map(([key, edges]) => { - const level = +key; - - return { - edges, - level, - isMaxLevel: level === maxLevel, - }; - }); - - if (edgeTree.length === 0) { - return defaultEdgeTree; } - return edgeTree; + return edge; } type IsEdgeVisibleParams = {