feat(svelte): add useHandleConnections, useNodesData and useUpdateNodeData
This commit is contained in:
@@ -5,7 +5,7 @@ function ResultNode() {
|
|||||||
const connections = useHandleConnections({
|
const connections = useHandleConnections({
|
||||||
handleType: 'target',
|
handleType: 'target',
|
||||||
});
|
});
|
||||||
const nodesData = useNodesData<{ text: string }>(connections.map((connection) => connection.source));
|
const nodesData = useNodesData(connections.map((connection) => connection.source));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('incoming data changed', nodesData);
|
console.log('incoming data changed', nodesData);
|
||||||
@@ -16,7 +16,8 @@ function ResultNode() {
|
|||||||
<Handle type="target" position={Position.Left} />
|
<Handle type="target" position={Position.Left} />
|
||||||
<div>
|
<div>
|
||||||
incoming texts:{' '}
|
incoming texts:{' '}
|
||||||
{nodesData?.filter((nodeData) => nodeData.text).map(({ text }, i) => <div key={i}>{text}</div>) || 'none'}
|
{nodesData?.filter((nodeData) => nodeData.text !== undefined).map(({ text }, i) => <div key={i}>{text}</div>) ||
|
||||||
|
'none'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ function UppercaseNode({ id }: NodeProps) {
|
|||||||
const connections = useHandleConnections({
|
const connections = useHandleConnections({
|
||||||
handleType: 'target',
|
handleType: 'target',
|
||||||
});
|
});
|
||||||
const nodeData = useNodesData<{ text: string }>(connections[0]?.source);
|
const nodeData = useNodesData(connections[0]?.source);
|
||||||
const updateNodeData = useUpdateNodeData();
|
const updateNodeData = useUpdateNodeData();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -14,9 +14,9 @@ function UppercaseNode({ id }: NodeProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ background: '#eee', color: '#222', padding: 10, fontSize: 12, borderRadius: 10 }}>
|
<div style={{ background: '#eee', color: '#222', padding: 10, fontSize: 12, borderRadius: 10 }}>
|
||||||
<Handle type="source" position={Position.Right} />
|
|
||||||
<div>uppercase transform</div>
|
|
||||||
<Handle type="target" position={Position.Left} isConnectable={connections.length === 0} />
|
<Handle type="target" position={Position.Left} isConnectable={connections.length === 0} />
|
||||||
|
<div>uppercase transform</div>
|
||||||
|
<Handle type="source" position={Position.Right} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,18 @@ import TextNode from './TextNode';
|
|||||||
import ResultNode from './ResultNode';
|
import ResultNode from './ResultNode';
|
||||||
import UppercaseNode from './UppercaseNode';
|
import UppercaseNode from './UppercaseNode';
|
||||||
|
|
||||||
|
export type TextNode = Node<{ text: string }, 'text'>;
|
||||||
|
export type ResultNode = Node<{}, 'result'>;
|
||||||
|
export type UppercaseNode = Node<{}, 'uppercase'>;
|
||||||
|
export type MyNode = Node<{ text: string }, 'text'> | Node<{}, 'result'> | Node<{}, 'uppercase'>;
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
text: TextNode,
|
text: TextNode,
|
||||||
result: ResultNode,
|
result: ResultNode,
|
||||||
uppercase: UppercaseNode,
|
uppercase: UppercaseNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
const initNodes: Node[] = [
|
const initNodes: MyNode[] = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
'drag-n-drop',
|
'drag-n-drop',
|
||||||
'edges',
|
'edges',
|
||||||
'figma',
|
'figma',
|
||||||
|
'handle-connect',
|
||||||
'interaction',
|
'interaction',
|
||||||
'intersections',
|
'intersections',
|
||||||
'node-toolbar',
|
'node-toolbar',
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
'stress',
|
'stress',
|
||||||
'subflows',
|
'subflows',
|
||||||
'two-way-viewport',
|
'two-way-viewport',
|
||||||
|
'usenodesdata',
|
||||||
'usesvelteflow',
|
'usesvelteflow',
|
||||||
'useupdatenodeinternals',
|
'useupdatenodeinternals',
|
||||||
'validation'
|
'validation'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
import { Handle, Position, type NodeProps, BezierEdge, SmoothStepEdge } from '@xyflow/svelte';
|
import { Handle, Position, type NodeProps } from '@xyflow/svelte';
|
||||||
|
|
||||||
type $$Props = NodeProps;
|
type $$Props = NodeProps;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import {
|
||||||
|
SvelteFlow,
|
||||||
|
Controls,
|
||||||
|
Background,
|
||||||
|
BackgroundVariant,
|
||||||
|
MiniMap,
|
||||||
|
type Node,
|
||||||
|
type NodeTypes,
|
||||||
|
type Edge
|
||||||
|
} from '@xyflow/svelte';
|
||||||
|
import SingleHandleNode from './SingleHandleNode.svelte';
|
||||||
|
import MultiHandleNode from './MultiHandleNode.svelte';
|
||||||
|
|
||||||
|
import '@xyflow/svelte/dist/style.css';
|
||||||
|
|
||||||
|
const nodeTypes: NodeTypes = {
|
||||||
|
single: SingleHandleNode,
|
||||||
|
multi: MultiHandleNode
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodes = writable<Node[]>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'single',
|
||||||
|
data: {},
|
||||||
|
position: { x: 0, y: 0 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: 'single',
|
||||||
|
data: {},
|
||||||
|
position: { x: 200, y: -100 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: 'single',
|
||||||
|
data: {},
|
||||||
|
position: { x: 200, y: 100 }
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
type: 'multi',
|
||||||
|
data: {},
|
||||||
|
position: { x: 400, y: 0 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
type: 'multi',
|
||||||
|
data: {},
|
||||||
|
position: { x: 600, y: -100 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
type: 'multi',
|
||||||
|
data: {},
|
||||||
|
position: { x: 600, y: 100 }
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const edges = writable<Edge[]>([
|
||||||
|
{
|
||||||
|
id: 'e1-2',
|
||||||
|
source: '1',
|
||||||
|
target: '2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e1-3',
|
||||||
|
source: '1',
|
||||||
|
target: '3'
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'e4a-5',
|
||||||
|
source: '4',
|
||||||
|
sourceHandle: 'a',
|
||||||
|
target: '5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e4b-5',
|
||||||
|
source: '4',
|
||||||
|
sourceHandle: 'b',
|
||||||
|
target: '6'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SvelteFlow {nodes} {edges} {nodeTypes} fitView colorMode="dark">
|
||||||
|
<Controls />
|
||||||
|
<Background variant={BackgroundVariant.Dots} />
|
||||||
|
<MiniMap />
|
||||||
|
</SvelteFlow>
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
Handle,
|
||||||
|
Position,
|
||||||
|
type NodeProps,
|
||||||
|
type Connection,
|
||||||
|
useHandleConnections
|
||||||
|
} from '@xyflow/svelte';
|
||||||
|
|
||||||
|
type $$Props = NodeProps;
|
||||||
|
|
||||||
|
export let id: $$Props['id'];
|
||||||
|
|
||||||
|
function onConnectTarget(connection: Connection[]) {
|
||||||
|
console.log('connect target', connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConnectSource(handleId: string, connection: Connection[]) {
|
||||||
|
console.log('connect source', handleId, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDisconnectTarget(connection: Connection[]) {
|
||||||
|
console.log('disconnect target', connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDisconnectSource(handleId: string, connection: Connection[]) {
|
||||||
|
console.log('disconnect source', handleId, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
const connections = useHandleConnections({ nodeId: id, type: 'target' });
|
||||||
|
|
||||||
|
$: {
|
||||||
|
console.log('connections', id, $connections);
|
||||||
|
}
|
||||||
|
|
||||||
|
export let data: $$Props['data'];
|
||||||
|
export let targetPosition: $$Props['targetPosition'] = Position.Top;
|
||||||
|
export let sourcePosition: $$Props['sourcePosition'] = Position.Bottom;
|
||||||
|
export let width: $$Props['width'] = undefined;
|
||||||
|
export let height: $$Props['height'] = undefined;
|
||||||
|
export let selected: $$Props['selected'] = undefined;
|
||||||
|
export let type: $$Props['type'] = undefined;
|
||||||
|
export let zIndex: $$Props['zIndex'] = undefined;
|
||||||
|
export let dragging: $$Props['dragging'] = false;
|
||||||
|
export let dragHandle: $$Props['dragHandle'] = undefined;
|
||||||
|
export let positionAbsolute: $$Props['positionAbsolute'] = {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
export let isConnectable: $$Props['isConnectable'] = undefined;
|
||||||
|
|
||||||
|
data;
|
||||||
|
targetPosition;
|
||||||
|
sourcePosition;
|
||||||
|
width;
|
||||||
|
height;
|
||||||
|
selected;
|
||||||
|
type;
|
||||||
|
zIndex;
|
||||||
|
dragging;
|
||||||
|
dragHandle;
|
||||||
|
positionAbsolute;
|
||||||
|
isConnectable;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="custom">
|
||||||
|
<Handle
|
||||||
|
type="target"
|
||||||
|
position={Position.Left}
|
||||||
|
onconnect={onConnectTarget}
|
||||||
|
ondisconnect={onDisconnectTarget}
|
||||||
|
/>
|
||||||
|
<div>node {id}</div>
|
||||||
|
<Handle
|
||||||
|
id="a"
|
||||||
|
type="source"
|
||||||
|
position={Position.Right}
|
||||||
|
onconnect={(connections) => onConnectSource('a', connections)}
|
||||||
|
ondisconnect={(connections) => onDisconnectSource('a', connections)}
|
||||||
|
class="source-a"
|
||||||
|
/>
|
||||||
|
<Handle
|
||||||
|
id="b"
|
||||||
|
type="source"
|
||||||
|
position={Position.Right}
|
||||||
|
onconnect={(connections) => onConnectSource('b', connections)}
|
||||||
|
ondisconnect={(connections) => onDisconnectSource('b', connections)}
|
||||||
|
class="source-b"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.custom {
|
||||||
|
background-color: #333;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom :global(.source-a) {
|
||||||
|
top: 5px;
|
||||||
|
transform: translate(50%, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom :global(.source-b) {
|
||||||
|
bottom: 5px;
|
||||||
|
top: auto;
|
||||||
|
transform: translate(50%, 0);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Handle, Position, type NodeProps, type Connection } from '@xyflow/svelte';
|
||||||
|
|
||||||
|
type $$Props = NodeProps;
|
||||||
|
|
||||||
|
export let id: $$Props['id'];
|
||||||
|
|
||||||
|
function onConnectTarget(connection: Connection[]) {
|
||||||
|
console.log('connect target', connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConnectSource(connection: Connection[]) {
|
||||||
|
console.log('connect source', connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDisconnectTarget(connection: Connection[]) {
|
||||||
|
console.log('disconnect target', connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDisconnectSource(connection: Connection[]) {
|
||||||
|
console.log('disconnect source', connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
export let data: $$Props['data'];
|
||||||
|
export let targetPosition: $$Props['targetPosition'] = Position.Top;
|
||||||
|
export let sourcePosition: $$Props['sourcePosition'] = Position.Bottom;
|
||||||
|
export let width: $$Props['width'] = undefined;
|
||||||
|
export let height: $$Props['height'] = undefined;
|
||||||
|
export let selected: $$Props['selected'] = undefined;
|
||||||
|
export let type: $$Props['type'] = undefined;
|
||||||
|
export let zIndex: $$Props['zIndex'] = undefined;
|
||||||
|
export let dragging: $$Props['dragging'] = false;
|
||||||
|
export let dragHandle: $$Props['dragHandle'] = undefined;
|
||||||
|
export let positionAbsolute: $$Props['positionAbsolute'] = {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
export let isConnectable: $$Props['isConnectable'] = undefined;
|
||||||
|
|
||||||
|
data;
|
||||||
|
targetPosition;
|
||||||
|
sourcePosition;
|
||||||
|
width;
|
||||||
|
height;
|
||||||
|
selected;
|
||||||
|
type;
|
||||||
|
zIndex;
|
||||||
|
dragging;
|
||||||
|
dragHandle;
|
||||||
|
positionAbsolute;
|
||||||
|
isConnectable;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="custom">
|
||||||
|
<Handle
|
||||||
|
type="target"
|
||||||
|
position={Position.Left}
|
||||||
|
onconnect={onConnectTarget}
|
||||||
|
ondisconnect={onDisconnectTarget}
|
||||||
|
/>
|
||||||
|
<div>node {id}</div>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Right}
|
||||||
|
onconnect={onConnectSource}
|
||||||
|
ondisconnect={onDisconnectSource}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.custom {
|
||||||
|
background-color: #333;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import {
|
||||||
|
SvelteFlow,
|
||||||
|
Controls,
|
||||||
|
Background,
|
||||||
|
BackgroundVariant,
|
||||||
|
MiniMap,
|
||||||
|
type Node,
|
||||||
|
type NodeTypes,
|
||||||
|
type Edge
|
||||||
|
} from '@xyflow/svelte';
|
||||||
|
import '@xyflow/svelte/dist/style.css';
|
||||||
|
|
||||||
|
import TextNode from './TextNode.svelte';
|
||||||
|
import UppercaseNode from './UppercaseNode.svelte';
|
||||||
|
import ResultNode from './ResultNode.svelte';
|
||||||
|
|
||||||
|
const nodeTypes: NodeTypes = {
|
||||||
|
text: TextNode,
|
||||||
|
uppercase: UppercaseNode,
|
||||||
|
result: ResultNode
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodes = writable<Node[]>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'text',
|
||||||
|
data: {
|
||||||
|
text: 'hello'
|
||||||
|
},
|
||||||
|
position: { x: -100, y: -50 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1a',
|
||||||
|
type: 'uppercase',
|
||||||
|
data: {},
|
||||||
|
position: { x: 100, y: 0 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: 'text',
|
||||||
|
data: {
|
||||||
|
text: 'world'
|
||||||
|
},
|
||||||
|
position: { x: 0, y: 100 }
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: 'result',
|
||||||
|
data: {},
|
||||||
|
position: { x: 300, y: 50 }
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const edges = writable<Edge[]>([
|
||||||
|
{
|
||||||
|
id: 'e1-1a',
|
||||||
|
source: '1',
|
||||||
|
target: '1a'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e1a-3',
|
||||||
|
source: '1a',
|
||||||
|
target: '3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e2-3',
|
||||||
|
source: '2',
|
||||||
|
target: '3'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SvelteFlow {nodes} {edges} {nodeTypes} fitView>
|
||||||
|
<Controls />
|
||||||
|
<Background variant={BackgroundVariant.Dots} />
|
||||||
|
<MiniMap />
|
||||||
|
</SvelteFlow>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
Handle,
|
||||||
|
Position,
|
||||||
|
useHandleConnections,
|
||||||
|
useNodesData,
|
||||||
|
useUpdateNodeData,
|
||||||
|
type NodeProps
|
||||||
|
} from '@xyflow/svelte';
|
||||||
|
|
||||||
|
type $$Props = NodeProps;
|
||||||
|
|
||||||
|
export let id: $$Props['id'];
|
||||||
|
|
||||||
|
const connections = useHandleConnections({
|
||||||
|
nodeId: id,
|
||||||
|
type: 'target'
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeData = useNodesData($connections.map((connection) => connection.source));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="custom">
|
||||||
|
<Handle type="target" position={Position.Left} />
|
||||||
|
<div>incoming texts:</div>
|
||||||
|
|
||||||
|
{#each $nodeData as data}
|
||||||
|
<div>{data.text}</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.custom {
|
||||||
|
background-color: #eee;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Handle, Position, type NodeProps, useUpdateNodeData } from '@xyflow/svelte';
|
||||||
|
|
||||||
|
type $$Props = NodeProps;
|
||||||
|
|
||||||
|
export let id: $$Props['id'];
|
||||||
|
export let data: $$Props['data'];
|
||||||
|
|
||||||
|
const updateNodeData = useUpdateNodeData();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="custom">
|
||||||
|
<div>node {id}</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
value={data.text}
|
||||||
|
on:input={(evt) => updateNodeData(id, { text: evt.currentTarget.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Handle type="source" position={Position.Right} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.custom {
|
||||||
|
background-color: #eee;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
Handle,
|
||||||
|
Position,
|
||||||
|
type NodeProps,
|
||||||
|
useHandleConnections,
|
||||||
|
useNodesData,
|
||||||
|
useUpdateNodeData
|
||||||
|
} from '@xyflow/svelte';
|
||||||
|
|
||||||
|
type $$Props = NodeProps;
|
||||||
|
|
||||||
|
export let id: $$Props['id'];
|
||||||
|
|
||||||
|
const updateNodeData = useUpdateNodeData();
|
||||||
|
const connections = useHandleConnections({
|
||||||
|
nodeId: id,
|
||||||
|
type: 'target'
|
||||||
|
});
|
||||||
|
|
||||||
|
$: nodeData = useNodesData($connections[0]?.source);
|
||||||
|
|
||||||
|
$: {
|
||||||
|
updateNodeData(id, { text: $nodeData?.text?.toUpperCase() || '' });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="custom">
|
||||||
|
<Handle type="target" position={Position.Left} isConnectable={$connections.length === 0} />
|
||||||
|
<div>uppercase transform</div>
|
||||||
|
<Handle type="source" position={Position.Right} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.custom {
|
||||||
|
background-color: #eee;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -171,7 +171,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
lib,
|
lib,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid && connection) {
|
||||||
onConnectExtended(connection);
|
onConnectExtended(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useMemo, useRef } from 'react';
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
import { Connection, HandleType } from '@xyflow/system';
|
import { Connection, HandleType, areConnectionMapsEqual, handleConnectionChange } from '@xyflow/system';
|
||||||
|
|
||||||
import { useStore } from './useStore';
|
import { useStore } from './useStore';
|
||||||
import { useNodeId } from '../contexts/NodeIdContext';
|
import { useNodeId } from '../contexts/NodeIdContext';
|
||||||
@@ -52,55 +52,3 @@ export function useHandleConnections({
|
|||||||
|
|
||||||
return useMemo(() => Array.from(connections?.values() ?? []), [connections]);
|
return useMemo(() => Array.from(connections?.values() ?? []), [connections]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
function areConnectionMapsEqual(a?: Map<string, Connection>, b?: Map<string, Connection>) {
|
|
||||||
if (!a && !b) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!a || !b || a.size !== b.size) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!a.size && !b.size) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key of a.keys()) {
|
|
||||||
if (!b.has(key)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We call the callback for all connections in a that are not in b
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
function handleConnectionChange(
|
|
||||||
a: Map<string, Connection>,
|
|
||||||
b: Map<string, Connection>,
|
|
||||||
cb?: (diff: Connection[]) => void
|
|
||||||
) {
|
|
||||||
if (!cb) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const diff: Connection[] = [];
|
|
||||||
|
|
||||||
a.forEach((connection, key) => {
|
|
||||||
if (!b?.has(key)) {
|
|
||||||
diff.push(connection);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (diff.length) {
|
|
||||||
cb(diff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,9 +2,14 @@ import { useCallback } from 'react';
|
|||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
|
|
||||||
import { useStore } from '../hooks/useStore';
|
import { useStore } from '../hooks/useStore';
|
||||||
|
import type { Node } from '../types';
|
||||||
|
|
||||||
export function useNodesData<NodeData = unknown>(nodeId: string): NodeData | null;
|
export function useNodesData<NodeType extends Node = Node>(nodeId: string): NodeType['data'] | null;
|
||||||
export function useNodesData<NodeData = unknown>(nodeIds: string[]): NodeData[];
|
export function useNodesData<NodeType extends Node = Node>(nodeIds: string[]): NodeType['data'][];
|
||||||
|
export function useNodesData<NodeType extends Node = Node>(
|
||||||
|
nodeIds: string[],
|
||||||
|
guard: (node: Node) => node is NodeType
|
||||||
|
): NodeType['data'][];
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export function useNodesData(nodeIds: any): any {
|
export function useNodesData(nodeIds: any): any {
|
||||||
const nodesData = useStore(
|
const nodesData = useStore(
|
||||||
@@ -14,15 +19,17 @@ export function useNodesData(nodeIds: any): any {
|
|||||||
return s.nodeLookup.get(nodeIds)?.data || null;
|
return s.nodeLookup.get(nodeIds)?.data || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeIds.reduce((res, id) => {
|
const data = [];
|
||||||
const node = s.nodeLookup.get(id);
|
|
||||||
|
|
||||||
if (node) {
|
for (const nodeId of nodeIds) {
|
||||||
res.push(node.data);
|
const nodeData = s.nodeLookup.get(nodeId)?.data;
|
||||||
|
|
||||||
|
if (nodeData) {
|
||||||
|
data.push(nodeData);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return data;
|
||||||
}, []);
|
|
||||||
},
|
},
|
||||||
[nodeIds]
|
[nodeIds]
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import {
|
|||||||
panBy as panBySystem,
|
panBy as panBySystem,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
updateNodeDimensions as updateNodeDimensionsSystem,
|
updateNodeDimensions as updateNodeDimensionsSystem,
|
||||||
|
updateConnectionLookup,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import { applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
import { applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
||||||
import { updateConnectionLookup, updateNodesAndEdgesSelections } from './utils';
|
import { updateNodesAndEdgesSelections } from './utils';
|
||||||
import getInitialState from './initialState';
|
import getInitialState from './initialState';
|
||||||
import type {
|
import type {
|
||||||
ReactFlowState,
|
ReactFlowState,
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ import {
|
|||||||
getNodesBounds,
|
getNodesBounds,
|
||||||
getViewportForBounds,
|
getViewportForBounds,
|
||||||
Transform,
|
Transform,
|
||||||
Connection,
|
updateConnectionLookup,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { Edge, Node, ReactFlowStore } from '../types';
|
import type { Edge, Node, ReactFlowStore } from '../types';
|
||||||
import { updateConnectionLookup } from './utils';
|
|
||||||
|
|
||||||
const getInitialState = ({
|
const getInitialState = ({
|
||||||
nodes = [],
|
nodes = [],
|
||||||
@@ -24,8 +23,8 @@ const getInitialState = ({
|
|||||||
height?: number;
|
height?: number;
|
||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
} = {}): ReactFlowStore => {
|
} = {}): ReactFlowStore => {
|
||||||
const nodeLookup = new Map<string, Node>();
|
const nodeLookup = new Map();
|
||||||
const connectionLookup = updateConnectionLookup(new Map<string, Map<string, Connection>>(), edges);
|
const connectionLookup = updateConnectionLookup(new Map(), edges);
|
||||||
const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
||||||
|
|
||||||
let transform: Transform = [0, 0, 1];
|
let transform: Transform = [0, 0, 1];
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { StoreApi } from 'zustand';
|
import type { StoreApi } from 'zustand';
|
||||||
import type { Edge, EdgeSelectionChange, Node, NodeSelectionChange, ReactFlowState } from '../types';
|
import type { Edge, EdgeSelectionChange, Node, NodeSelectionChange, ReactFlowState } from '../types';
|
||||||
import { Connection } from '@xyflow/system';
|
|
||||||
|
|
||||||
export function handleControlledSelectionChange<NodeOrEdge extends Node | Edge>(
|
export function handleControlledSelectionChange<NodeOrEdge extends Node | Edge>(
|
||||||
changes: NodeSelectionChange[] | EdgeSelectionChange[],
|
changes: NodeSelectionChange[] | EdgeSelectionChange[],
|
||||||
@@ -43,23 +42,3 @@ export function updateNodesAndEdgesSelections({ changedNodes, changedEdges, get,
|
|||||||
onEdgesChange?.(changedEdges);
|
onEdgesChange?.(changedEdges);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateConnectionLookup(lookup: Map<string, Map<string, Connection>>, edges: Edge[]) {
|
|
||||||
lookup.clear();
|
|
||||||
|
|
||||||
edges.forEach(({ source, target, sourceHandle = null, targetHandle = null }) => {
|
|
||||||
if (source && target) {
|
|
||||||
const sourceKey = `${source}-source-${sourceHandle}`;
|
|
||||||
const targetKey = `${target}-target-${targetHandle}`;
|
|
||||||
|
|
||||||
const prevSource = lookup.get(sourceKey) || new Map();
|
|
||||||
const prevTarget = lookup.get(targetKey) || new Map();
|
|
||||||
const connection = { source, target, sourceHandle, targetHandle };
|
|
||||||
|
|
||||||
lookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection));
|
|
||||||
lookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return lookup;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
XYHandle,
|
XYHandle,
|
||||||
isMouseEvent,
|
isMouseEvent,
|
||||||
type Connection,
|
type Connection,
|
||||||
type HandleType
|
type HandleType,
|
||||||
|
areConnectionMapsEqual,
|
||||||
|
handleConnectionChange
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
@@ -20,6 +22,8 @@
|
|||||||
export let position: $$Props['position'] = Position.Top;
|
export let position: $$Props['position'] = Position.Top;
|
||||||
export let style: $$Props['style'] = undefined;
|
export let style: $$Props['style'] = undefined;
|
||||||
export let isConnectable: $$Props['isConnectable'] = undefined;
|
export let isConnectable: $$Props['isConnectable'] = undefined;
|
||||||
|
export let onconnect: $$Props['onconnect'] = undefined;
|
||||||
|
export let ondisconnect: $$Props['ondisconnect'] = undefined;
|
||||||
// export let isConnectableStart: $$Props['isConnectableStart'] = undefined;
|
// export let isConnectableStart: $$Props['isConnectableStart'] = undefined;
|
||||||
// export let isConnectableEnd: $$Props['isConnectableEnd'] = undefined;
|
// export let isConnectableEnd: $$Props['isConnectableEnd'] = undefined;
|
||||||
|
|
||||||
@@ -59,7 +63,9 @@
|
|||||||
panBy,
|
panBy,
|
||||||
cancelConnection,
|
cancelConnection,
|
||||||
updateConnection,
|
updateConnection,
|
||||||
autoPanOnConnect
|
autoPanOnConnect,
|
||||||
|
edges,
|
||||||
|
connectionLookup
|
||||||
} = store;
|
} = store;
|
||||||
|
|
||||||
function onPointerDown(event: MouseEvent | TouchEvent) {
|
function onPointerDown(event: MouseEvent | TouchEvent) {
|
||||||
@@ -108,6 +114,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let prevConnections: Map<string, Connection> | null = null;
|
||||||
|
let connections: Map<string, Connection> | undefined;
|
||||||
|
|
||||||
|
$: if (onconnect || ondisconnect) {
|
||||||
|
// connectionLookup is not reactive, so we use edges to get notified about updates
|
||||||
|
$edges;
|
||||||
|
connections = $connectionLookup.get(`${nodeId}-${type}-${id || null}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (prevConnections && !areConnectionMapsEqual(connections, prevConnections)) {
|
||||||
|
const _connections = connections ?? new Map();
|
||||||
|
|
||||||
|
handleConnectionChange(prevConnections, _connections, ondisconnect);
|
||||||
|
handleConnectionChange(_connections, prevConnections, onconnect);
|
||||||
|
}
|
||||||
|
|
||||||
|
prevConnections = connections ?? new Map();
|
||||||
|
}
|
||||||
|
|
||||||
// @todo implement connectablestart, connectableend
|
// @todo implement connectablestart, connectableend
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@
|
|||||||
trigger: [
|
trigger: [
|
||||||
{
|
{
|
||||||
...deleteKeyDefinition,
|
...deleteKeyDefinition,
|
||||||
callback: () => deleteKeyDefinition.key && deleteKeyPressed.set(true)
|
callback: (event) => deleteKeyDefinition.key && deleteKeyPressed.set(true)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
type: 'keydown'
|
type: 'keydown'
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { derived } from 'svelte/store';
|
||||||
|
import { areConnectionMapsEqual, type Connection, type HandleType } from '@xyflow/system';
|
||||||
|
|
||||||
|
import { useStore } from '$lib/store';
|
||||||
|
|
||||||
|
export type useHandleConnectionsParams = {
|
||||||
|
nodeId: string;
|
||||||
|
type: HandleType;
|
||||||
|
id?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialConnections: Connection[] = [];
|
||||||
|
|
||||||
|
export function useHandleConnections({ nodeId, type, id = null }: useHandleConnectionsParams) {
|
||||||
|
const { edges, connectionLookup } = useStore();
|
||||||
|
let prevConnections: Map<string, Connection> | undefined = undefined;
|
||||||
|
|
||||||
|
return derived(
|
||||||
|
[edges, connectionLookup],
|
||||||
|
([, connectionLookup], set) => {
|
||||||
|
const nextConnections = connectionLookup.get(`${nodeId}-${type}-${id || null}`);
|
||||||
|
|
||||||
|
if (!areConnectionMapsEqual(nextConnections, prevConnections)) {
|
||||||
|
prevConnections = nextConnections;
|
||||||
|
set(Array.from(prevConnections?.values() || []));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initialConnections
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { derived, type Readable } from 'svelte/store';
|
||||||
|
|
||||||
|
import type { Node } from '$lib/types';
|
||||||
|
import { useStore } from '$lib/store';
|
||||||
|
|
||||||
|
function areNodesDataEqual(a: Node['data'][] | null, b: Node['data'][] | null) {
|
||||||
|
if ((!a && !b) || (!a?.length && !b?.length)) {
|
||||||
|
true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a || !b || a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (a[i] !== b[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useNodesData<NodeType extends Node = Node>(
|
||||||
|
nodeId: string
|
||||||
|
): Readable<NodeType['data'] | null>;
|
||||||
|
export function useNodesData<NodeType extends Node = Node>(
|
||||||
|
nodeIds: string[]
|
||||||
|
): Readable<NodeType['data'][]>;
|
||||||
|
export function useNodesData<NodeType extends Node = Node>(
|
||||||
|
nodeIds: string[],
|
||||||
|
guard: (node: Node) => node is NodeType
|
||||||
|
): Readable<NodeType['data'][]>;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export function useNodesData(nodeIds: any): any {
|
||||||
|
const { nodes, nodeLookup } = useStore();
|
||||||
|
let prevNodesData: (Node['data'] | null)[] | null = null;
|
||||||
|
|
||||||
|
return derived([nodes, nodeLookup], ([, nodeLookup], set) => {
|
||||||
|
let nextNodesData: (Node['data'] | null)[] | null = null;
|
||||||
|
const nodeIdArray = Array.isArray(nodeIds);
|
||||||
|
|
||||||
|
if (!nodeIdArray) {
|
||||||
|
nextNodesData = [nodeLookup.get(nodeIds)?.data || null];
|
||||||
|
} else {
|
||||||
|
const data = [];
|
||||||
|
|
||||||
|
for (const nodeId of nodeIds) {
|
||||||
|
const nodeData = nodeLookup.get(nodeId)?.data;
|
||||||
|
|
||||||
|
if (nodeData) {
|
||||||
|
data.push(nodeData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextNodesData = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!areNodesDataEqual(nextNodesData, prevNodesData)) {
|
||||||
|
prevNodesData = nextNodesData;
|
||||||
|
set(nodeIdArray ? nextNodesData : nextNodesData[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { useStore } from '$lib/store';
|
||||||
|
|
||||||
|
export function useUpdateNodeData(): (id: string, data: unknown) => void {
|
||||||
|
const { nodes } = useStore();
|
||||||
|
|
||||||
|
const updateNodeData = (id: string, data: unknown) => {
|
||||||
|
nodes.update((nds) =>
|
||||||
|
nds.map((node) => {
|
||||||
|
if (node.id === id) {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return updateNodeData;
|
||||||
|
}
|
||||||
@@ -27,6 +27,9 @@ export * from '$lib/hooks/useSvelteFlow';
|
|||||||
export * from '$lib/hooks/useUpdateNodeInternals';
|
export * from '$lib/hooks/useUpdateNodeInternals';
|
||||||
export * from '$lib/hooks/useConnection';
|
export * from '$lib/hooks/useConnection';
|
||||||
export * from '$lib/hooks/useNodesEdges';
|
export * from '$lib/hooks/useNodesEdges';
|
||||||
|
export * from '$lib/hooks/useHandleConnections';
|
||||||
|
export * from '$lib/hooks/useNodesData';
|
||||||
|
export * from '$lib/hooks/useUpdateNodeData';
|
||||||
|
|
||||||
// types
|
// types
|
||||||
export type {
|
export type {
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ import {
|
|||||||
type Viewport,
|
type Viewport,
|
||||||
updateNodes,
|
updateNodes,
|
||||||
getNodesBounds,
|
getNodesBounds,
|
||||||
getViewportForBounds
|
getViewportForBounds,
|
||||||
|
updateConnectionLookup,
|
||||||
|
type ConnectionLookup
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
||||||
@@ -72,11 +74,12 @@ export const getInitialStore = ({
|
|||||||
height?: number;
|
height?: number;
|
||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const nodeLookup = new Map<string, Node>();
|
const nodeLookup = new Map();
|
||||||
const nextNodes = updateNodes(nodes, nodeLookup, {
|
const nextNodes = updateNodes(nodes, nodeLookup, {
|
||||||
nodeOrigin: [0, 0],
|
nodeOrigin: [0, 0],
|
||||||
elevateNodesOnSelect: false
|
elevateNodesOnSelect: false
|
||||||
});
|
});
|
||||||
|
const connectionLookup = updateConnectionLookup(new Map(), edges);
|
||||||
|
|
||||||
let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
|
let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
|
||||||
|
|
||||||
@@ -91,8 +94,9 @@ export const getInitialStore = ({
|
|||||||
nodes: createNodesStore(nextNodes, nodeLookup),
|
nodes: createNodesStore(nextNodes, nodeLookup),
|
||||||
nodeLookup: readable<Map<string, Node>>(nodeLookup),
|
nodeLookup: readable<Map<string, Node>>(nodeLookup),
|
||||||
visibleNodes: readable<Node[]>([]),
|
visibleNodes: readable<Node[]>([]),
|
||||||
edges: createEdgesStore(edges),
|
edges: createEdgesStore(edges, connectionLookup),
|
||||||
edgeTree: readable<GroupedEdges<EdgeLayouted>[]>([]),
|
edgeTree: readable<GroupedEdges<EdgeLayouted>[]>([]),
|
||||||
|
connectionLookup: readable<ConnectionLookup>(connectionLookup),
|
||||||
height: writable<number>(500),
|
height: writable<number>(500),
|
||||||
width: writable<number>(500),
|
width: writable<number>(500),
|
||||||
minZoom: writable<number>(0.5),
|
minZoom: writable<number>(0.5),
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ import {
|
|||||||
type Writable,
|
type Writable,
|
||||||
get
|
get
|
||||||
} from 'svelte/store';
|
} from 'svelte/store';
|
||||||
import { updateNodes, type Viewport, type PanZoomInstance } from '@xyflow/system';
|
import {
|
||||||
|
updateNodes,
|
||||||
|
type Viewport,
|
||||||
|
type PanZoomInstance,
|
||||||
|
type ConnectionLookup,
|
||||||
|
updateConnectionLookup
|
||||||
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
||||||
|
|
||||||
@@ -168,6 +174,7 @@ export const createNodesStore = (
|
|||||||
|
|
||||||
export const createEdgesStore = (
|
export const createEdgesStore = (
|
||||||
edges: Edge[],
|
edges: Edge[],
|
||||||
|
connectionLookup: ConnectionLookup,
|
||||||
defaultOptions?: DefaultEdgeOptions
|
defaultOptions?: DefaultEdgeOptions
|
||||||
): Writable<Edge[]> & { setDefaultOptions: (opts: DefaultEdgeOptions) => void } => {
|
): Writable<Edge[]> & { setDefaultOptions: (opts: DefaultEdgeOptions) => void } => {
|
||||||
const { subscribe, set, update } = writable<Edge[]>([]);
|
const { subscribe, set, update } = writable<Edge[]>([]);
|
||||||
@@ -176,6 +183,9 @@ export const createEdgesStore = (
|
|||||||
|
|
||||||
const _set: typeof set = (eds: Edge[]) => {
|
const _set: typeof set = (eds: Edge[]) => {
|
||||||
const nextEdges = defaults ? eds.map((edge) => ({ ...defaults, ...edge })) : eds;
|
const nextEdges = defaults ? eds.map((edge) => ({ ...defaults, ...edge })) : eds;
|
||||||
|
|
||||||
|
updateConnectionLookup(connectionLookup, nextEdges);
|
||||||
|
|
||||||
value = nextEdges;
|
value = nextEdges;
|
||||||
set(value);
|
set(value);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ export type HandleComponentProps = {
|
|||||||
isConnectable?: boolean;
|
isConnectable?: boolean;
|
||||||
isConnectableStart?: boolean;
|
isConnectableStart?: boolean;
|
||||||
isConnectableEnd?: boolean;
|
isConnectableEnd?: boolean;
|
||||||
|
onconnect?: (connections: Connection[]) => void;
|
||||||
|
ondisconnect?: (connections: Connection[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FitViewOptions = FitViewOptionsBase<Node>;
|
export type FitViewOptions = FitViewOptionsBase<Node>;
|
||||||
|
|||||||
@@ -139,3 +139,5 @@ export type UpdateConnection = (params: {
|
|||||||
|
|
||||||
export type ColorModeClass = 'light' | 'dark';
|
export type ColorModeClass = 'light' | 'dark';
|
||||||
export type ColorMode = ColorModeClass | 'system';
|
export type ColorMode = ColorModeClass | 'system';
|
||||||
|
|
||||||
|
export type ConnectionLookup = Map<string, Map<string, Connection>>;
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { Connection } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function areConnectionMapsEqual(a?: Map<string, Connection>, b?: Map<string, Connection>) {
|
||||||
|
if (!a && !b) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a || !b || a.size !== b.size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a.size && !b.size) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of a.keys()) {
|
||||||
|
if (!b.has(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We call the callback for all connections in a that are not in b
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function handleConnectionChange(
|
||||||
|
a: Map<string, Connection>,
|
||||||
|
b: Map<string, Connection>,
|
||||||
|
cb?: (diff: Connection[]) => void
|
||||||
|
) {
|
||||||
|
if (!cb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const diff: Connection[] = [];
|
||||||
|
|
||||||
|
a.forEach((connection, key) => {
|
||||||
|
if (!b?.has(key)) {
|
||||||
|
diff.push(connection);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (diff.length) {
|
||||||
|
cb(diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export * from './connections';
|
||||||
export * from './dom';
|
export * from './dom';
|
||||||
export * from './edges';
|
export * from './edges';
|
||||||
export * from './graph';
|
export * from './graph';
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
Transform,
|
Transform,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
XYZPosition,
|
XYZPosition,
|
||||||
|
ConnectionLookup,
|
||||||
|
EdgeBase,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { getDimensions, getHandleBounds } from './dom';
|
import { getDimensions, getHandleBounds } from './dom';
|
||||||
import { isNumeric } from './general';
|
import { isNumeric } from './general';
|
||||||
@@ -71,11 +73,13 @@ export function updateNodes<NodeType extends NodeBase>(
|
|||||||
defaults: {},
|
defaults: {},
|
||||||
}
|
}
|
||||||
): NodeType[] {
|
): NodeType[] {
|
||||||
|
const tmpLookup = new Map(nodeLookup);
|
||||||
|
nodeLookup.clear();
|
||||||
const parentNodes: ParentNodes = {};
|
const parentNodes: ParentNodes = {};
|
||||||
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
|
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
|
||||||
|
|
||||||
const nextNodes = nodes.map((n) => {
|
const nextNodes = nodes.map((n) => {
|
||||||
const currentStoreNode = nodeLookup.get(n.id);
|
const currentStoreNode = tmpLookup.get(n.id);
|
||||||
const node: NodeType = {
|
const node: NodeType = {
|
||||||
...options.defaults,
|
...options.defaults,
|
||||||
...n,
|
...n,
|
||||||
@@ -233,3 +237,23 @@ export function panBy({
|
|||||||
|
|
||||||
return transformChanged;
|
return transformChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateConnectionLookup(lookup: ConnectionLookup, edges: EdgeBase[]) {
|
||||||
|
lookup.clear();
|
||||||
|
|
||||||
|
edges.forEach(({ source, target, sourceHandle = null, targetHandle = null }) => {
|
||||||
|
if (source && target) {
|
||||||
|
const sourceKey = `${source}-source-${sourceHandle}`;
|
||||||
|
const targetKey = `${target}-target-${targetHandle}`;
|
||||||
|
|
||||||
|
const prevSource = lookup.get(sourceKey) || new Map();
|
||||||
|
const prevTarget = lookup.get(targetKey) || new Map();
|
||||||
|
const connection = { source, target, sourceHandle, targetHandle };
|
||||||
|
|
||||||
|
lookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection));
|
||||||
|
lookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return lookup;
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,12 +58,10 @@ export type XYHandleInstance = {
|
|||||||
type Result = {
|
type Result = {
|
||||||
handleDomNode: Element | null;
|
handleDomNode: Element | null;
|
||||||
isValid: boolean;
|
isValid: boolean;
|
||||||
connection: Connection;
|
connection: Connection | null;
|
||||||
endHandle: ConnectingHandle | null;
|
endHandle: ConnectingHandle | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const nullConnection: Connection = { source: null, target: null, sourceHandle: null, targetHandle: null };
|
|
||||||
|
|
||||||
const alwaysValid = () => true;
|
const alwaysValid = () => true;
|
||||||
|
|
||||||
let connectionStartHandle: ConnectingHandle | null = null;
|
let connectionStartHandle: ConnectingHandle | null = null;
|
||||||
@@ -197,7 +195,7 @@ function onPointerDown(
|
|||||||
return resetRecentHandle(prevActiveHandle, lib);
|
return resetRecentHandle(prevActiveHandle, lib);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connection.source !== connection.target && handleDomNode) {
|
if (connection?.source !== connection?.target && handleDomNode) {
|
||||||
resetRecentHandle(prevActiveHandle, lib);
|
resetRecentHandle(prevActiveHandle, lib);
|
||||||
prevActiveHandle = handleDomNode;
|
prevActiveHandle = handleDomNode;
|
||||||
handleDomNode.classList.add('connecting', `${lib}-flow__handle-connecting`);
|
handleDomNode.classList.add('connecting', `${lib}-flow__handle-connecting`);
|
||||||
@@ -269,7 +267,7 @@ function isValidHandle(
|
|||||||
const result: Result = {
|
const result: Result = {
|
||||||
handleDomNode: handleToCheck,
|
handleDomNode: handleToCheck,
|
||||||
isValid: false,
|
isValid: false,
|
||||||
connection: nullConnection,
|
connection: null,
|
||||||
endHandle: null,
|
endHandle: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -280,6 +278,10 @@ function isValidHandle(
|
|||||||
const connectable = handleToCheck.classList.contains('connectable');
|
const connectable = handleToCheck.classList.contains('connectable');
|
||||||
const connectableEnd = handleToCheck.classList.contains('connectableend');
|
const connectableEnd = handleToCheck.classList.contains('connectableend');
|
||||||
|
|
||||||
|
if (!handleNodeId) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
const connection: Connection = {
|
const connection: Connection = {
|
||||||
source: isTarget ? handleNodeId : fromNodeId,
|
source: isTarget ? handleNodeId : fromNodeId,
|
||||||
sourceHandle: isTarget ? handleId : fromHandleId,
|
sourceHandle: isTarget ? handleId : fromHandleId,
|
||||||
|
|||||||
Reference in New Issue
Block a user