refactor(edges): simplify edge rendering

This commit is contained in:
moklick
2023-11-16 15:41:30 +01:00
parent b659443507
commit 08e61f3111
12 changed files with 275 additions and 318 deletions
@@ -26,6 +26,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
labelBgPadding, labelBgPadding,
labelBgBorderRadius, labelBgBorderRadius,
style, style,
zIndex,
source, source,
target, target,
isSelectable, isSelectable,
@@ -201,86 +202,88 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
}; };
return ( return (
<g <svg className="react-flow__edgewrapper" style={{ zIndex }}>
className={cc([ <g
'react-flow__edge', className={cc([
`react-flow__edge-${type}`, 'react-flow__edge',
className, `react-flow__edge-${type}`,
{ selected, animated, inactive, updating: updateHover }, className,
])} { selected, animated, inactive, updating: updateHover },
onClick={onEdgeClick} ])}
onDoubleClick={onEdgeDoubleClickHandler} onClick={onEdgeClick}
onContextMenu={onEdgeContextMenu} onDoubleClick={onEdgeDoubleClickHandler}
onMouseEnter={onEdgeMouseEnter} onContextMenu={onEdgeContextMenu}
onMouseMove={onEdgeMouseMove} onMouseEnter={onEdgeMouseEnter}
onMouseLeave={onEdgeMouseLeave} onMouseMove={onEdgeMouseMove}
onKeyDown={isFocusable ? onKeyDown : undefined} onMouseLeave={onEdgeMouseLeave}
tabIndex={isFocusable ? 0 : undefined} onKeyDown={isFocusable ? onKeyDown : undefined}
role={isFocusable ? 'button' : 'img'} tabIndex={isFocusable ? 0 : undefined}
data-id={id} role={isFocusable ? 'button' : 'img'}
data-testid={`rf__edge-${id}`} data-id={id}
aria-label={ariaLabel === null ? undefined : ariaLabel ? ariaLabel : `Edge from ${source} to ${target}`} data-testid={`rf__edge-${id}`}
aria-describedby={isFocusable ? `${ARIA_EDGE_DESC_KEY}-${rfId}` : undefined} aria-label={ariaLabel === null ? undefined : ariaLabel ? ariaLabel : `Edge from ${source} to ${target}`}
ref={edgeRef} aria-describedby={isFocusable ? `${ARIA_EDGE_DESC_KEY}-${rfId}` : undefined}
> ref={edgeRef}
{!updating && ( >
<EdgeComponent {!updating && (
id={id} <EdgeComponent
source={source} id={id}
target={target} source={source}
selected={selected} target={target}
animated={animated} selected={selected}
label={label} animated={animated}
labelStyle={labelStyle} label={label}
labelShowBg={labelShowBg} labelStyle={labelStyle}
labelBgStyle={labelBgStyle} labelShowBg={labelShowBg}
labelBgPadding={labelBgPadding} labelBgStyle={labelBgStyle}
labelBgBorderRadius={labelBgBorderRadius} labelBgPadding={labelBgPadding}
data={data} labelBgBorderRadius={labelBgBorderRadius}
style={style} data={data}
sourceX={edgePosition.sourceX} style={style}
sourceY={edgePosition.sourceY} sourceX={edgePosition.sourceX}
targetX={edgePosition.targetX} sourceY={edgePosition.sourceY}
targetY={edgePosition.targetY} targetX={edgePosition.targetX}
sourcePosition={edgePosition.sourcePosition} targetY={edgePosition.targetY}
targetPosition={edgePosition.targetPosition} sourcePosition={edgePosition.sourcePosition}
sourceHandleId={sourceHandleId} targetPosition={edgePosition.targetPosition}
targetHandleId={targetHandleId} sourceHandleId={sourceHandleId}
markerStart={markerStartUrl} targetHandleId={targetHandleId}
markerEnd={markerEndUrl} markerStart={markerStartUrl}
pathOptions={pathOptions} markerEnd={markerEndUrl}
interactionWidth={interactionWidth} pathOptions={pathOptions}
/> interactionWidth={interactionWidth}
)} />
{isUpdatable && ( )}
<> {isUpdatable && (
{(isUpdatable === 'source' || isUpdatable === true) && ( <>
<EdgeAnchor {(isUpdatable === 'source' || isUpdatable === true) && (
position={edgePosition.sourcePosition} <EdgeAnchor
centerX={edgePosition.sourceX} position={edgePosition.sourcePosition}
centerY={edgePosition.sourceY} centerX={edgePosition.sourceX}
radius={edgeUpdaterRadius} centerY={edgePosition.sourceY}
onMouseDown={onEdgeUpdaterSourceMouseDown} radius={edgeUpdaterRadius}
onMouseEnter={onEdgeUpdaterMouseEnter} onMouseDown={onEdgeUpdaterSourceMouseDown}
onMouseOut={onEdgeUpdaterMouseOut} onMouseEnter={onEdgeUpdaterMouseEnter}
type="source" onMouseOut={onEdgeUpdaterMouseOut}
/> type="source"
)} />
{(isUpdatable === 'target' || isUpdatable === true) && ( )}
<EdgeAnchor {(isUpdatable === 'target' || isUpdatable === true) && (
position={edgePosition.targetPosition} <EdgeAnchor
centerX={edgePosition.targetX} position={edgePosition.targetPosition}
centerY={edgePosition.targetY} centerX={edgePosition.targetX}
radius={edgeUpdaterRadius} centerY={edgePosition.targetY}
onMouseDown={onEdgeUpdaterTargetMouseDown} radius={edgeUpdaterRadius}
onMouseEnter={onEdgeUpdaterMouseEnter} onMouseDown={onEdgeUpdaterTargetMouseDown}
onMouseOut={onEdgeUpdaterMouseOut} onMouseEnter={onEdgeUpdaterMouseEnter}
type="target" onMouseOut={onEdgeUpdaterMouseOut}
/> type="target"
)} />
</> )}
)} </>
</g> )}
</g>
</svg>
); );
}; };
@@ -63,82 +63,76 @@ const EdgeRenderer = ({
children, children,
}: EdgeRendererProps) => { }: EdgeRendererProps) => {
const { edgesFocusable, edgesUpdatable, elementsSelectable, onError } = useStore(selector, shallow); 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 const edges = useVisibleEdges(onlyRenderVisibleElements, elevateEdgesOnSelect);
// each zIndex gets its own svg element
const edgeTree = useVisibleEdges(onlyRenderVisibleElements, elevateEdgesOnSelect);
return ( return (
<> <div className="react-flow__edges">
{edgeTree.map(({ level, edges, isMaxLevel }) => ( <svg className="react-flow__container">
<svg key={level} style={{ zIndex: level }} className="react-flow__edges react-flow__container"> <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={rfId} />
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={rfId} />} </svg>
<>
{edges.map((edge) => {
let edgeType = edge.type || 'default';
if (!edgeTypes[edgeType]) { {edges.map((edge) => {
onError?.('011', errorMessages['error011'](edgeType)); let edgeType = edge.type || 'default';
edgeType = 'default';
}
const EdgeComponent = edgeTypes[edgeType]; if (!edgeTypes[edgeType]) {
const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined')); onError?.('011', errorMessages['error011'](edgeType));
const isUpdatable = edgeType = 'default';
typeof onEdgeUpdate !== 'undefined' && }
(edge.updatable || (edgesUpdatable && typeof edge.updatable === 'undefined'));
const isSelectable = !!( const EdgeComponent = edgeTypes[edgeType];
edge.selectable || const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined'));
(elementsSelectable && typeof edge.selectable === 'undefined') const isUpdatable =
); typeof onEdgeUpdate !== 'undefined' &&
(edge.updatable || (edgesUpdatable && typeof edge.updatable === 'undefined'));
const isSelectable = !!(edge.selectable || (elementsSelectable && typeof edge.selectable === 'undefined'));
return (
<EdgeComponent
key={edge.id}
id={edge.id}
className={cc([edge.className, noPanClassName])}
type={edgeType}
data={edge.data}
selected={!!edge.selected}
animated={!!edge.animated}
hidden={!!edge.hidden}
label={edge.label}
labelStyle={edge.labelStyle}
labelShowBg={edge.labelShowBg}
labelBgStyle={edge.labelBgStyle}
labelBgPadding={edge.labelBgPadding}
labelBgBorderRadius={edge.labelBgBorderRadius}
style={edge.style}
zIndex={edge.zIndex}
source={edge.source}
target={edge.target}
sourceHandleId={edge.sourceHandle}
targetHandleId={edge.targetHandle}
markerEnd={edge.markerEnd}
markerStart={edge.markerStart}
isSelectable={isSelectable}
onEdgeUpdate={onEdgeUpdate}
onContextMenu={onEdgeContextMenu}
onMouseEnter={onEdgeMouseEnter}
onMouseMove={onEdgeMouseMove}
onMouseLeave={onEdgeMouseLeave}
onClick={onEdgeClick}
edgeUpdaterRadius={edgeUpdaterRadius}
onEdgeDoubleClick={onEdgeDoubleClick}
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
rfId={rfId}
ariaLabel={edge.ariaLabel}
isFocusable={isFocusable}
isUpdatable={isUpdatable}
pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined}
interactionWidth={edge.interactionWidth}
/>
);
})}
return (
<EdgeComponent
key={edge.id}
id={edge.id}
className={cc([edge.className, noPanClassName])}
type={edge.type}
data={edge.data}
selected={!!edge.selected}
animated={!!edge.animated}
hidden={!!edge.hidden}
label={edge.label}
labelStyle={edge.labelStyle}
labelShowBg={edge.labelShowBg}
labelBgStyle={edge.labelBgStyle}
labelBgPadding={edge.labelBgPadding}
labelBgBorderRadius={edge.labelBgBorderRadius}
style={edge.style}
source={edge.source}
target={edge.target}
sourceHandleId={edge.sourceHandle}
targetHandleId={edge.targetHandle}
markerEnd={edge.markerEnd}
markerStart={edge.markerStart}
isSelectable={isSelectable}
onEdgeUpdate={onEdgeUpdate}
onContextMenu={onEdgeContextMenu}
onMouseEnter={onEdgeMouseEnter}
onMouseMove={onEdgeMouseMove}
onMouseLeave={onEdgeMouseLeave}
onClick={onEdgeClick}
edgeUpdaterRadius={edgeUpdaterRadius}
onEdgeDoubleClick={onEdgeDoubleClick}
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
rfId={rfId}
ariaLabel={edge.ariaLabel}
isFocusable={isFocusable}
isUpdatable={isUpdatable}
pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined}
interactionWidth={edge.interactionWidth}
/>
);
})}
</>
</svg>
))}
{children} {children}
</> </div>
); );
}; };
+6 -15
View File
@@ -1,11 +1,11 @@
import { useCallback } from 'react'; 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 { useStore } from '../hooks/useStore';
import { Edge, type ReactFlowState } from '../types'; import { type Edge, type ReactFlowState } from '../types';
import { shallow } from 'zustand/shallow';
function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boolean): GroupedEdges<Edge>[] { function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boolean): Edge[] {
const edges = useStore( const edges = useStore(
useCallback( useCallback(
(s: ReactFlowState) => { (s: ReactFlowState) => {
@@ -29,20 +29,11 @@ function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boole
}) })
: s.edges; : s.edges;
return groupEdgesByZLevel(visibleEdges, s.nodeLookup, elevateEdgesOnSelect); return visibleEdges.map((edge) => adjustEdgeZIndex(edge, s.nodeLookup, elevateEdgesOnSelect));
}, },
[onlyRenderVisible, elevateEdgesOnSelect] [onlyRenderVisible, elevateEdgesOnSelect]
), ),
(groupA, groupB) => { shallow
const unEqual = groupA.some(
(item, index) =>
item.isMaxLevel !== groupB[index].isMaxLevel ||
item.level !== groupB[index].level ||
!shallow(item.edges, groupB[index].edges)
);
return !unEqual;
}
); );
return edges; return edges;
@@ -18,6 +18,7 @@
export let target: $$Props['target'] = ''; export let target: $$Props['target'] = '';
export let data: $$Props['data'] = {}; export let data: $$Props['data'] = {};
export let style: $$Props['style'] = undefined; export let style: $$Props['style'] = undefined;
export let zIndex: $$Props['zIndex'] = undefined;
export let animated: $$Props['animated'] = false; export let animated: $$Props['animated'] = false;
export let selected: $$Props['selected'] = false; export let selected: $$Props['selected'] = false;
@@ -95,42 +96,44 @@
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-noninteractive-element-interactions --> <!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
{#if !hidden} {#if !hidden}
<g <svg class="svelte-flow__edgewrapper" style:zIndex>
class={cc(['svelte-flow__edge', className])} <g
class:animated class={cc(['svelte-flow__edge', className])}
class:selected class:animated
data-id={id} class:selected
on:click={onClick} data-id={id}
on:contextmenu={onContextMenu} on:click={onClick}
aria-label={ariaLabel === null on:contextmenu={onContextMenu}
? undefined aria-label={ariaLabel === null
: ariaLabel ? undefined
? ariaLabel : ariaLabel
: `Edge from ${source} to ${target}`} ? ariaLabel
role="img" : `Edge from ${source} to ${target}`}
> role="img"
<svelte:component >
this={edgeComponent} <svelte:component
{id} this={edgeComponent}
{source} {id}
{target} {source}
{sourceX} {target}
{sourceY} {sourceX}
{targetX} {sourceY}
{targetY} {targetX}
{sourcePosition} {targetY}
{targetPosition} {sourcePosition}
{animated} {targetPosition}
{selected} {animated}
{label} {selected}
{labelStyle} {label}
{data} {labelStyle}
{style} {data}
{interactionWidth} {style}
sourceHandleId={sourceHandle} {interactionWidth}
targetHandleId={targetHandle} sourceHandleId={sourceHandle}
markerStart={markerStartUrl} targetHandleId={targetHandle}
markerEnd={markerEndUrl} markerStart={markerStartUrl}
/> markerEnd={markerEndUrl}
</g> />
</g>
</svg>
{/if} {/if}
@@ -9,7 +9,7 @@
const { const {
elementsSelectable, elementsSelectable,
edgeTree, visibleEdges,
edges: { setDefaultOptions } edges: { setDefaultOptions }
} = useStore(); } = useStore();
@@ -18,57 +18,47 @@
}); });
</script> </script>
{#each $edgeTree as group (group.level)} <div class="svelte-flow__edges">
<svg style="z-index: {group.level}" class="svelte-flow__edges"> <svg class="svelte-flow__container">
{#if group.isMaxLevel} <MarkerDefinition />{/if} <MarkerDefinition />
<g>
{#each group.edges as edge (edge.id)}
{@const edgeType = edge.type || 'default'}
{@const selectable = !!(
edge.selectable ||
($elementsSelectable && typeof edge.selectable === 'undefined')
)}
<EdgeWrapper
id={edge.id}
source={edge.source}
target={edge.target}
data={edge.data}
style={edge.style}
animated={edge.animated}
selected={edge.selected}
hidden={edge.hidden}
label={edge.label}
labelStyle={edge.labelStyle}
markerStart={edge.markerStart}
markerEnd={edge.markerEnd}
sourceHandle={edge.sourceHandle}
targetHandle={edge.targetHandle}
sourceX={edge.sourceX}
sourceY={edge.sourceY}
targetX={edge.targetX}
targetY={edge.targetY}
sourcePosition={edge.sourcePosition}
targetPosition={edge.targetPosition}
ariaLabel={edge.ariaLabel}
interactionWidth={edge.interactionWidth}
class={edge.class}
type={edgeType}
{selectable}
on:edgeclick
on:edgecontextmenu
/>
{/each}
</g>
</svg> </svg>
{/each}
<style> {#each $visibleEdges as edge (edge.id)}
.svelte-flow__edges { {@const edgeType = edge.type || 'default'}
width: 100%; {@const selectable = !!(
height: 100%; edge.selectable ||
position: absolute; ($elementsSelectable && typeof edge.selectable === 'undefined')
top: 0; )}
left: 0;
} <EdgeWrapper
</style> id={edge.id}
source={edge.source}
target={edge.target}
data={edge.data}
style={edge.style}
animated={edge.animated}
selected={edge.selected}
hidden={edge.hidden}
label={edge.label}
labelStyle={edge.labelStyle}
markerStart={edge.markerStart}
markerEnd={edge.markerEnd}
sourceHandle={edge.sourceHandle}
targetHandle={edge.targetHandle}
sourceX={edge.sourceX}
sourceY={edge.sourceY}
targetX={edge.targetX}
targetY={edge.targetY}
sourcePosition={edge.sourcePosition}
targetPosition={edge.targetPosition}
ariaLabel={edge.ariaLabel}
interactionWidth={edge.interactionWidth}
class={edge.class}
type={edgeType}
zIndex={edge.zIndex}
{selectable}
on:edgeclick
on:edgecontextmenu
/>
{/each}
</div>
+2 -2
View File
@@ -24,7 +24,7 @@ import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions, ConnectionData }
import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store'; import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store';
import type { SvelteFlowStore } from './types'; import type { SvelteFlowStore } from './types';
import { syncNodeStores, syncEdgeStores, syncViewportStores } from './utils'; import { syncNodeStores, syncEdgeStores, syncViewportStores } from './utils';
import { getEdgeTree } from './edge-tree'; import { getVisibleEdges } from './visible-edges';
import { getVisibleNodes } from './visible-nodes'; import { getVisibleNodes } from './visible-nodes';
import { getDerivedConnectionProps } from './derived-connection-props'; import { getDerivedConnectionProps } from './derived-connection-props';
@@ -349,8 +349,8 @@ export function createStore({
...store, ...store,
// derived state // derived state
edgeTree: getEdgeTree(store),
connection: getDerivedConnectionProps(store, currentConnection), connection: getDerivedConnectionProps(store, currentConnection),
visibleEdges: getVisibleEdges(store),
visibleNodes: getVisibleNodes(store), visibleNodes: getVisibleNodes(store),
markers: derived( markers: derived(
[store.edges, store.defaultMarkerColor, store.flowId], [store.edges, store.defaultMarkerColor, store.flowId],
@@ -91,7 +91,7 @@ export const getInitialStore = ({
nodeLookup: readable<Map<string, Node>>(nodeLookup), nodeLookup: readable<Map<string, Node>>(nodeLookup),
visibleNodes: readable<Node[]>([]), visibleNodes: readable<Node[]>([]),
edges: createEdgesStore(edges), edges: createEdgesStore(edges),
edgeTree: readable<GroupedEdges<EdgeLayouted>[]>([]), visibleEdges: readable<EdgeLayouted[]>([]),
height: writable<number>(500), height: writable<number>(500),
width: writable<number>(500), width: writable<number>(500),
minZoom: writable<number>(0.5), minZoom: writable<number>(0.5),
@@ -1,10 +1,10 @@
import { derived } from 'svelte/store'; 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 { EdgeLayouted } from '$lib/types';
import type { SvelteFlowStoreState } from './types'; import type { SvelteFlowStoreState } from './types';
export function getEdgeTree(store: SvelteFlowStoreState) { export function getVisibleEdges(store: SvelteFlowStoreState) {
const visibleEdges = derived( const visibleEdges = derived(
[ [
store.edges, store.edges,
@@ -41,8 +41,15 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
); );
return derived( 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<EdgeLayouted[]>((res, edge) => { const layoutedEdges = visibleEdges.reduce<EdgeLayouted[]>((res, edge) => {
const sourceNode = nodeLookup.get(edge.source); const sourceNode = nodeLookup.get(edge.source);
const targetNode = nodeLookup.get(edge.target); const targetNode = nodeLookup.get(edge.target);
@@ -63,7 +70,7 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
if (edgePosition) { if (edgePosition) {
res.push({ res.push({
...edge, ...adjustEdgeZIndex(edge, nodeLookup, onlyRenderVisibleElements),
...edgePosition ...edgePosition
}); });
} }
@@ -71,9 +78,7 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
return res; return res;
}, []); }, []);
const groupedEdges = groupEdgesByZLevel<EdgeLayouted>(layoutedEdges, nodeLookup, false); return layoutedEdges;
return groupedEdges;
} }
); );
} }
+1
View File
@@ -70,6 +70,7 @@ export type EdgeLayouted = Pick<
| 'ariaLabel' | 'ariaLabel'
| 'hidden' | 'hidden'
| 'class' | 'class'
| 'zIndex'
> & > &
EdgePosition & { EdgePosition & {
sourceNode?: Node; sourceNode?: Node;
-5
View File
@@ -57,11 +57,6 @@
outline: none; outline: none;
} }
.xy-flow .xy-flow__edges {
pointer-events: none;
overflow: visible;
}
.xy-flow__edge-path { .xy-flow__edge-path {
stroke: var(--edge-stroke, var(--edge-stroke-default)); stroke: var(--edge-stroke, var(--edge-stroke-default));
stroke-width: var(--edge-stroke-width, var(--edge-stroke-width-default)); stroke-width: var(--edge-stroke-width, var(--edge-stroke-width-default));
+6
View File
@@ -20,6 +20,12 @@
--controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08); --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 { .xy-flow__edge {
&.updating { &.updating {
.xy-flow__edge-path { .xy-flow__edge-path {
+15 -46
View File
@@ -23,63 +23,32 @@ export function getEdgeCenter({
return [centerX, centerY, xOffset, yOffset]; return [centerX, centerY, xOffset, yOffset];
} }
const defaultEdgeTree = [{ level: 0, isMaxLevel: true, edges: [] }];
export type GroupedEdges<EdgeType extends EdgeBase> = { export type GroupedEdges<EdgeType extends EdgeBase> = {
edges: EdgeType[]; edges: EdgeType[];
level: number; level: number;
isMaxLevel: boolean; isMaxLevel: boolean;
}; };
export function groupEdgesByZLevel<EdgeType extends EdgeBase>( export function adjustEdgeZIndex(edge: EdgeBase, nodeLookup: Map<string, NodeBase>, elevateEdgesOnSelect: boolean) {
edges: EdgeType[], const hasZIndex = isNumeric(edge.zIndex);
nodeLookup: Map<string, NodeBase>, let z = hasZIndex ? edge.zIndex! : 0;
elevateEdgesOnSelect = false
): GroupedEdges<EdgeType>[] {
let maxLevel = -1;
const levelLookup = edges.reduce<Record<string, EdgeType[]>>((tree, edge) => { if (elevateEdgesOnSelect) {
const hasZIndex = isNumeric(edge.zIndex); const targetNode = nodeLookup.get(edge.target)!;
let z = hasZIndex ? edge.zIndex! : 0; 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) { if (edge.zIndex !== z) {
const targetNode = nodeLookup.get(edge.target); return {
const sourceNode = nodeLookup.get(edge.source); ...edge,
const edgeOrConnectedNodeSelected = edge.selected || targetNode?.selected || sourceNode?.selected; zIndex: z,
const selectedZIndex = Math.max( };
sourceNode?.[internalsSymbol]?.z || 0,
targetNode?.[internalsSymbol]?.z || 0,
1000
);
z = (hasZIndex ? edge.zIndex! : 0) + (edgeOrConnectedNodeSelected ? selectedZIndex : 0);
} }
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 = { type IsEdgeVisibleParams = {