Reworked edge rerendering

This commit is contained in:
peterkogo
2024-12-11 15:22:44 +01:00
parent 1c67bf848f
commit 24735aa003
8 changed files with 79 additions and 124 deletions

View File

@@ -48,7 +48,7 @@
}
let nodes = $state.raw(nodeItems);
let edges = $state.raw([]);
let edges = $state.raw(edgeItems);
</script>
<SvelteFlow bind:nodes bind:edges fitView minZoom={0.2}>

View File

@@ -1,4 +1,3 @@
import { get } from 'svelte/store';
import { XYDrag, type OnDrag } from '@xyflow/system';
import type { SvelteFlowStore } from '$lib/store/types';

View File

@@ -11,51 +11,55 @@
import type { SvelteFlowStore } from '$lib/store/types';
const {
id,
type = 'default',
edge,
store,
onedgeclick,
onedgecontextmenu,
onedgemouseenter,
onedgemouseleave
}: { store: SvelteFlowStore; edge: EdgeLayouted } & EdgeEvents = $props();
let {
source,
target,
data = {},
style,
zIndex,
animated = false,
selected = false,
selectable,
deletable,
hidden,
label,
labelStyle,
markerStart,
markerEnd,
sourceHandle,
targetHandle,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
ariaLabel,
animated,
selected,
label,
labelStyle,
data = {},
style,
interactionWidth,
type = 'default',
sourceHandle,
targetHandle,
markerStart,
markerEnd,
selectable: edgeSelectable,
deletable,
hidden,
zIndex,
class: className,
onedgeclick,
onedgecontextmenu,
onedgemouseenter,
onedgemouseleave
}: { store: SvelteFlowStore } & EdgeLayouted & EdgeEvents = $props();
ariaLabel
} = $derived(edge);
const { id } = edge;
setContext('svelteflow__edge_id', id);
let edgeType = $derived(type ?? 'default');
let EdgeComponent = $derived(store.edgeTypes[edgeType] ?? BezierEdgeInternal);
let selectable = $derived(edgeSelectable ?? store.elementsSelectable);
let EdgeComponent = $derived(store.edgeTypes[type] ?? BezierEdgeInternal);
let markerStartUrl = $derived(
markerStart ? `url('#${getMarkerId(markerStart, store.flowId)}')` : undefined
);
let markerEndUrl = $derived(
markerEnd ? `url('#${getMarkerId(markerEnd, store.flowId)}')` : undefined
);
let isSelectable = $derived(selectable ?? store.elementsSelectable);
const handleEdgeSelect = useHandleEdgeSelect();
@@ -88,7 +92,7 @@
class={cc(['svelte-flow__edge', className])}
class:animated
class:selected
class:selectable={isSelectable}
class:selectable
data-id={id}
{onclick}
oncontextmenu={onedgecontextmenu
@@ -130,9 +134,9 @@
{data}
{style}
{interactionWidth}
selectable={isSelectable}
{selectable}
deletable={deletable ?? true}
type={edgeType}
{type}
sourceHandleId={sourceHandle}
targetHandleId={targetHandle}
markerStart={markerStartUrl}

View File

@@ -4,9 +4,10 @@
import { errorMessages, nodeHasDimensions, Position } from '@xyflow/system';
import drag from '$lib/actions/drag';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
import type { ConnectableContext, NodeWrapperProps } from './types';
import { getNodeInlineStyleDimensions } from './utils';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
import type { ConnectableContext, NodeWrapperProps } from './types';
import type { Node, NodeEvents } from '$lib/types';
let {
@@ -25,7 +26,6 @@
}: NodeWrapperProps & NodeEvents = $props();
let {
id,
data = {},
selected = false,
draggable: _draggable,
@@ -48,18 +48,16 @@
dragHandle
} = $derived(node);
let { id } = node;
let draggable = $derived(_draggable ?? store.nodesDraggable);
let selectable = $derived(_selectable ?? store.elementsSelectable);
let connectable = $derived(_connectable ?? store.nodesConnectable);
let initialized = $derived(nodeHasDimensions(node));
// TODO: does this make sense
let flipFlop = false;
let shouldRerenderSignal = $derived.by(() =>
store.adoptNodes.has(id) ? (flipFlop = !flipFlop) : flipFlop
);
function getInternalNode(node: Node, shouldRerender: boolean = false) {
// we also pass store.nodes to this function to rerender when a node changes
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function getInternalNode(node: Node, nodes: Node[]) {
return { ...store.nodeLookup.get(node.id)! };
}
@@ -68,7 +66,7 @@
z: zIndex = 0,
positionAbsolute: { x: positionX, y: positionY }
}
} = $derived(getInternalNode(node, shouldRerenderSignal));
} = $derived(getInternalNode(node, store.nodes));
function isInParentLookup(id: string) {
return store.parentLookup.has(id);

View File

@@ -1,45 +1,13 @@
import type { SvelteFlowStore } from '$lib/store/types';
import type { InternalNode, Node } from '$lib/types';
import type { Node } from '$lib/types';
export type ConnectableContext = {
value: boolean;
};
export type NodeWrapperProps = {
// Pick<
// Node,
// | 'id'
// | 'class'
// | 'connectable'
// | 'data'
// | 'draggable'
// | 'dragging'
// | 'selected'
// | 'selectable'
// | 'deletable'
// | 'style'
// | 'type'
// | 'sourcePosition'
// | 'targetPosition'
// | 'dragHandle'
// | 'hidden'
// | 'width'
// | 'height'
// | 'initialWidth'
// | 'initialHeight'
// | 'parentId'
// > & {
// measuredWidth?: number;
// measuredHeight?: number;
// type: string;
// positionX: number;
// positionY: number;
// 'on:nodeclick'?: (event: MouseEvent) => void;
// isParent?: boolean;
// zIndex: number;
// initialized: boolean;
node: Node;
store: SvelteFlowStore;
nodeClickDistance?: number;
resizeObserver?: ResizeObserver | null;
store: SvelteFlowStore;
};

View File

@@ -14,16 +14,26 @@
onedgemouseleave
}: { store: SvelteFlowStore } & EdgeEvents = $props();
// const { edges, nodes } = store;
let previousLayoutedEdges = new Map<string, EdgeLayouted>();
let layoutedEdges = $derived.by(() => {
store.nodes;
const layoutedEdges = store.edges.reduce<EdgeLayouted[]>((res, edge) => {
const layoutedEdges = new Map<string, EdgeLayouted>();
for (let edge of store.edges) {
const sourceNode = store.nodeLookup.get(edge.source);
const targetNode = store.nodeLookup.get(edge.target);
if (!sourceNode || !targetNode) {
return res;
continue;
}
// we reuse the previous edge object if the source and target node are the same as before
// references to internalNodes that haven't changed stay the same
const previous = previousLayoutedEdges.get(edge.id);
if (previous && sourceNode == previous.sourceNode && targetNode == previous.targetNode) {
layoutedEdges.set(edge.id, previous);
continue;
}
const edgePosition = getEdgePosition({
@@ -37,7 +47,7 @@
});
if (edgePosition) {
res.push({
layoutedEdges.set(edge.id, {
...edge,
zIndex: getElevatedEdgeZIndex({
selected: edge.selected,
@@ -46,13 +56,14 @@
targetNode,
elevateOnSelect: false
}),
...edgePosition
...edgePosition,
sourceNode,
targetNode
});
}
}
return res;
}, []);
previousLayoutedEdges = layoutedEdges;
return layoutedEdges;
});
</script>
@@ -62,36 +73,10 @@
<MarkerDefinition />
</svg>
{#each layoutedEdges as edge (edge.id)}
{#each layoutedEdges.values() as edge (edge.id)}
<EdgeWrapper
{store}
id={edge.id}
source={edge.source}
target={edge.target}
data={edge.data}
style={edge.style}
animated={edge.animated}
selected={edge.selected}
selectable={edge.selectable ?? store.elementsSelectable}
deletable={edge.deletable}
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={edge.type || 'default'}
zIndex={edge.zIndex}
{edge}
{onedgeclick}
{onedgecontextmenu}
{onedgemouseenter}
@@ -99,7 +84,7 @@
/>
{/each}
{#if layoutedEdges.length > 0}
{#if layoutedEdges.size > 0}
<CallOnMount
onMount={() => {
store.edgesInitialized = true;

View File

@@ -2,6 +2,7 @@
import { onDestroy } from 'svelte';
import { NodeWrapper } from '$lib/components/NodeWrapper';
import type { NodeEvents } from '$lib/types';
import type { SvelteFlowStore } from '$lib/store/types';

View File

@@ -93,7 +93,6 @@ export function adoptUserNodes<NodeType extends NodeBase>(
nodeLookup.clear();
parentLookup.clear();
const updatedChildNodes = new Set<string>();
for (const userNode of nodes) {
let internalNode = tmpLookup.get(userNode.id);
@@ -124,10 +123,9 @@ export function adoptUserNodes<NodeType extends NodeBase>(
}
if (userNode.parentId) {
updateChildNode(internalNode, nodeLookup, parentLookup, options, updatedChildNodes);
updateChildNode(internalNode, nodeLookup, parentLookup, options);
}
}
return updatedChildNodes;
}
function updateParentLookup<NodeType extends NodeBase>(
@@ -154,8 +152,7 @@ function updateChildNode<NodeType extends NodeBase>(
node: InternalNodeBase<NodeType>,
nodeLookup: NodeLookup<InternalNodeBase<NodeType>>,
parentLookup: ParentLookup<InternalNodeBase<NodeType>>,
options?: UpdateNodesOptions<NodeType>,
updatedChildNodes?: Set<string>
options?: UpdateNodesOptions<NodeType>
) {
const { elevateNodesOnSelect, nodeOrigin, nodeExtent } = mergeObjects(defaultOptions, options);
const parentId = node.parentId!;
@@ -176,12 +173,15 @@ function updateChildNode<NodeType extends NodeBase>(
const positionChanged = x !== positionAbsolute.x || y !== positionAbsolute.y;
if (positionChanged || z !== node.internals.z) {
node.internals = {
...node.internals,
positionAbsolute: positionChanged ? { x, y } : positionAbsolute,
z,
};
updatedChildNodes?.add(node.id);
// we create a new object to mark the node as updated
nodeLookup.set(node.id, {
...node,
internals: {
...node.internals,
positionAbsolute: positionChanged ? { x, y } : positionAbsolute,
z,
},
});
}
}