fixed subflows, cleaned up nodeRenderer, cleaned up nodewrapper

This commit is contained in:
peterkogo
2024-12-11 13:38:48 +01:00
parent dca2eb6f37
commit 1c67bf848f
10 changed files with 370 additions and 384 deletions
@@ -1,243 +1,8 @@
<script lang="ts"> <script lang="ts">
import { import { SvelteFlowProvider } from '@xyflow/svelte';
SvelteFlow, import Flow from './Flow.svelte';
Controls,
Background,
BackgroundVariant,
MiniMap,
Panel,
SelectionMode,
type NodeTypes,
type EdgeTypes,
type Node,
type Edge,
ConnectionMode,
ControlButton,
type FitViewOptions
} from '@xyflow/svelte';
import CustomNode from './CustomNode.svelte';
import CustomNodeDragHandle from './CustomNodeDragHandle.svelte';
import CustomEdge from './CustomEdge.svelte';
import '@xyflow/svelte/dist/style.css';
import InitTracker from './InitTracker.svelte';
const nodeTypes: NodeTypes = {
custom: CustomNode,
dragHandle: CustomNodeDragHandle
};
const edgeTypes: EdgeTypes = {
custom: CustomEdge
};
const fitViewOptions: FitViewOptions = {
padding: 0.2,
nodes: [{ id: '1' }, { id: '2' }]
};
let nodes = $state.raw<Node[]>([
{
id: '1',
type: 'input',
data: { label: 'Input Node' },
position: { x: 150, y: 5 }
},
{
id: '2',
type: 'default',
data: { label: 'Node' },
position: { x: 0, y: 150 },
selectable: false
},
{
id: 'A',
type: 'default',
data: { label: 'Styled with class' },
class: 'custom-style',
position: { x: 150, y: 150 }
},
{
id: 'D',
type: 'default',
data: { label: 'Not draggable' },
position: { x: 150, y: 200 },
draggable: false
},
{
id: '3',
type: 'output',
data: { label: 'Output Node' },
position: { x: 300, y: 150 }
},
{
id: 'B',
type: 'default',
data: { label: 'Styled with style' },
style: 'border: 2px solid #ff5050;',
position: { x: 450, y: 150 }
},
{
id: 'C',
type: 'dragHandle',
data: { label: 'custom drag handle' },
dragHandle: '.custom-drag-handle',
position: { x: 450, y: 250 }
},
{
id: '4',
type: 'custom',
data: { label: 'Custom Node' },
position: { x: 150, y: 300 }
},
{
id: 'hideunhide',
data: { label: 'HIDE ME' },
position: { x: 300, y: 75 },
hidden: true
}
]);
let edges = $state.raw<Edge[]>([
{
id: '1-2',
type: 'default',
source: '1',
target: '2',
label: 'Edge Text'
},
{
id: '1-3',
type: 'smoothstep',
source: '1',
target: '3',
selectable: false
},
{
id: '2-4',
type: 'custom',
source: '2',
target: '4',
animated: true
}
]);
function updateNode() {
// We might as well just use updateNode() here
// this is just to show what is required to update a node
const newNode = { ...nodes[0] };
newNode.position.x += 20;
nodes[0] = newNode;
nodes = [...nodes];
}
function updateEdge() {
// We might as well just use updateEdge() here
// this is just to show what is required to update an edge
const newEdge = { ...edges[0] };
newEdge.type = newEdge.type === 'default' ? 'smoothstep' : 'default';
edges[0] = newEdge;
edges = [...edges];
}
</script> </script>
<SvelteFlow <SvelteFlowProvider>
bind:nodes <Flow />
bind:edges </SvelteFlowProvider>
{nodeTypes}
{edgeTypes}
fitView
fitViewOptions={{
padding: 0.1,
nodes: [{ id: '1' }, { id: '2' }, { id: '3' }]
}}
minZoom={0}
maxZoom={Infinity}
selectionMode={SelectionMode.Full}
initialViewport={{ x: 100, y: 100, zoom: 2 }}
snapGrid={[25, 25]}
oninit={() => console.log('on init')}
onnodeclick={(event) => console.log('on node click', event)}
onnodemouseenter={(event) => console.log('on node enter', event)}
onnodemouseleave={(event) => console.log('on node leave', event)}
onedgeclick={(event) => console.log('edge click', event)}
onedgemouseenter={(event) => console.log('edge enter', event)}
onedgemouseleave={(event) => console.log('edge leave', event)}
onconnectstart={(event) => console.log('on connect start', event)}
onconnect={(event) => console.log('on connect', event)}
onconnectend={(event) => console.log('on connect end', event)}
onpaneclick={(event) => console.log('on pane click', event)}
onpanecontextmenu={(event) => {
console.log('on pane contextmenu', event);
}}
onnodedrag={(event) => {
console.log('on node drag', event);
}}
onnodedragstart={(event) => {
console.log('on node drag start', event);
}}
onnodedragstop={({ event }) => {
console.log('on node drag stop', event);
}}
onnodecontextmenu={({ event }) => {
event.preventDefault();
console.log('on node contextmenu', event);
}}
onedgecontextmenu={({ event, edge }) => {
event.preventDefault();
console.log('on edge contextmenu', edge);
}}
onselectionclick={(event) => console.log('on selection click', event)}
onselectioncontextmenu={(event) => console.log('on selection contextmenu', event)}
onbeforedelete={async ({ nodes, edges }) => {
console.log('on before delete', nodes, edges);
const deleteElements = confirm('Are you sure you want to delete the selected elements?');
return deleteElements;
}}
autoPanOnConnect
autoPanOnNodeDrag
connectionMode={ConnectionMode.Strict}
attributionPosition={'top-center'}
deleteKey={['Backspace', 'd']}
>
<Controls orientation="horizontal" {fitViewOptions}>
{#snippet before()}
<ControlButton>xy</ControlButton>
{/snippet}
<ControlButton aria-label="log" onclick={() => console.log('control button')}>log</ControlButton
>
</Controls>
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
<Panel position="top-right">
<button onclick={updateNode}>update node pos</button>
<button onclick={updateEdge}>update edge type</button>
<button
onclick={() => {
$nodes[$nodes.length - 1].hidden = !$nodes[$nodes.length - 1].hidden;
$nodes = $nodes;
}}>hide/unhide</button
>
</Panel>
<InitTracker />
</SvelteFlow>
<style>
:global(.svelte-flow .custom-style) {
background: #ff5050;
color: white;
}
:root {
--background-color: #ffffdd;
--background-pattern-color: #5050ff;
--minimap-background-color: #f5f6f7;
--minimap-mask-color: rgb(255, 255, 240, 0.6);
--controls-button-background-color: #ddd;
--controls-button-background-color-hover: #ccc;
}
</style>
@@ -0,0 +1,245 @@
<script lang="ts">
import {
SvelteFlow,
Controls,
Background,
BackgroundVariant,
MiniMap,
Panel,
SelectionMode,
type NodeTypes,
type EdgeTypes,
type Node,
type Edge,
ConnectionMode,
ControlButton,
type FitViewOptions,
useSvelteFlow
} from '@xyflow/svelte';
import CustomNode from './CustomNode.svelte';
import CustomNodeDragHandle from './CustomNodeDragHandle.svelte';
import CustomEdge from './CustomEdge.svelte';
import '@xyflow/svelte/dist/style.css';
import InitTracker from './InitTracker.svelte';
const nodeTypes: NodeTypes = {
custom: CustomNode,
dragHandle: CustomNodeDragHandle
};
const edgeTypes: EdgeTypes = {
custom: CustomEdge
};
const fitViewOptions: FitViewOptions = {
padding: 0.2,
nodes: [{ id: '1' }, { id: '2' }]
};
let nodes = $state.raw<Node[]>([
{
id: '1',
type: 'input',
data: { label: 'Input Node' },
position: { x: 150, y: 5 }
},
{
id: '2',
type: 'default',
data: { label: 'Node' },
position: { x: 0, y: 150 },
selectable: false
},
{
id: 'A',
type: 'default',
data: { label: 'Styled with class' },
class: 'custom-style',
position: { x: 150, y: 150 }
},
{
id: 'D',
type: 'default',
data: { label: 'Not draggable' },
position: { x: 150, y: 200 },
draggable: false
},
{
id: '3',
type: 'output',
data: { label: 'Output Node' },
position: { x: 300, y: 150 }
},
{
id: 'B',
type: 'default',
data: { label: 'Styled with style' },
style: 'border: 2px solid #ff5050;',
position: { x: 450, y: 150 }
},
{
id: 'C',
type: 'dragHandle',
data: { label: 'custom drag handle' },
dragHandle: '.custom-drag-handle',
position: { x: 450, y: 250 }
},
{
id: '4',
type: 'custom',
data: { label: 'Custom Node' },
position: { x: 150, y: 300 }
},
{
id: 'hideunhide',
data: { label: 'HIDE ME' },
position: { x: 300, y: 75 },
hidden: true
}
]);
let edges = $state.raw<Edge[]>([
{
id: '1-2',
type: 'default',
source: '1',
target: '2',
label: 'Edge Text'
},
{
id: '1-3',
type: 'smoothstep',
source: '1',
target: '3',
selectable: false
},
{
id: '2-4',
type: 'custom',
source: '2',
target: '4',
animated: true
}
]);
function moveNode() {
// We might as well just use updateNode() here
// this is just to show what is required to update a node
const newNode = { ...nodes[0] };
newNode.position.x += 20;
nodes[0] = newNode;
nodes = [...nodes];
}
function changeEdgeType() {
// We might as well just use updateEdge() here
// this is just to show what is required to update an edge
const newEdge = { ...edges[0] };
newEdge.type = newEdge.type === 'default' ? 'smoothstep' : 'default';
edges[0] = newEdge;
edges = [...edges];
}
let { updateNode } = $derived(useSvelteFlow());
function hideUnhide() {
updateNode('hideunhide', (node) => ({ hidden: !node.hidden }));
}
</script>
<SvelteFlow
bind:nodes
bind:edges
{nodeTypes}
{edgeTypes}
fitView
fitViewOptions={{
padding: 0.1,
nodes: [{ id: '1' }, { id: '2' }, { id: '3' }]
}}
minZoom={0}
maxZoom={Infinity}
selectionMode={SelectionMode.Full}
initialViewport={{ x: 100, y: 100, zoom: 2 }}
snapGrid={[25, 25]}
oninit={() => console.log('on init')}
onnodeclick={(event) => console.log('on node click', event)}
onnodemouseenter={(event) => console.log('on node enter', event)}
onnodemouseleave={(event) => console.log('on node leave', event)}
onedgeclick={(event) => console.log('edge click', event)}
onedgemouseenter={(event) => console.log('edge enter', event)}
onedgemouseleave={(event) => console.log('edge leave', event)}
onconnectstart={(event) => console.log('on connect start', event)}
onconnect={(event) => console.log('on connect', event)}
onconnectend={(event) => console.log('on connect end', event)}
onpaneclick={(event) => console.log('on pane click', event)}
onpanecontextmenu={(event) => {
console.log('on pane contextmenu', event);
}}
onnodedrag={(event) => {
console.log('on node drag', event);
}}
onnodedragstart={(event) => {
console.log('on node drag start', event);
}}
onnodedragstop={({ event }) => {
console.log('on node drag stop', event);
}}
onnodecontextmenu={({ event }) => {
event.preventDefault();
console.log('on node contextmenu', event);
}}
onedgecontextmenu={({ event, edge }) => {
event.preventDefault();
console.log('on edge contextmenu', edge);
}}
onselectionclick={(event) => console.log('on selection click', event)}
onselectioncontextmenu={(event) => console.log('on selection contextmenu', event)}
onbeforedelete={async ({ nodes, edges }) => {
console.log('on before delete', nodes, edges);
const deleteElements = confirm('Are you sure you want to delete the selected elements?');
return deleteElements;
}}
autoPanOnConnect
autoPanOnNodeDrag
connectionMode={ConnectionMode.Strict}
attributionPosition={'top-center'}
deleteKey={['Backspace', 'd']}
>
<Controls orientation="horizontal" {fitViewOptions}>
{#snippet before()}
<ControlButton>xy</ControlButton>
{/snippet}
<ControlButton aria-label="log" onclick={() => console.log('control button')}>log</ControlButton
>
</Controls>
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
<Panel position="top-right">
<button onclick={moveNode}>update node pos</button>
<button onclick={changeEdgeType}>update edge type</button>
<button onclick={hideUnhide}>hide/unhide</button>
</Panel>
<InitTracker />
</SvelteFlow>
<style>
:global(.svelte-flow .custom-style) {
background: #ff5050;
color: white;
}
:root {
--background-color: #ffffdd;
--background-pattern-color: #5050ff;
--minimap-background-color: #f5f6f7;
--minimap-mask-color: rgb(255, 255, 240, 0.6);
--controls-button-background-color: #ddd;
--controls-button-background-color-hover: #ccc;
}
</style>
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -7,7 +6,8 @@
BackgroundVariant, BackgroundVariant,
MiniMap, MiniMap,
type NodeTypes, type NodeTypes,
type Node type Node,
type Edge
} from '@xyflow/svelte'; } from '@xyflow/svelte';
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
@@ -18,7 +18,7 @@
default: DebugNode default: DebugNode
}; };
const nodes = writable<Node[]>([ let nodes = $state<Node[]>([
{ {
id: '1', id: '1',
type: 'input', type: 'input',
@@ -95,7 +95,7 @@
} }
]); ]);
const edges = writable([ let edges = $state<Edge[]>([
{ {
id: 'e1-2', id: 'e1-2',
source: '1', source: '1',
@@ -115,11 +115,9 @@
{ id: 'e4a-4b2', source: '4a', target: '4b2', zIndex: 100 }, { id: 'e4a-4b2', source: '4a', target: '4b2', zIndex: 100 },
{ id: 'e4b1-4b2', source: '4b1', target: '4b2' } { id: 'e4b1-4b2', source: '4b1', target: '4b2' }
]); ]);
$inspect($nodes);
</script> </script>
<SvelteFlow {nodes} {edges} {nodeTypes} fitView minZoom={0.1} maxZoom={2.5}> <SvelteFlow bind:nodes bind:edges {nodeTypes} fitView minZoom={0.1} maxZoom={2.5}>
<Controls /> <Controls />
<Background variant={BackgroundVariant.Dots} /> <Background variant={BackgroundVariant.Dots} />
<MiniMap /> <MiniMap />
@@ -1,45 +1,18 @@
<script lang="ts"> <script lang="ts">
import { setContext, onDestroy } from 'svelte'; import { setContext, onDestroy } from 'svelte';
import cc from 'classcat'; import cc from 'classcat';
import { errorMessages, 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 DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
import type { ConnectableContext, NodeWrapperProps } from './types'; import type { ConnectableContext, NodeWrapperProps } from './types';
import { getNodeInlineStyleDimensions } from './utils'; import { getNodeInlineStyleDimensions } from './utils';
import type { NodeEvents } from '$lib/types'; import type { Node, NodeEvents } from '$lib/types';
let { let {
store, store,
node, node,
id, resizeObserver,
data = {},
selected = false,
draggable,
selectable,
deletable,
connectable = true,
hidden = false,
dragging = false,
resizeObserver = null,
style,
class: className,
type = 'default',
isParent = false,
parentId,
positionX,
positionY,
sourcePosition,
targetPosition,
zIndex,
measuredWidth,
measuredHeight,
initialWidth,
initialHeight,
width,
height,
dragHandle,
initialized = false,
nodeClickDistance, nodeClickDistance,
onnodeclick, onnodeclick,
onnodedrag, onnodedrag,
@@ -51,6 +24,58 @@
onnodecontextmenu onnodecontextmenu
}: NodeWrapperProps & NodeEvents = $props(); }: NodeWrapperProps & NodeEvents = $props();
let {
id,
data = {},
selected = false,
draggable: _draggable,
selectable: _selectable,
deletable = true,
connectable: _connectable,
hidden = false,
dragging = false,
style,
class: className,
type = 'default',
parentId,
sourcePosition,
targetPosition,
measured: { width: measuredWidth, height: measuredHeight } = { width: 0, height: 0 },
initialWidth,
initialHeight,
width,
height,
dragHandle
} = $derived(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) {
return { ...store.nodeLookup.get(node.id)! };
}
let {
internals: {
z: zIndex = 0,
positionAbsolute: { x: positionX, y: positionY }
}
} = $derived(getInternalNode(node, shouldRerenderSignal));
function isInParentLookup(id: string) {
return store.parentLookup.has(id);
}
let isParent = $derived(isInParentLookup(id));
let nodeRef: HTMLDivElement | null = $state(null); let nodeRef: HTMLDivElement | null = $state(null);
let prevNodeRef: HTMLDivElement | null = null; let prevNodeRef: HTMLDivElement | null = null;
@@ -144,7 +169,7 @@
store.handleNodeSelection(id); store.handleNodeSelection(id);
} }
onnodeclick?.({ node: node.internals.userNode, event }); onnodeclick?.({ node, event });
} }
</script> </script>
@@ -5,40 +5,41 @@ export type ConnectableContext = {
value: boolean; value: boolean;
}; };
export type NodeWrapperProps = Pick< export type NodeWrapperProps = {
Node, // Pick<
| 'id' // Node,
| 'class' // | 'id'
| 'connectable' // | 'class'
| 'data' // | 'connectable'
| 'draggable' // | 'data'
| 'dragging' // | 'draggable'
| 'selected' // | 'dragging'
| 'selectable' // | 'selected'
| 'deletable' // | 'selectable'
| 'style' // | 'deletable'
| 'type' // | 'style'
| 'sourcePosition' // | 'type'
| 'targetPosition' // | 'sourcePosition'
| 'dragHandle' // | 'targetPosition'
| 'hidden' // | 'dragHandle'
| 'width' // | 'hidden'
| 'height' // | 'width'
| 'initialWidth' // | 'height'
| 'initialHeight' // | 'initialWidth'
| 'parentId' // | 'initialHeight'
> & { // | 'parentId'
measuredWidth?: number; // > & {
measuredHeight?: number; // measuredWidth?: number;
type: string; // measuredHeight?: number;
positionX: number; // type: string;
positionY: number; // positionX: number;
'on:nodeclick'?: (event: MouseEvent) => void; // positionY: number;
resizeObserver?: ResizeObserver | null; // 'on:nodeclick'?: (event: MouseEvent) => void;
isParent?: boolean; // isParent?: boolean;
zIndex: number; // zIndex: number;
node: InternalNode; // initialized: boolean;
initialized: boolean; node: Node;
nodeClickDistance?: number; nodeClickDistance?: number;
resizeObserver?: ResizeObserver | null;
store: SvelteFlowStore; store: SvelteFlowStore;
}; };
@@ -1,9 +1,8 @@
<script lang="ts"> <script lang="ts">
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { nodeHasDimensions } from '@xyflow/system';
import type { Node, NodeEvents } from '$lib/types';
import { NodeWrapper } from '$lib/components/NodeWrapper'; import { NodeWrapper } from '$lib/components/NodeWrapper';
import type { NodeEvents } from '$lib/types';
import type { SvelteFlowStore } from '$lib/store/types'; import type { SvelteFlowStore } from '$lib/store/types';
let { let {
@@ -19,8 +18,6 @@
onnodedragstop onnodedragstop
}: { store: SvelteFlowStore; nodeClickDistance?: number } & NodeEvents = $props(); }: { store: SvelteFlowStore; nodeClickDistance?: number } & NodeEvents = $props();
// const { nodes } = store;
const resizeObserver: ResizeObserver | null = const resizeObserver: ResizeObserver | null =
typeof ResizeObserver === 'undefined' typeof ResizeObserver === 'undefined'
? null ? null
@@ -43,59 +40,13 @@
onDestroy(() => { onDestroy(() => {
resizeObserver?.disconnect(); resizeObserver?.disconnect();
}); });
function give(node: Node) {
const internalNode = store.nodeLookup.get(node.id);
// console.log('this reruns most definitely', internalNode);
console.log(internalNode?.position);
return internalNode;
}
// $inspect(store.nodes).with(console.trace);
</script> </script>
<div class="svelte-flow__nodes"> <div class="svelte-flow__nodes">
{#each store.nodes as node (node.id)} {#each store.nodes as node (node.id)}
{@const internalNode = store.nodeLookup.get(node.id)}
<NodeWrapper <NodeWrapper
{store} {store}
node={internalNode!} {node}
id={node.id}
data={node.data}
selected={!!node.selected}
hidden={!!node.hidden}
draggable={!!(
node.draggable ||
(store.nodesDraggable && typeof node.draggable === 'undefined')
)}
selectable={!!(
node.selectable ||
(store.elementsSelectable && typeof node.selectable === 'undefined')
)}
connectable={!!(
node.connectable ||
(store.nodesConnectable && typeof node.connectable === 'undefined')
)}
deletable={node.deletable ?? true}
positionX={internalNode!.internals.positionAbsolute.x}
positionY={internalNode!.internals.positionAbsolute.y}
isParent={store.parentLookup.has(node.id)}
style={node.style}
class={node.class}
type={node.type ?? 'default'}
sourcePosition={node.sourcePosition}
targetPosition={node.targetPosition}
dragging={node.dragging}
zIndex={internalNode!.internals.z ?? 0}
dragHandle={node.dragHandle}
initialized={nodeHasDimensions(node)}
width={node.width}
height={node.height}
initialWidth={node.initialWidth}
initialHeight={node.initialHeight}
measuredWidth={node.measured?.width ?? 0}
measuredHeight={node.measured?.height ?? 0}
parentId={node.parentId}
{resizeObserver} {resizeObserver}
{nodeClickDistance} {nodeClickDistance}
{onnodeclick} {onnodeclick}
+10 -14
View File
@@ -211,13 +211,11 @@ export function useSvelteFlow(): {
// nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>), // nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>),
// options?: { replace: boolean } // options?: { replace: boolean }
// ) => void; // ) => void;
updateNode: updateNode: (
| ((id: string, nodeUpdate: Node | ((node: Node) => Node), options: { replace: true }) => void) id: string,
| (( nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>),
id: string, options?: { replace: boolean }
nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>), ) => void;
options: { replace: false }
) => void);
/** /**
* Updates the data attribute of a node. * Updates the data attribute of a node.
* *
@@ -248,13 +246,11 @@ export function useSvelteFlow(): {
* @example * @example
* updateNode('node-1', (node) => ({ position: { x: node.position.x + 10, y: node.position.y } })); * updateNode('node-1', (node) => ({ position: { x: node.position.x + 10, y: node.position.y } }));
*/ */
updateEdge: updateEdge: (
| ((id: string, edgeUpdate: Edge | ((edge: Edge) => Edge), options: { replace: true }) => void) id: string,
| (( edgeUpdate: Partial<Edge> | ((edge: Edge) => Partial<Edge>),
id: string, options?: { replace: boolean }
edgeUpdate: Partial<Edge> | ((edge: Edge) => Partial<Edge>), ) => void;
options?: { replace: false }
) => void);
toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport }; toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
/** /**
* Returns the bounds of the given nodes or node ids. * Returns the bounds of the given nodes or node ids.
+2
View File
@@ -243,6 +243,8 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
if (node.selected !== selected) { if (node.selected !== selected) {
// we need to mutate the node here in order to have the correct selected state in the drag handler // we need to mutate the node here in order to have the correct selected state in the drag handler
const internalNode = store.nodeLookup.get(node.id);
if (internalNode) internalNode.selected = selected;
node.selected = selected; node.selected = selected;
return { ...node }; return { ...node };
} }
@@ -91,14 +91,13 @@ export const getInitialStore = (signals: StoreSignals) => {
connectionLookup: ConnectionLookup = new Map(); connectionLookup: ConnectionLookup = new Map();
edgeLookup: EdgeLookup = new Map(); edgeLookup: EdgeLookup = new Map();
adoptNodes: true = $derived.by(() => { adoptNodes: Set<string> = $derived.by(() => {
adoptUserNodes(signals.nodes, this.nodeLookup, this.parentLookup, { return adoptUserNodes(signals.nodes, this.nodeLookup, this.parentLookup, {
nodeExtent: this.nodeExtent, nodeExtent: this.nodeExtent,
nodeOrigin: this.nodeOrigin, nodeOrigin: this.nodeOrigin,
elevateNodesOnSelect: false, elevateNodesOnSelect: false,
checkEquality: true checkEquality: true
}); });
return true;
}); });
adoptEdges: true = $derived.by(() => { adoptEdges: true = $derived.by(() => {
updateConnectionLookup(this.connectionLookup, this.edgeLookup, signals.edges); updateConnectionLookup(this.connectionLookup, this.edgeLookup, signals.edges);
@@ -197,8 +196,8 @@ export const getInitialStore = (signals: StoreSignals) => {
edgesInitialized: boolean = $state(false); edgesInitialized: boolean = $state(false);
viewportInitialized: boolean = $state(false); viewportInitialized: boolean = $state(false);
initialNodesLength: number = signals.nodes.length; initialNodesLength: number = signals.nodes?.length ?? 0;
initialEdgesLength: number = signals.edges.length; initialEdgesLength: number = signals.edges?.length ?? 0;
initialized: boolean = $derived.by(() => { initialized: boolean = $derived.by(() => {
let initialized = false; let initialized = false;
// if it hasn't been initialised check if it's now // if it hasn't been initialised check if it's now
+6 -2
View File
@@ -93,6 +93,7 @@ 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);
@@ -123,9 +124,10 @@ export function adoptUserNodes<NodeType extends NodeBase>(
} }
if (userNode.parentId) { if (userNode.parentId) {
updateChildNode(internalNode, nodeLookup, parentLookup, options); updateChildNode(internalNode, nodeLookup, parentLookup, options, updatedChildNodes);
} }
} }
return updatedChildNodes;
} }
function updateParentLookup<NodeType extends NodeBase>( function updateParentLookup<NodeType extends NodeBase>(
@@ -152,7 +154,8 @@ 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!;
@@ -178,6 +181,7 @@ function updateChildNode<NodeType extends NodeBase>(
positionAbsolute: positionChanged ? { x, y } : positionAbsolute, positionAbsolute: positionChanged ? { x, y } : positionAbsolute,
z, z,
}; };
updatedChildNodes?.add(node.id);
} }
} }