Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): add useNodeConnections composable #1728

Merged
merged 5 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading