refactor(edges): optimize edge zIndex calculation

This commit is contained in:
moklick
2023-12-17 11:56:21 +01:00
parent c3834208d2
commit 2bc02547f6
6 changed files with 58 additions and 45 deletions
@@ -8,13 +8,14 @@ import {
type Connection, type Connection,
getEdgePosition, getEdgePosition,
errorMessages, errorMessages,
getEdgeZIndex,
} from '@xyflow/system'; } from '@xyflow/system';
import { useStoreApi, useStore } from '../../hooks/useStore'; import { useStoreApi, useStore } from '../../hooks/useStore';
import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions'; import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions';
import { EdgeAnchor } from '../Edges/EdgeAnchor'; import { EdgeAnchor } from '../Edges/EdgeAnchor';
import { getMouseHandler } from '../Edges/utils'; import { getMouseHandler } from '../Edges/utils';
import type { EdgeWrapperProps } from '../../types'; import type { EdgeWrapperProps, Node } from '../../types';
import { builtinEdgeTypes } from './utils'; import { builtinEdgeTypes } from './utils';
function EdgeWrapper({ function EdgeWrapper({
@@ -56,7 +57,8 @@ function EdgeWrapper({
pathOptions, pathOptions,
interactionWidth, interactionWidth,
edgeTypes, edgeTypes,
zIndex, zIndex: edgeZIndex,
elevateEdgesOnSelect,
onError, onError,
}: EdgeWrapperProps): JSX.Element | null { }: EdgeWrapperProps): JSX.Element | null {
let edgeType = type || 'default'; let edgeType = type || 'default';
@@ -72,27 +74,45 @@ function EdgeWrapper({
const [updateHover, setUpdateHover] = useState<boolean>(false); const [updateHover, setUpdateHover] = useState<boolean>(false);
const [updating, setUpdating] = useState<boolean>(false); const [updating, setUpdating] = useState<boolean>(false);
const store = useStoreApi(); const store = useStoreApi();
const edgePosition = useStore( const prevSourceNode = useRef<Node | undefined>();
const prevTargetNode = useRef<Node | undefined>();
const prevZIndex = useRef<number | undefined>(edgeZIndex);
const prevEdgePosition = useRef<ReturnType<typeof getEdgePosition> | null>(null);
const { edgePosition, zIndex } = useStore(
useCallback( useCallback(
(state) => { (state) => {
const sourceNode = state.nodeLookup.get(source); const sourceNode = state.nodeLookup.get(source);
const targetNode = state.nodeLookup.get(target); const targetNode = state.nodeLookup.get(target);
if (!sourceNode || !targetNode) { if (!sourceNode || !targetNode) {
return null; return { edgePosition: null, zIndex: edgeZIndex };
} }
return getEdgePosition({ const nodesChanged = prevSourceNode.current !== sourceNode || prevTargetNode.current !== targetNode;
id,
sourceNode, prevSourceNode.current = sourceNode;
targetNode, prevTargetNode.current = targetNode;
sourceHandle: sourceHandleId || null,
targetHandle: targetHandleId || null, prevEdgePosition.current = nodesChanged
connectionMode: state.connectionMode, ? getEdgePosition({
onError: state.onError, id,
}); sourceNode,
targetNode,
sourceHandle: sourceHandleId || null,
targetHandle: targetHandleId || null,
connectionMode: state.connectionMode,
onError: state.onError,
})
: prevEdgePosition.current;
prevZIndex.current = getEdgeZIndex(selected, edgeZIndex, sourceNode, targetNode, elevateEdgesOnSelect);
return {
edgePosition: prevEdgePosition.current,
zIndex: prevZIndex.current,
};
}, },
[source, target] [source, target, selected, edgeZIndex]
), ),
shallow shallow
); );
@@ -63,7 +63,7 @@ const EdgeRenderer = ({
children, children,
}: EdgeRendererProps) => { }: EdgeRendererProps) => {
const { edgesFocusable, edgesUpdatable, elementsSelectable, onError } = useStore(selector, shallow); const { edgesFocusable, edgesUpdatable, elementsSelectable, onError } = useStore(selector, shallow);
const edges = useVisibleEdges(onlyRenderVisibleElements, elevateEdgesOnSelect); const edges = useVisibleEdges(onlyRenderVisibleElements);
return ( return (
<div className="react-flow__edges"> <div className="react-flow__edges">
@@ -121,6 +121,7 @@ const EdgeRenderer = ({
interactionWidth={edge.interactionWidth} interactionWidth={edge.interactionWidth}
onError={onError} onError={onError}
edgeTypes={edgeTypes} edgeTypes={edgeTypes}
elevateEdgesOnSelect={elevateEdgesOnSelect}
/> />
); );
})} })}
+4 -4
View File
@@ -1,11 +1,11 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { shallow } from 'zustand/shallow'; import { shallow } from 'zustand/shallow';
import { adjustEdgeZIndex, isEdgeVisible } from '@xyflow/system'; import { isEdgeVisible } from '@xyflow/system';
import { useStore } from '../hooks/useStore'; import { useStore } from '../hooks/useStore';
import { type Edge, type ReactFlowState } from '../types'; import { type Edge, type ReactFlowState } from '../types';
function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boolean): Edge[] { function useVisibleEdges(onlyRenderVisible: boolean): Edge[] {
const edges = useStore( const edges = useStore(
useCallback( useCallback(
(s: ReactFlowState) => { (s: ReactFlowState) => {
@@ -29,9 +29,9 @@ function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boole
}) })
: s.edges; : s.edges;
return visibleEdges.map((edge) => adjustEdgeZIndex(edge, s.nodeLookup, elevateEdgesOnSelect)); return visibleEdges;
}, },
[onlyRenderVisible, elevateEdgesOnSelect] [onlyRenderVisible]
), ),
shallow shallow
); );
+1
View File
@@ -78,6 +78,7 @@ export type EdgeWrapperProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHa
pathOptions?: BezierPathOptions | SmoothStepPathOptions; pathOptions?: BezierPathOptions | SmoothStepPathOptions;
edgeTypes?: EdgeTypes; edgeTypes?: EdgeTypes;
onError?: OnError; onError?: OnError;
elevateEdgesOnSelect?: boolean;
}; };
export type DefaultEdgeOptions = DefaultEdgeOptionsBase<Edge>; export type DefaultEdgeOptions = DefaultEdgeOptionsBase<Edge>;
+5 -11
View File
@@ -1,5 +1,5 @@
import { derived } from 'svelte/store'; import { derived } from 'svelte/store';
import { isEdgeVisible, getEdgePosition, adjustEdgeZIndex } from '@xyflow/system'; import { isEdgeVisible, getEdgePosition, getEdgeZIndex } 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';
@@ -41,15 +41,8 @@ export function getVisibleEdges(store: SvelteFlowStoreState) {
); );
return derived( return derived(
[ [visibleEdges, store.nodes, store.nodeLookup, store.connectionMode, store.onerror],
visibleEdges, ([visibleEdges, , nodeLookup, connectionMode, onerror]) => {
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);
@@ -70,7 +63,8 @@ export function getVisibleEdges(store: SvelteFlowStoreState) {
if (edgePosition) { if (edgePosition) {
res.push({ res.push({
...adjustEdgeZIndex(edge, nodeLookup, onlyRenderVisibleElements), ...edge,
zIndex: getEdgeZIndex(edge.selected, edge.zIndex, sourceNode, targetNode, false),
...edgePosition ...edgePosition
}); });
} }
+12 -15
View File
@@ -23,26 +23,23 @@ export function getEdgeCenter({
return [centerX, centerY, xOffset, yOffset]; return [centerX, centerY, xOffset, yOffset];
} }
export function adjustEdgeZIndex(edge: EdgeBase, nodeLookup: Map<string, NodeBase>, elevateEdgesOnSelect: boolean) { export function getEdgeZIndex(
const hasZIndex = isNumeric(edge.zIndex); selected: boolean | undefined,
let z = hasZIndex ? edge.zIndex! : 0; zIndex: number | undefined,
sourceNode: NodeBase,
targetNode: NodeBase,
elevateEdgesOnSelect?: boolean
) {
const hasZIndex = isNumeric(zIndex);
let nextZIndex = hasZIndex ? zIndex! : 0;
if (elevateEdgesOnSelect) { if (elevateEdgesOnSelect) {
const targetNode = nodeLookup.get(edge.target)!; const edgeOrConnectedNodeSelected = selected || targetNode?.selected || sourceNode?.selected;
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); const selectedZIndex = Math.max(sourceNode?.[internalsSymbol]?.z || 0, targetNode?.[internalsSymbol]?.z || 0, 1000);
z = (hasZIndex ? edge.zIndex! : 0) + (edgeOrConnectedNodeSelected ? selectedZIndex : 0); nextZIndex = (hasZIndex ? zIndex! : 0) + (edgeOrConnectedNodeSelected ? selectedZIndex : 0);
if (edge.zIndex !== z) {
return {
...edge,
zIndex: z,
};
}
} }
return edge; return nextZIndex;
} }
type IsEdgeVisibleParams = { type IsEdgeVisibleParams = {