Reworked edge rerendering
This commit is contained in:
@@ -48,7 +48,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let nodes = $state.raw(nodeItems);
|
let nodes = $state.raw(nodeItems);
|
||||||
let edges = $state.raw([]);
|
let edges = $state.raw(edgeItems);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SvelteFlow bind:nodes bind:edges fitView minZoom={0.2}>
|
<SvelteFlow bind:nodes bind:edges fitView minZoom={0.2}>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { get } from 'svelte/store';
|
|
||||||
import { XYDrag, type OnDrag } from '@xyflow/system';
|
import { XYDrag, type OnDrag } from '@xyflow/system';
|
||||||
|
|
||||||
import type { SvelteFlowStore } from '$lib/store/types';
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
|
|||||||
@@ -11,51 +11,55 @@
|
|||||||
import type { SvelteFlowStore } from '$lib/store/types';
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
id,
|
edge,
|
||||||
type = 'default',
|
|
||||||
store,
|
store,
|
||||||
|
onedgeclick,
|
||||||
|
onedgecontextmenu,
|
||||||
|
onedgemouseenter,
|
||||||
|
onedgemouseleave
|
||||||
|
}: { store: SvelteFlowStore; edge: EdgeLayouted } & EdgeEvents = $props();
|
||||||
|
|
||||||
|
let {
|
||||||
source,
|
source,
|
||||||
target,
|
target,
|
||||||
data = {},
|
|
||||||
style,
|
|
||||||
zIndex,
|
|
||||||
animated = false,
|
|
||||||
selected = false,
|
|
||||||
selectable,
|
|
||||||
deletable,
|
|
||||||
hidden,
|
|
||||||
label,
|
|
||||||
labelStyle,
|
|
||||||
markerStart,
|
|
||||||
markerEnd,
|
|
||||||
sourceHandle,
|
|
||||||
targetHandle,
|
|
||||||
sourceX,
|
sourceX,
|
||||||
sourceY,
|
sourceY,
|
||||||
targetX,
|
targetX,
|
||||||
targetY,
|
targetY,
|
||||||
sourcePosition,
|
sourcePosition,
|
||||||
targetPosition,
|
targetPosition,
|
||||||
ariaLabel,
|
animated,
|
||||||
|
selected,
|
||||||
|
label,
|
||||||
|
labelStyle,
|
||||||
|
data = {},
|
||||||
|
style,
|
||||||
interactionWidth,
|
interactionWidth,
|
||||||
|
type = 'default',
|
||||||
|
sourceHandle,
|
||||||
|
targetHandle,
|
||||||
|
markerStart,
|
||||||
|
markerEnd,
|
||||||
|
selectable: edgeSelectable,
|
||||||
|
deletable,
|
||||||
|
hidden,
|
||||||
|
zIndex,
|
||||||
class: className,
|
class: className,
|
||||||
onedgeclick,
|
ariaLabel
|
||||||
onedgecontextmenu,
|
} = $derived(edge);
|
||||||
onedgemouseenter,
|
|
||||||
onedgemouseleave
|
|
||||||
}: { store: SvelteFlowStore } & EdgeLayouted & EdgeEvents = $props();
|
|
||||||
|
|
||||||
|
const { id } = edge;
|
||||||
setContext('svelteflow__edge_id', id);
|
setContext('svelteflow__edge_id', id);
|
||||||
|
|
||||||
let edgeType = $derived(type ?? 'default');
|
let selectable = $derived(edgeSelectable ?? store.elementsSelectable);
|
||||||
let EdgeComponent = $derived(store.edgeTypes[edgeType] ?? BezierEdgeInternal);
|
let EdgeComponent = $derived(store.edgeTypes[type] ?? BezierEdgeInternal);
|
||||||
|
|
||||||
let markerStartUrl = $derived(
|
let markerStartUrl = $derived(
|
||||||
markerStart ? `url('#${getMarkerId(markerStart, store.flowId)}')` : undefined
|
markerStart ? `url('#${getMarkerId(markerStart, store.flowId)}')` : undefined
|
||||||
);
|
);
|
||||||
let markerEndUrl = $derived(
|
let markerEndUrl = $derived(
|
||||||
markerEnd ? `url('#${getMarkerId(markerEnd, store.flowId)}')` : undefined
|
markerEnd ? `url('#${getMarkerId(markerEnd, store.flowId)}')` : undefined
|
||||||
);
|
);
|
||||||
let isSelectable = $derived(selectable ?? store.elementsSelectable);
|
|
||||||
|
|
||||||
const handleEdgeSelect = useHandleEdgeSelect();
|
const handleEdgeSelect = useHandleEdgeSelect();
|
||||||
|
|
||||||
@@ -88,7 +92,7 @@
|
|||||||
class={cc(['svelte-flow__edge', className])}
|
class={cc(['svelte-flow__edge', className])}
|
||||||
class:animated
|
class:animated
|
||||||
class:selected
|
class:selected
|
||||||
class:selectable={isSelectable}
|
class:selectable
|
||||||
data-id={id}
|
data-id={id}
|
||||||
{onclick}
|
{onclick}
|
||||||
oncontextmenu={onedgecontextmenu
|
oncontextmenu={onedgecontextmenu
|
||||||
@@ -130,9 +134,9 @@
|
|||||||
{data}
|
{data}
|
||||||
{style}
|
{style}
|
||||||
{interactionWidth}
|
{interactionWidth}
|
||||||
selectable={isSelectable}
|
{selectable}
|
||||||
deletable={deletable ?? true}
|
deletable={deletable ?? true}
|
||||||
type={edgeType}
|
{type}
|
||||||
sourceHandleId={sourceHandle}
|
sourceHandleId={sourceHandle}
|
||||||
targetHandleId={targetHandle}
|
targetHandleId={targetHandle}
|
||||||
markerStart={markerStartUrl}
|
markerStart={markerStartUrl}
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
import { errorMessages, nodeHasDimensions, Position } from '@xyflow/system';
|
import { errorMessages, nodeHasDimensions, Position } from '@xyflow/system';
|
||||||
|
|
||||||
import drag from '$lib/actions/drag';
|
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 { getNodeInlineStyleDimensions } from './utils';
|
||||||
|
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
||||||
|
|
||||||
|
import type { ConnectableContext, NodeWrapperProps } from './types';
|
||||||
import type { Node, NodeEvents } from '$lib/types';
|
import type { Node, NodeEvents } from '$lib/types';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -25,7 +26,6 @@
|
|||||||
}: NodeWrapperProps & NodeEvents = $props();
|
}: NodeWrapperProps & NodeEvents = $props();
|
||||||
|
|
||||||
let {
|
let {
|
||||||
id,
|
|
||||||
data = {},
|
data = {},
|
||||||
selected = false,
|
selected = false,
|
||||||
draggable: _draggable,
|
draggable: _draggable,
|
||||||
@@ -48,18 +48,16 @@
|
|||||||
dragHandle
|
dragHandle
|
||||||
} = $derived(node);
|
} = $derived(node);
|
||||||
|
|
||||||
|
let { id } = node;
|
||||||
|
|
||||||
let draggable = $derived(_draggable ?? store.nodesDraggable);
|
let draggable = $derived(_draggable ?? store.nodesDraggable);
|
||||||
let selectable = $derived(_selectable ?? store.elementsSelectable);
|
let selectable = $derived(_selectable ?? store.elementsSelectable);
|
||||||
let connectable = $derived(_connectable ?? store.nodesConnectable);
|
let connectable = $derived(_connectable ?? store.nodesConnectable);
|
||||||
let initialized = $derived(nodeHasDimensions(node));
|
let initialized = $derived(nodeHasDimensions(node));
|
||||||
|
|
||||||
// TODO: does this make sense
|
// we also pass store.nodes to this function to rerender when a node changes
|
||||||
let flipFlop = false;
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
let shouldRerenderSignal = $derived.by(() =>
|
function getInternalNode(node: Node, nodes: Node[]) {
|
||||||
store.adoptNodes.has(id) ? (flipFlop = !flipFlop) : flipFlop
|
|
||||||
);
|
|
||||||
|
|
||||||
function getInternalNode(node: Node, shouldRerender: boolean = false) {
|
|
||||||
return { ...store.nodeLookup.get(node.id)! };
|
return { ...store.nodeLookup.get(node.id)! };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +66,7 @@
|
|||||||
z: zIndex = 0,
|
z: zIndex = 0,
|
||||||
positionAbsolute: { x: positionX, y: positionY }
|
positionAbsolute: { x: positionX, y: positionY }
|
||||||
}
|
}
|
||||||
} = $derived(getInternalNode(node, shouldRerenderSignal));
|
} = $derived(getInternalNode(node, store.nodes));
|
||||||
|
|
||||||
function isInParentLookup(id: string) {
|
function isInParentLookup(id: string) {
|
||||||
return store.parentLookup.has(id);
|
return store.parentLookup.has(id);
|
||||||
|
|||||||
@@ -1,45 +1,13 @@
|
|||||||
import type { SvelteFlowStore } from '$lib/store/types';
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
import type { InternalNode, Node } from '$lib/types';
|
import type { Node } from '$lib/types';
|
||||||
|
|
||||||
export type ConnectableContext = {
|
export type ConnectableContext = {
|
||||||
value: boolean;
|
value: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeWrapperProps = {
|
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;
|
node: Node;
|
||||||
|
store: SvelteFlowStore;
|
||||||
nodeClickDistance?: number;
|
nodeClickDistance?: number;
|
||||||
resizeObserver?: ResizeObserver | null;
|
resizeObserver?: ResizeObserver | null;
|
||||||
store: SvelteFlowStore;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,16 +14,26 @@
|
|||||||
onedgemouseleave
|
onedgemouseleave
|
||||||
}: { store: SvelteFlowStore } & EdgeEvents = $props();
|
}: { store: SvelteFlowStore } & EdgeEvents = $props();
|
||||||
|
|
||||||
// const { edges, nodes } = store;
|
let previousLayoutedEdges = new Map<string, EdgeLayouted>();
|
||||||
|
|
||||||
let layoutedEdges = $derived.by(() => {
|
let layoutedEdges = $derived.by(() => {
|
||||||
store.nodes;
|
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 sourceNode = store.nodeLookup.get(edge.source);
|
||||||
const targetNode = store.nodeLookup.get(edge.target);
|
const targetNode = store.nodeLookup.get(edge.target);
|
||||||
|
|
||||||
if (!sourceNode || !targetNode) {
|
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({
|
const edgePosition = getEdgePosition({
|
||||||
@@ -37,7 +47,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (edgePosition) {
|
if (edgePosition) {
|
||||||
res.push({
|
layoutedEdges.set(edge.id, {
|
||||||
...edge,
|
...edge,
|
||||||
zIndex: getElevatedEdgeZIndex({
|
zIndex: getElevatedEdgeZIndex({
|
||||||
selected: edge.selected,
|
selected: edge.selected,
|
||||||
@@ -46,13 +56,14 @@
|
|||||||
targetNode,
|
targetNode,
|
||||||
elevateOnSelect: false
|
elevateOnSelect: false
|
||||||
}),
|
}),
|
||||||
...edgePosition
|
...edgePosition,
|
||||||
|
sourceNode,
|
||||||
|
targetNode
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
previousLayoutedEdges = layoutedEdges;
|
||||||
}, []);
|
|
||||||
|
|
||||||
return layoutedEdges;
|
return layoutedEdges;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -62,36 +73,10 @@
|
|||||||
<MarkerDefinition />
|
<MarkerDefinition />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
{#each layoutedEdges as edge (edge.id)}
|
{#each layoutedEdges.values() as edge (edge.id)}
|
||||||
<EdgeWrapper
|
<EdgeWrapper
|
||||||
{store}
|
{store}
|
||||||
id={edge.id}
|
{edge}
|
||||||
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}
|
|
||||||
{onedgeclick}
|
{onedgeclick}
|
||||||
{onedgecontextmenu}
|
{onedgecontextmenu}
|
||||||
{onedgemouseenter}
|
{onedgemouseenter}
|
||||||
@@ -99,7 +84,7 @@
|
|||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#if layoutedEdges.length > 0}
|
{#if layoutedEdges.size > 0}
|
||||||
<CallOnMount
|
<CallOnMount
|
||||||
onMount={() => {
|
onMount={() => {
|
||||||
store.edgesInitialized = true;
|
store.edgesInitialized = true;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
import { NodeWrapper } from '$lib/components/NodeWrapper';
|
import { NodeWrapper } from '$lib/components/NodeWrapper';
|
||||||
|
|
||||||
import type { NodeEvents } from '$lib/types';
|
import type { NodeEvents } from '$lib/types';
|
||||||
import type { SvelteFlowStore } from '$lib/store/types';
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ export function adoptUserNodes<NodeType extends NodeBase>(
|
|||||||
nodeLookup.clear();
|
nodeLookup.clear();
|
||||||
parentLookup.clear();
|
parentLookup.clear();
|
||||||
|
|
||||||
const updatedChildNodes = new Set<string>();
|
|
||||||
for (const userNode of nodes) {
|
for (const userNode of nodes) {
|
||||||
let internalNode = tmpLookup.get(userNode.id);
|
let internalNode = tmpLookup.get(userNode.id);
|
||||||
|
|
||||||
@@ -124,10 +123,9 @@ export function adoptUserNodes<NodeType extends NodeBase>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (userNode.parentId) {
|
if (userNode.parentId) {
|
||||||
updateChildNode(internalNode, nodeLookup, parentLookup, options, updatedChildNodes);
|
updateChildNode(internalNode, nodeLookup, parentLookup, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return updatedChildNodes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateParentLookup<NodeType extends NodeBase>(
|
function updateParentLookup<NodeType extends NodeBase>(
|
||||||
@@ -154,8 +152,7 @@ function updateChildNode<NodeType extends NodeBase>(
|
|||||||
node: InternalNodeBase<NodeType>,
|
node: InternalNodeBase<NodeType>,
|
||||||
nodeLookup: NodeLookup<InternalNodeBase<NodeType>>,
|
nodeLookup: NodeLookup<InternalNodeBase<NodeType>>,
|
||||||
parentLookup: ParentLookup<InternalNodeBase<NodeType>>,
|
parentLookup: ParentLookup<InternalNodeBase<NodeType>>,
|
||||||
options?: UpdateNodesOptions<NodeType>,
|
options?: UpdateNodesOptions<NodeType>
|
||||||
updatedChildNodes?: Set<string>
|
|
||||||
) {
|
) {
|
||||||
const { elevateNodesOnSelect, nodeOrigin, nodeExtent } = mergeObjects(defaultOptions, options);
|
const { elevateNodesOnSelect, nodeOrigin, nodeExtent } = mergeObjects(defaultOptions, options);
|
||||||
const parentId = node.parentId!;
|
const parentId = node.parentId!;
|
||||||
@@ -176,12 +173,15 @@ function updateChildNode<NodeType extends NodeBase>(
|
|||||||
const positionChanged = x !== positionAbsolute.x || y !== positionAbsolute.y;
|
const positionChanged = x !== positionAbsolute.x || y !== positionAbsolute.y;
|
||||||
|
|
||||||
if (positionChanged || z !== node.internals.z) {
|
if (positionChanged || z !== node.internals.z) {
|
||||||
node.internals = {
|
// we create a new object to mark the node as updated
|
||||||
...node.internals,
|
nodeLookup.set(node.id, {
|
||||||
positionAbsolute: positionChanged ? { x, y } : positionAbsolute,
|
...node,
|
||||||
z,
|
internals: {
|
||||||
};
|
...node.internals,
|
||||||
updatedChildNodes?.add(node.id);
|
positionAbsolute: positionChanged ? { x, y } : positionAbsolute,
|
||||||
|
z,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user