Reworked edge rerendering
This commit is contained in:
@@ -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}>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { get } from 'svelte/store';
|
||||
import { XYDrag, type OnDrag } from '@xyflow/system';
|
||||
|
||||
import type { SvelteFlowStore } from '$lib/store/types';
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user