feat(nodes): add option to pass a valid target pos / valid source pos func to nodes
* edge updater cannot discern a valid connection by itself so we allow an option to pass a valid connection func to nodes which will then be used to determine if updating connection is valid * it is optional, by default all connections are valid Signed-off-by: Braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
@@ -1,9 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Position, Handle, Connection } from '~/index'
|
import { Position, Handle, ValidConnectionFunc } from '~/index'
|
||||||
|
|
||||||
const isValidConnection = (connection: Connection) => connection.target === 'B'
|
interface CustomInputProps {
|
||||||
|
isValidTargetPos: ValidConnectionFunc
|
||||||
|
}
|
||||||
|
const props = defineProps<CustomInputProps>()
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>Only connectable with B</div>
|
<div>Only connectable with B</div>
|
||||||
<Handle type="source" :position="Position.Right" :is-valid-connection="isValidConnection" />
|
<Handle type="source" :position="Position.Right" :is-valid-connection="props.isValidTargetPos" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Position, Handle, Connection, NodeProps } from '~/index'
|
import { Position, Handle, NodeProps, ValidConnectionFunc } from '~/index'
|
||||||
|
|
||||||
interface CustomNodeProps extends NodeProps {
|
interface CustomNodeProps extends NodeProps {
|
||||||
id: string
|
id: string
|
||||||
|
isValidSourcePos: ValidConnectionFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<CustomNodeProps>()
|
const props = defineProps<CustomNodeProps>()
|
||||||
|
|
||||||
const isValidConnection = (connection: Connection) => connection.target === 'B'
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Handle type="target" :position="Position.Left" :is-valid-connection="isValidConnection" />
|
<Handle type="target" :position="Position.Left" :is-valid-connection="props.isValidSourcePos" />
|
||||||
<div>{{ props.id }}</div>
|
<div>{{ props.id }}</div>
|
||||||
<Handle type="source" :position="Position.Right" :is-valid-connection="isValidConnection" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,24 +1,32 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import CustomInput from './CustomInput.vue'
|
import CustomInput from './CustomInput.vue'
|
||||||
import CustomNode from './CustomNode.vue'
|
import CustomNode from './CustomNode.vue'
|
||||||
import { VueFlow, addEdge, Connection, Elements, Edge, OnConnectStartParams, FlowInstance } from '~/index'
|
import { VueFlow, addEdge, Connection, Elements, OnConnectStartParams, FlowInstance } from '~/index'
|
||||||
|
|
||||||
import './validation.css'
|
import './validation.css'
|
||||||
|
|
||||||
const initialElements: Elements = [
|
const initialElements: Elements = [
|
||||||
{ id: '0', type: 'custominput', position: { x: 0, y: 150 } },
|
{ id: '0', type: 'custominput', position: { x: 0, y: 150 }, isValidTargetPos: (connection) => connection.target === 'B' },
|
||||||
{ id: 'A', type: 'customnode', position: { x: 250, y: 0 } },
|
{
|
||||||
{ id: 'B', type: 'customnode', position: { x: 250, y: 150 } },
|
id: 'A',
|
||||||
{ id: 'C', type: 'customnode', position: { x: 250, y: 300 } },
|
type: 'customnode',
|
||||||
|
position: { x: 250, y: 0 },
|
||||||
|
isValidSourcePos: (connection) => {
|
||||||
|
console.log(connection)
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ id: 'B', type: 'customnode', position: { x: 250, y: 150 }, isValidSourcePos: (connection) => connection.target === 'B' },
|
||||||
|
{ id: 'C', type: 'customnode', position: { x: 250, y: 300 }, isValidSourcePos: (connection) => connection.target === 'B' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const onLoad = (reactFlowInstance: FlowInstance) => reactFlowInstance.fitView()
|
const onLoad = (flowInstance: FlowInstance) => flowInstance.fitView()
|
||||||
const onConnectStart = ({ nodeId, handleType }: OnConnectStartParams) => console.log('on connect start', { nodeId, handleType })
|
const onConnectStart = ({ nodeId, handleType }: OnConnectStartParams) => console.log('on connect start', { nodeId, handleType })
|
||||||
const onConnectStop = (event: MouseEvent) => console.log('on connect stop', event)
|
const onConnectStop = (event: MouseEvent) => console.log('on connect stop', event)
|
||||||
const onConnectEnd = (event: MouseEvent) => console.log('on connect end', event)
|
const onConnectEnd = (event: MouseEvent) => console.log('on connect end', event)
|
||||||
|
|
||||||
const elements = ref<Elements>(initialElements)
|
const elements = ref<Elements>(initialElements)
|
||||||
const onConnect = (params: Connection | Edge) => {
|
const onConnect = (params: Connection) => {
|
||||||
console.log('on connect', params)
|
console.log('on connect', params)
|
||||||
elements.value = addEdge(params, elements.value)
|
elements.value = addEdge(params, elements.value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,16 +43,14 @@ const onEdgeUpdaterTargetMouseDown = (event: MouseEvent) => handleEdgeUpdater(ev
|
|||||||
const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => {
|
const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => {
|
||||||
const nodeId = isSourceHandle ? props.edge.target : props.edge.source
|
const nodeId = isSourceHandle ? props.edge.target : props.edge.source
|
||||||
const handleId = (isSourceHandle ? props.edge.targetHandle : props.edge.sourceHandle) ?? ''
|
const handleId = (isSourceHandle ? props.edge.targetHandle : props.edge.sourceHandle) ?? ''
|
||||||
const isValidConnection = () => true
|
|
||||||
const isTarget = isSourceHandle
|
|
||||||
|
|
||||||
store.hooks.edgeUpdateStart.trigger({ event, edge: props.edge })
|
store.hooks.edgeUpdateStart.trigger({ event, edge: props.edge })
|
||||||
handler(
|
handler(
|
||||||
event,
|
event,
|
||||||
handleId,
|
handleId,
|
||||||
nodeId,
|
nodeId,
|
||||||
isTarget,
|
isSourceHandle,
|
||||||
isValidConnection,
|
undefined,
|
||||||
isSourceHandle ? 'target' : 'source',
|
isSourceHandle ? 'target' : 'source',
|
||||||
(connection) => store.hooks.edgeUpdate.trigger({ edge: props.edge, connection }),
|
(connection) => store.hooks.edgeUpdate.trigger({ edge: props.edge, connection }),
|
||||||
() => store.hooks.edgeUpdateEnd.trigger({ event, edge: props.edge }),
|
() => store.hooks.edgeUpdateEnd.trigger({ event, edge: props.edge }),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const nodeId = inject(NodeId, '')
|
|||||||
|
|
||||||
const handler = useHandle()
|
const handler = useHandle()
|
||||||
const onMouseDownHandler = (event: MouseEvent) =>
|
const onMouseDownHandler = (event: MouseEvent) =>
|
||||||
handler(event, props.id, nodeId, props.type === 'target', props.isValidConnection ?? (() => true))
|
handler(event, props.id, nodeId, props.type === 'target', props.isValidConnection)
|
||||||
</script>
|
</script>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Handle from '../Handle/Handle.vue'
|
import Handle from '../Handle/Handle.vue'
|
||||||
import { NodeProps, Position } from '../../types'
|
import { NodeProps, Position, ValidConnectionFunc } from '../../types'
|
||||||
|
|
||||||
interface DefaultNodeProps extends NodeProps {
|
interface DefaultNodeProps extends NodeProps {
|
||||||
data?: NodeProps['data']
|
data?: NodeProps['data']
|
||||||
connectable?: NodeProps['connectable']
|
connectable?: NodeProps['connectable']
|
||||||
targetPosition?: NodeProps['targetPosition']
|
targetPosition?: NodeProps['targetPosition']
|
||||||
sourcePosition?: NodeProps['sourcePosition']
|
sourcePosition?: NodeProps['sourcePosition']
|
||||||
|
isValidTargetPos?: ValidConnectionFunc
|
||||||
|
isValidSourcePos?: ValidConnectionFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<DefaultNodeProps>(), {
|
const props = withDefaults(defineProps<DefaultNodeProps>(), {
|
||||||
@@ -23,10 +25,20 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Handle type="target" :position="props.targetPosition" :is-connectable="props.connectable" />
|
<Handle
|
||||||
|
type="target"
|
||||||
|
:position="props.targetPosition"
|
||||||
|
:is-connectable="props.connectable"
|
||||||
|
:is-valid-connection="props.isValidTargetPos"
|
||||||
|
/>
|
||||||
<slot v-bind="props">
|
<slot v-bind="props">
|
||||||
<component :is="props.data?.label" v-if="typeof props.data?.label !== 'string'" />
|
<component :is="props.data?.label" v-if="typeof props.data?.label !== 'string'" />
|
||||||
<span v-else v-html="props.data?.label"></span>
|
<span v-else v-html="props.data?.label"></span>
|
||||||
</slot>
|
</slot>
|
||||||
<Handle type="source" :position="props.sourcePosition" :is-connectable="props.connectable" />
|
<Handle
|
||||||
|
type="source"
|
||||||
|
:position="props.sourcePosition"
|
||||||
|
:is-connectable="props.connectable"
|
||||||
|
:is-valid-connection="props.isValidSourcePos"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Handle from '../Handle/Handle.vue'
|
import Handle from '../Handle/Handle.vue'
|
||||||
import { NodeProps, Position } from '../../types'
|
import { NodeProps, Position, ValidConnectionFunc } from '../../types'
|
||||||
|
|
||||||
interface InputNodeProps extends NodeProps {
|
interface InputNodeProps extends NodeProps {
|
||||||
data?: NodeProps['data']
|
data?: NodeProps['data']
|
||||||
connectable?: NodeProps['connectable']
|
connectable?: NodeProps['connectable']
|
||||||
sourcePosition?: NodeProps['sourcePosition']
|
sourcePosition?: NodeProps['sourcePosition']
|
||||||
|
isValidSourcePos?: ValidConnectionFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<InputNodeProps>(), {
|
const props = withDefaults(defineProps<InputNodeProps>(), {
|
||||||
@@ -25,5 +26,10 @@ export default {
|
|||||||
<component :is="props.data?.label" v-if="typeof props.data?.label !== 'string'" />
|
<component :is="props.data?.label" v-if="typeof props.data?.label !== 'string'" />
|
||||||
<span v-else v-html="props.data?.label"></span>
|
<span v-else v-html="props.data?.label"></span>
|
||||||
</slot>
|
</slot>
|
||||||
<Handle type="source" :position="props.sourcePosition" :connectable="props.connectable" />
|
<Handle
|
||||||
|
type="source"
|
||||||
|
:position="props.sourcePosition"
|
||||||
|
:connectable="props.connectable"
|
||||||
|
:is-valid-connection="props.isValidSourcePos"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -180,6 +180,8 @@ export default {
|
|||||||
sourcePosition: props.node.sourcePosition,
|
sourcePosition: props.node.sourcePosition,
|
||||||
targetPosition: props.node.targetPosition,
|
targetPosition: props.node.targetPosition,
|
||||||
dragging: props.node.__vf.isDragging,
|
dragging: props.node.__vf.isDragging,
|
||||||
|
isValidTargetPos: props.node.isValidTargetPos,
|
||||||
|
isValidSourcePos: props.node.isValidSourcePos,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
@@ -195,6 +197,8 @@ export default {
|
|||||||
sourcePosition: props.node.sourcePosition,
|
sourcePosition: props.node.sourcePosition,
|
||||||
targetPosition: props.node.targetPosition,
|
targetPosition: props.node.targetPosition,
|
||||||
dragging: props.node.__vf.isDragging,
|
dragging: props.node.__vf.isDragging,
|
||||||
|
isValidTargetPos: props.node.isValidTargetPos,
|
||||||
|
isValidSourcePos: props.node.isValidSourcePos,
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Handle from '../Handle/Handle.vue'
|
import Handle from '../Handle/Handle.vue'
|
||||||
import { NodeProps, Position } from '../../types'
|
import { NodeProps, Position, ValidConnectionFunc } from '../../types'
|
||||||
|
|
||||||
interface OutputNodeProps extends NodeProps {
|
interface OutputNodeProps extends NodeProps {
|
||||||
data?: NodeProps['data']
|
data?: NodeProps['data']
|
||||||
connectable?: NodeProps['connectable']
|
connectable?: NodeProps['connectable']
|
||||||
targetPosition?: NodeProps['targetPosition']
|
targetPosition?: NodeProps['targetPosition']
|
||||||
|
isValidTargetPos?: ValidConnectionFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<OutputNodeProps>(), {
|
const props = withDefaults(defineProps<OutputNodeProps>(), {
|
||||||
@@ -25,5 +26,10 @@ export default {
|
|||||||
<component :is="props.data?.label" v-if="typeof props.data?.label !== 'string'" />
|
<component :is="props.data?.label" v-if="typeof props.data?.label !== 'string'" />
|
||||||
<span v-else v-html="props.data?.label"></span>
|
<span v-else v-html="props.data?.label"></span>
|
||||||
</slot>
|
</slot>
|
||||||
<Handle type="source" :position="props.targetPosition" :is-connectable="props.connectable" />
|
<Handle
|
||||||
|
type="source"
|
||||||
|
:position="props.targetPosition"
|
||||||
|
:is-connectable="props.connectable"
|
||||||
|
:is-valid-connection="props.isValidTargetPos"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -73,9 +73,7 @@ export default (store = useStore()) =>
|
|||||||
handleId: ElementId,
|
handleId: ElementId,
|
||||||
nodeId: ElementId,
|
nodeId: ElementId,
|
||||||
isTarget: boolean,
|
isTarget: boolean,
|
||||||
isValidConnection: ValidConnectionFunc = () => {
|
isValidConnection?: ValidConnectionFunc,
|
||||||
return true
|
|
||||||
},
|
|
||||||
elementEdgeUpdaterType?: HandleType,
|
elementEdgeUpdaterType?: HandleType,
|
||||||
onEdgeUpdate?: (connection: Connection) => void,
|
onEdgeUpdate?: (connection: Connection) => void,
|
||||||
onEdgeUpdateEnd?: () => void,
|
onEdgeUpdateEnd?: () => void,
|
||||||
@@ -85,7 +83,11 @@ export default (store = useStore()) =>
|
|||||||
const doc = getHostForElement(event.target as HTMLElement)
|
const doc = getHostForElement(event.target as HTMLElement)
|
||||||
|
|
||||||
if (!doc) return
|
if (!doc) return
|
||||||
|
let validConnectFunc: ValidConnectionFunc = isValidConnection ?? (() => true)
|
||||||
|
if (!isValidConnection) {
|
||||||
|
const node = store.getNodes.find((n) => n.id === nodeId)
|
||||||
|
if (node) validConnectFunc = (!isTarget ? node.isValidTargetPos : node.isValidSourcePos) ?? (() => true)
|
||||||
|
}
|
||||||
const elementBelow = doc.elementFromPoint(event.clientX, event.clientY)
|
const elementBelow = doc.elementFromPoint(event.clientX, event.clientY)
|
||||||
const elementBelowIsTarget = elementBelow?.classList.contains('target')
|
const elementBelowIsTarget = elementBelow?.classList.contains('target')
|
||||||
const elementBelowIsSource = elementBelow?.classList.contains('source')
|
const elementBelowIsSource = elementBelow?.classList.contains('source')
|
||||||
@@ -104,7 +106,7 @@ export default (store = useStore()) =>
|
|||||||
connectionHandleId: handleId,
|
connectionHandleId: handleId,
|
||||||
connectionHandleType: handleType,
|
connectionHandleType: handleType,
|
||||||
})
|
})
|
||||||
store.hooks.connectStart.trigger({ event, params: { nodeId, handleId, handleType } })
|
store.hooks.connectStart.trigger({ event, nodeId, handleId, handleType })
|
||||||
|
|
||||||
function onMouseMove(event: MouseEvent) {
|
function onMouseMove(event: MouseEvent) {
|
||||||
store.connectionPosition.x = event.clientX - containerBounds.left
|
store.connectionPosition.x = event.clientX - containerBounds.left
|
||||||
@@ -116,7 +118,7 @@ export default (store = useStore()) =>
|
|||||||
isTarget,
|
isTarget,
|
||||||
nodeId,
|
nodeId,
|
||||||
handleId,
|
handleId,
|
||||||
isValidConnection,
|
validConnectFunc,
|
||||||
doc,
|
doc,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -140,9 +142,10 @@ export default (store = useStore()) =>
|
|||||||
isTarget,
|
isTarget,
|
||||||
nodeId,
|
nodeId,
|
||||||
handleId,
|
handleId,
|
||||||
isValidConnection,
|
validConnectFunc,
|
||||||
doc,
|
doc,
|
||||||
)
|
)
|
||||||
|
console.log(isValidConnection)
|
||||||
|
|
||||||
store.hooks.connectStop.trigger(event)
|
store.hooks.connectStop.trigger(event)
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ export enum ConnectionLineType {
|
|||||||
export interface Connection {
|
export interface Connection {
|
||||||
source: ElementId
|
source: ElementId
|
||||||
target: ElementId
|
target: ElementId
|
||||||
sourceHandle: ElementId
|
sourceHandle?: ElementId
|
||||||
targetHandle: ElementId
|
targetHandle?: ElementId
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConnectionLineProps = {
|
export type ConnectionLineProps = {
|
||||||
@@ -28,9 +28,9 @@ export type ConnectionLineProps = {
|
|||||||
|
|
||||||
export type OnConnectFunc = (connection: Connection) => void
|
export type OnConnectFunc = (connection: Connection) => void
|
||||||
export type OnConnectStartParams = {
|
export type OnConnectStartParams = {
|
||||||
nodeId: ElementId | undefined
|
nodeId?: ElementId
|
||||||
handleId: ElementId | undefined
|
handleId?: ElementId
|
||||||
handleType: HandleType | undefined
|
handleType?: HandleType
|
||||||
}
|
}
|
||||||
export type OnConnectStartFunc = (event: MouseEvent, params: OnConnectStartParams) => void
|
export type OnConnectStartFunc = (event: MouseEvent, params: OnConnectStartParams) => void
|
||||||
export type OnConnectStopFunc = (event: MouseEvent) => void
|
export type OnConnectStopFunc = (event: MouseEvent) => void
|
||||||
|
|||||||
+1
-2
@@ -21,8 +21,7 @@ export interface FlowEvents {
|
|||||||
connect: Connection
|
connect: Connection
|
||||||
connectStart: {
|
connectStart: {
|
||||||
event: MouseEvent
|
event: MouseEvent
|
||||||
params: OnConnectStartParams
|
} & { [key in keyof OnConnectStartParams]: OnConnectStartParams[key] }
|
||||||
}
|
|
||||||
connectStop: MouseEvent
|
connectStop: MouseEvent
|
||||||
connectEnd: MouseEvent
|
connectEnd: MouseEvent
|
||||||
load: FlowInstance
|
load: FlowInstance
|
||||||
|
|||||||
+5
-1
@@ -1,7 +1,7 @@
|
|||||||
import { Component, CSSProperties, DefineComponent } from 'vue'
|
import { Component, CSSProperties, DefineComponent } from 'vue'
|
||||||
import { DraggableOptions } from '@braks/revue-draggable'
|
import { DraggableOptions } from '@braks/revue-draggable'
|
||||||
import { XYPosition, ElementId, Position, SnapGrid } from './flow'
|
import { XYPosition, ElementId, Position, SnapGrid } from './flow'
|
||||||
import { HandleElement } from './components'
|
import { HandleElement, ValidConnectionFunc } from './components'
|
||||||
|
|
||||||
export interface VFInternals {
|
export interface VFInternals {
|
||||||
isDragging?: boolean
|
isDragging?: boolean
|
||||||
@@ -24,6 +24,8 @@ export interface NodeProps<T = any> {
|
|||||||
targetPosition?: Position
|
targetPosition?: Position
|
||||||
sourcePosition?: Position
|
sourcePosition?: Position
|
||||||
dragging?: boolean
|
dragging?: boolean
|
||||||
|
isValidTargetPos?: ValidConnectionFunc
|
||||||
|
isValidSourcePos?: ValidConnectionFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NodeComponent = Component<NodeProps> | DefineComponent<NodeProps, any, any, any, any> | string
|
export type NodeComponent = Component<NodeProps> | DefineComponent<NodeProps, any, any, any, any> | string
|
||||||
@@ -46,6 +48,8 @@ export interface Node<T = any> {
|
|||||||
connectable?: boolean
|
connectable?: boolean
|
||||||
dragHandle?: string
|
dragHandle?: string
|
||||||
snapGrid?: SnapGrid
|
snapGrid?: SnapGrid
|
||||||
|
isValidTargetPos?: ValidConnectionFunc
|
||||||
|
isValidSourcePos?: ValidConnectionFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GraphNode<T = any> extends Node<T> {
|
export interface GraphNode<T = any> extends Node<T> {
|
||||||
|
|||||||
Reference in New Issue
Block a user