Skip to content

Commit

Permalink
feat(core): add useNodeConnections composable (#1728)
Browse files Browse the repository at this point in the history
* feat(core): add `useNodeConnections` composable

* chore(core): export `useNodeConnections`

* chore(docs): update math example

* docs: add guide for `useNodeConnections`

* chore(changeset): add
  • Loading branch information
bcakmakoglu committed Jan 9, 2025
1 parent a8f7e36 commit d4a6910
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/gentle-owls-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@vue-flow/core": minor
---

Add `useNodeConnections` composable
12 changes: 6 additions & 6 deletions docs/examples/math/ResultNode.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup>
import { computed } from 'vue'
import { Handle, Position, useHandleConnections, useNodesData, useVueFlow } from '@vue-flow/core'
import { Handle, Position, useHandleConnections, useNodeConnections, useNodesData } from '@vue-flow/core'
defineProps(['id'])
Expand All @@ -11,8 +11,6 @@ const mathFunctions = {
'/': (a, b) => a / b,
}
const { getConnectedEdges } = useVueFlow()
// Get the source connections of the result node. In this example it's only one operator node.
const sourceConnections = useHandleConnections({
// type target means all connections where *this* node is the target
Expand All @@ -21,9 +19,10 @@ const sourceConnections = useHandleConnections({
})
// Get the source connections of the operator node
const operatorSourceConnections = computed(() =>
getConnectedEdges(sourceConnections.value[0].source).filter((e) => e.source !== sourceConnections.value[0].source),
)
const operatorSourceConnections = useNodeConnections({
type: 'target',
nodeId: () => sourceConnections.value[0]?.source,
})
const operatorData = useNodesData(() => sourceConnections.value.map((connection) => connection.source))
Expand All @@ -50,6 +49,7 @@ const result = computed(() => {
</script>
<template>
{{ operatorSourceConnections }}
<div class="calculation">
<template v-for="(value, i) in valueData" :key="`${value.id}-${value.data}`">
<span>
Expand Down
41 changes: 37 additions & 4 deletions docs/src/guide/composables.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,14 @@ onInit((instance) => {

## [useHandleConnections](/typedocs/functions/useHandleConnections)

`useHandleConnections` provides you with an array of connections that are connected to the node you pass to it.
`useHandleConnections` provides you with an array of connections that are connected to specific `<Handle>`.

```ts
import { useHandleConnections } from '@vue-flow/core'
import { type HandleConnection, useHandleConnections } from '@vue-flow/core'

// get all connections where this node is the target (incoming connections)
const targetConnections = useHandleConnections({
// type is required
type: 'target',
})

Expand All @@ -89,10 +90,42 @@ const connections = useHandleConnections({
id: 'handle-1', // you can explicitly pass a handle id if there are multiple handles of the same type
nodeId: '1', // you can explicitly pass a node id, otherwise it's used from the `NodeId injection
type: 'target',
onConnect: (connections: Connection[]) => {
onConnect: (connections: HandleConnection[]) => {
// do something with the connections
},
onDisconnect: (connections: Connection[]) => {
onDisconnect: (connections: HandleConnection[]) => {
// do something with the connections
},
})
```

## [useNodeConnections](/typedocs/functions/useNodeConnections)

`useNodeConnections` provides you with an array of connections that are connected to a specific node.
This composable is especially useful when you want to get all connections (of either type `source` or `target`) of a node
instead of just the connections of a specific `<Handle>`.

```ts
import { type HandleConnection, useNodeConnections } from '@vue-flow/core'

// get all connections where this node is the target (incoming connections)
const targetConnections = useNodeConnections({
// type is required
type: 'target',
})

// get all connections where this node is the source (outgoing connections)
const sourceConnections = useNodeConnections({
type: 'source',
})

const connections = useNodeConnections({
nodeId: '1', // you can explicitly pass a node id, otherwise it's used from the `NodeId injection
type: 'target',
onConnect: (connections: HandleConnection[]) => {
// do something with the connections
},
onDisconnect: (connections: HandleConnection[]) => {
// do something with the connections
},
})
Expand Down
11 changes: 5 additions & 6 deletions examples/vite/src/Math/ResultNode.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
<script setup lang="ts">
import type { GraphNode } from '@vue-flow/core'
import { Handle, Position, useHandleConnections, useNodesData, useVueFlow } from '@vue-flow/core'
import { Handle, Position, useHandleConnections, useNodeConnections, useNodesData } from '@vue-flow/core'
import type { OperatorNodeData, ValueNodeData } from './types'
import { mathFunctions } from './utils'
defineProps<{ id: string }>()
const { getConnectedEdges } = useVueFlow()
// Get the source connections of the result node. In this example it's only one operator node.
const sourceConnections = useHandleConnections({
// type target means all connections where *this* node is the target
Expand All @@ -16,9 +14,10 @@ const sourceConnections = useHandleConnections({
})
// Get the source connections of the operator node
const operatorSourceConnections = computed(() =>
getConnectedEdges(sourceConnections.value[0].source).filter((e) => e.source !== sourceConnections.value[0].source),
)
const operatorSourceConnections = useNodeConnections({
type: 'target',
nodeId: () => sourceConnections.value[0]?.source,
})
const operatorData = useNodesData<GraphNode<OperatorNodeData>>(() =>
sourceConnections.value.map((connection) => connection.source),
Expand Down
120 changes: 120 additions & 0 deletions packages/core/src/composables/useNodeConnections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import type { ComputedRef, MaybeRefOrGetter } from 'vue'
import { computed, ref, toRef, toValue, watch } from 'vue'
import type { HandleConnection, HandleElement, HandleType } from '../types'
import { useNodeId } from './useNodeId'
import { useVueFlow } from './useVueFlow'

export interface UseNodeConnectionsParams {
type: MaybeRefOrGetter<HandleType>
nodeId?: MaybeRefOrGetter<string | null>
onConnect?: (connections: HandleConnection[]) => void
onDisconnect?: (connections: HandleConnection[]) => void
}

/**
* Composable that returns existing connections of a node by handle type.
* This is useful when you want to get all connections of a node by a specific handle type.
*
* @public
* @param params
* @param params.type - handle type `source` or `target`
* @param params.nodeId - node id - if not provided, the node id from the `useNodeId` (meaning, the context-based injection) is used
* @param params.onConnect - gets called when a connection is created
* @param params.onDisconnect - gets called when a connection is removed
*
* @returns An array of connections
*/
export function useNodeConnections(params: UseNodeConnectionsParams): ComputedRef<HandleConnection[]> {
const { type, nodeId, onConnect, onDisconnect } = params

const { connectionLookup, findNode } = useVueFlow()

const _nodeId = useNodeId()

const currentNodeId = toRef(() => toValue(nodeId) ?? _nodeId)

const handleType = toRef(() => toValue(type))

const node = computed(() => findNode(currentNodeId.value))

const handleIds = computed(() => {
if (!node.value) {
return []
}

const handles: HandleElement['id'][] = []
for (const handle of node.value?.handleBounds?.[handleType.value] ?? []) {
handles.push(handle.id)
}

return handles
})

const prevConnections = ref<Map<string, HandleConnection> | null>(null)

const connectionsFromLookup = computed(() => {
const nodeConnections = [] as Map<string, HandleConnection>[]

for (const handleId of handleIds.value) {
const connectionMap = connectionLookup.value.get(`${currentNodeId.value}-${handleType.value}-${handleId}`)
if (connectionMap) {
nodeConnections.push(connectionMap)
}
}

return nodeConnections
})

watch(
[connectionsFromLookup, () => typeof onConnect !== 'undefined', () => typeof onDisconnect !== 'undefined'],
([currentConnections]) => {
if (!currentConnections) {
return
}

const newConnections = new Map<string, HandleConnection>()

for (const connectionMap of currentConnections) {
for (const [key, connection] of connectionMap) {
newConnections.set(key, connection)
}
}

if (!prevConnections.value) {
prevConnections.value = new Map(newConnections)
return
}

const prevConnectionsValue = prevConnections.value

const addedConnections = Array.from(newConnections.keys()).filter((key) => !prevConnectionsValue.has(key))

const removedConnections = Array.from(prevConnectionsValue.keys()).filter((key) => !newConnections.has(key))

if (addedConnections.length && onConnect) {
const added = addedConnections.map((key) => newConnections.get(key)!)
onConnect(added)
}

if (removedConnections.length && onDisconnect) {
const removed = removedConnections.map((key) => prevConnectionsValue.get(key)!)
onDisconnect(removed)
}

prevConnections.value = new Map(newConnections)
},
{ immediate: true },
)

return computed(() => {
const connections = [] as HandleConnection[]

for (const connectionMap of connectionsFromLookup.value) {
for (const connection of connectionMap.values()) {
connections.push(connection)
}
}

return connections
})
}
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export { useGetPointerPosition } from './composables/useGetPointerPosition'
export { useNodeId } from './composables/useNodeId'
export { useConnection } from './composables/useConnection'
export { useHandleConnections } from './composables/useHandleConnections'
export { useNodeConnections } from './composables/useNodeConnections'
export { useNodesData } from './composables/useNodesData'
export { useEdgesData } from './composables/useEdgesData'
export { useNodesInitialized } from './composables/useNodesInitialized'
Expand Down

0 comments on commit d4a6910

Please sign in to comment.