refactor(edges): Make edge wrapper functional component
# What's changed? * remove edge-positions from graph edge * remove source and target nodes from graph edge * refactor edge wrapper into functional component and handle source/target positions internally
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
import { Component, CSSProperties, FunctionalComponent, VNode } from 'vue'
|
||||
import EdgeAnchor from './EdgeAnchor'
|
||||
import { ConnectionMode, EdgeComponent, EdgeMarkerType, EdgeTextProps, GraphNode, Position } from '~/types'
|
||||
import { getEdgePositions, getHandle, getMarkerId } from '~/utils'
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
type: EdgeComponent | Function | Object | false
|
||||
name: string
|
||||
source: string
|
||||
target: string
|
||||
sourceNode: GraphNode
|
||||
targetNode: GraphNode
|
||||
targetHandleId: string | null
|
||||
sourceHandleId: string | null
|
||||
selectable?: boolean
|
||||
updatable?: boolean
|
||||
label?: string | VNode | Component<EdgeTextProps> | Object
|
||||
data?: any
|
||||
labelStyle?: CSSProperties
|
||||
labelShowBg?: boolean
|
||||
labelBgStyle?: any
|
||||
labelBgPadding?: [number, number]
|
||||
labelBgBorderRadius?: number
|
||||
animated?: boolean
|
||||
selected?: boolean
|
||||
style: CSSProperties
|
||||
markerEnd?: EdgeMarkerType
|
||||
markerStart?: EdgeMarkerType
|
||||
connectionMode: ConnectionMode
|
||||
edgeUpdaterRadius: number
|
||||
}
|
||||
|
||||
const Wrapper: FunctionalComponent<Props> = function (
|
||||
{
|
||||
name,
|
||||
type,
|
||||
id,
|
||||
data,
|
||||
labelBgBorderRadius,
|
||||
labelBgPadding,
|
||||
labelBgStyle,
|
||||
labelStyle,
|
||||
labelShowBg,
|
||||
style,
|
||||
animated,
|
||||
label,
|
||||
updatable,
|
||||
target,
|
||||
source,
|
||||
sourceNode,
|
||||
targetNode,
|
||||
sourceHandleId,
|
||||
targetHandleId,
|
||||
selected,
|
||||
markerEnd,
|
||||
markerStart,
|
||||
connectionMode,
|
||||
edgeUpdaterRadius,
|
||||
},
|
||||
{ emit },
|
||||
) {
|
||||
if (!sourceNode || !targetNode) return null
|
||||
|
||||
let updating = $ref(false)
|
||||
|
||||
const onEdgeUpdaterMouseEnter = () => (updating = true)
|
||||
|
||||
const onEdgeUpdaterMouseOut = () => (updating = false)
|
||||
|
||||
const onEdgeUpdaterSourceMouseDown = () => emit('source-mousedown')
|
||||
|
||||
const onEdgeUpdaterTargetMouseDown = () => emit('target-mousedown')
|
||||
|
||||
let sourceNodeHandles
|
||||
if (connectionMode === ConnectionMode.Strict) {
|
||||
sourceNodeHandles = sourceNode.handleBounds.source
|
||||
} else {
|
||||
sourceNodeHandles = sourceNode.handleBounds.source ?? sourceNode.handleBounds.target
|
||||
}
|
||||
|
||||
const sourceHandle = getHandle(sourceNodeHandles, sourceHandleId)
|
||||
|
||||
let targetNodeHandles
|
||||
if (connectionMode === ConnectionMode.Strict) {
|
||||
targetNodeHandles = targetNode.handleBounds.target
|
||||
} else {
|
||||
targetNodeHandles = targetNode.handleBounds.target ?? targetNode.handleBounds.source
|
||||
}
|
||||
|
||||
const targetHandle = getHandle(targetNodeHandles, targetHandleId)
|
||||
|
||||
const sourcePosition = sourceHandle ? sourceHandle.position : Position.Bottom
|
||||
|
||||
const targetPosition = targetHandle ? targetHandle.position : Position.Top
|
||||
|
||||
const { sourceX, sourceY, targetY, targetX } = getEdgePositions(
|
||||
sourceNode,
|
||||
sourceHandle,
|
||||
sourcePosition,
|
||||
targetNode,
|
||||
targetHandle,
|
||||
targetPosition,
|
||||
)
|
||||
|
||||
return h('g', { class: [`vue-flow__node-${name}`, { updating }] }, [
|
||||
h(type as any, {
|
||||
id,
|
||||
sourceNode,
|
||||
targetNode,
|
||||
source,
|
||||
target,
|
||||
updatable,
|
||||
selected,
|
||||
animated,
|
||||
label,
|
||||
labelStyle,
|
||||
labelShowBg,
|
||||
labelBgStyle,
|
||||
labelBgPadding,
|
||||
labelBgBorderRadius,
|
||||
data,
|
||||
style,
|
||||
markerStart: `url(#${getMarkerId(markerStart)})`,
|
||||
markerEnd: `url(#${getMarkerId(markerEnd)})`,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourceHandleId,
|
||||
targetHandleId,
|
||||
}),
|
||||
updatable
|
||||
? [
|
||||
h(
|
||||
'g',
|
||||
{
|
||||
onMouseDown: onEdgeUpdaterSourceMouseDown,
|
||||
onMouseEnter: onEdgeUpdaterMouseEnter,
|
||||
onMouseOut: onEdgeUpdaterMouseOut,
|
||||
},
|
||||
h(EdgeAnchor, {
|
||||
position: sourcePosition,
|
||||
centerX: sourceX,
|
||||
centerY: sourceY,
|
||||
radius: edgeUpdaterRadius,
|
||||
}),
|
||||
),
|
||||
h(
|
||||
'g',
|
||||
{
|
||||
onMouseDown: onEdgeUpdaterTargetMouseDown,
|
||||
onMouseEnter: onEdgeUpdaterMouseEnter,
|
||||
onMouseOut: onEdgeUpdaterMouseOut,
|
||||
},
|
||||
h(EdgeAnchor, {
|
||||
position: targetPosition,
|
||||
centerX: targetX,
|
||||
centerY: targetY,
|
||||
radius: edgeUpdaterRadius,
|
||||
}),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
])
|
||||
}
|
||||
|
||||
Wrapper.props = [
|
||||
'name',
|
||||
'type',
|
||||
'id',
|
||||
'data',
|
||||
'labelBgBorderRadius',
|
||||
'labelBgPadding',
|
||||
'labelBgStyle',
|
||||
'labelStyle',
|
||||
'labelShowBg',
|
||||
'style',
|
||||
'animated',
|
||||
'label',
|
||||
'updatable',
|
||||
'selectable',
|
||||
'target',
|
||||
'source',
|
||||
'sourceNode',
|
||||
'targetNode',
|
||||
'sourceHandleId',
|
||||
'targetHandleId',
|
||||
'selected',
|
||||
'markerEnd',
|
||||
'markerStart',
|
||||
'connectionMode',
|
||||
'edgeUpdaterRadius',
|
||||
]
|
||||
|
||||
Wrapper.emits = ['source-mousedown', 'target-mousedown']
|
||||
|
||||
export default Wrapper
|
||||
@@ -1,14 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { ConnectionLine, EdgeWrapper } from '../../components'
|
||||
import { useVueFlow } from '../../composables'
|
||||
import { groupEdgesByZLevel } from '../../utils'
|
||||
import type { EdgeComponent, GraphEdge } from '../../types'
|
||||
import { CSSProperties } from 'vue'
|
||||
import EdgeWrapper from '../../components/Edges/Wrapper'
|
||||
import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue'
|
||||
import { useHandle, useVueFlow } from '../../composables'
|
||||
import { connectionExists, groupEdgesByZLevel } from '../../utils'
|
||||
import { EdgeComponent, GraphEdge } from '../../types'
|
||||
import { Slots } from '../../context'
|
||||
import MarkerDefinitions from './MarkerDefinitions.vue'
|
||||
|
||||
const slots = inject(Slots)
|
||||
|
||||
const {
|
||||
emits,
|
||||
connectionMode,
|
||||
edgeUpdaterRadius,
|
||||
onPaneReady,
|
||||
connectionNodeId,
|
||||
nodesConnectable,
|
||||
@@ -21,8 +26,14 @@ const {
|
||||
getNodes,
|
||||
getEdges,
|
||||
getEdgeTypes,
|
||||
setState,
|
||||
addSelectedEdges,
|
||||
noPanClassName,
|
||||
} = $(useVueFlow())
|
||||
|
||||
const selectable = (s?: boolean) => (typeof s === 'undefined' ? elementsSelectable : s)
|
||||
const updatable = (u?: boolean) => (typeof u === 'undefined' ? edgesUpdatable : u)
|
||||
|
||||
const sourceNode = $(
|
||||
controlledComputed($$(connectionNodeId), () => {
|
||||
if (connectionNodeId) return getNode(connectionNodeId)
|
||||
@@ -58,9 +69,9 @@ onPaneReady(() => {
|
||||
)
|
||||
})
|
||||
|
||||
const getType = (edge: GraphEdge) => {
|
||||
const name = edge.type || 'default'
|
||||
let edgeType = edge.template ?? getEdgeTypes[name]
|
||||
const getType = (type?: string, template?: GraphEdge['template']) => {
|
||||
const name = type || 'default'
|
||||
let edgeType = template ?? getEdgeTypes[name]
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
if (typeof edgeType === 'string') {
|
||||
@@ -75,12 +86,76 @@ const getType = (edge: GraphEdge) => {
|
||||
|
||||
const slot = slots?.[`edge-${name}`]
|
||||
if (!slot?.({})) {
|
||||
console.warn(`[vueflow]: Edge type "${edge.type}" not found and no edge-slot detected. Using fallback type "default".`)
|
||||
console.warn(`[vueflow]: Edge type "${type}" not found and no edge-slot detected. Using fallback type "default".`)
|
||||
return false
|
||||
}
|
||||
|
||||
return slot
|
||||
}
|
||||
|
||||
const onEdgeClick = (event: MouseEvent, edge: GraphEdge) => {
|
||||
const data = { event, edge }
|
||||
if (selectable(edge.selectable)) {
|
||||
setState({
|
||||
nodesSelectionActive: false,
|
||||
})
|
||||
|
||||
addSelectedEdges([edge])
|
||||
}
|
||||
emits.edgeClick(data)
|
||||
}
|
||||
|
||||
const onEdgeContextMenu = (event: MouseEvent, edge: GraphEdge) => emits.edgeContextMenu({ event, edge })
|
||||
|
||||
const onDoubleClick = (event: MouseEvent, edge: GraphEdge) => emits.edgeDoubleClick({ event, edge })
|
||||
|
||||
const onEdgeMouseEnter = (event: MouseEvent, edge: GraphEdge) => emits.edgeMouseEnter({ event, edge })
|
||||
|
||||
const onEdgeMouseMove = (event: MouseEvent, edge: GraphEdge) => emits.edgeMouseMove({ event, edge })
|
||||
|
||||
const onEdgeMouseLeave = (event: MouseEvent, edge: GraphEdge) => emits.edgeMouseLeave({ event, edge })
|
||||
|
||||
const onEdgeUpdaterSourceMouseDown = (event: MouseEvent, edge: GraphEdge) => handleEdgeUpdater(event, edge, true)
|
||||
|
||||
const onEdgeUpdaterTargetMouseDown = (event: MouseEvent, edge: GraphEdge) => handleEdgeUpdater(event, edge, false)
|
||||
|
||||
const { onMouseDown } = useHandle()
|
||||
|
||||
const handleEdgeUpdater = (event: MouseEvent, edge: GraphEdge, isSourceHandle: boolean) => {
|
||||
const nodeId = isSourceHandle ? edge.target : edge.source
|
||||
const handleId = (isSourceHandle ? edge.targetHandle : edge.sourceHandle) ?? ''
|
||||
|
||||
emits.edgeUpdateStart({ event, edge })
|
||||
|
||||
onMouseDown(
|
||||
event,
|
||||
handleId,
|
||||
nodeId,
|
||||
isSourceHandle,
|
||||
undefined,
|
||||
isSourceHandle ? 'target' : 'source',
|
||||
(connection) => {
|
||||
if (!connectionExists(edge, getEdges)) emits.edgeUpdate({ edge, connection })
|
||||
},
|
||||
() => emits.edgeUpdateEnd({ event, edge }),
|
||||
)
|
||||
}
|
||||
|
||||
const getClass = (edge: GraphEdge) => {
|
||||
const extraClass = edge.class instanceof Function ? edge.class(edge) : edge.class
|
||||
return [
|
||||
'vue-flow__edge',
|
||||
noPanClassName,
|
||||
{
|
||||
selected: edge.selected,
|
||||
animated: edge.animated,
|
||||
inactive: !selectable,
|
||||
},
|
||||
extraClass,
|
||||
]
|
||||
}
|
||||
|
||||
const getStyle = (edge: GraphEdge) => (edge.style instanceof Function ? edge.style(edge) : edge.style) as CSSProperties
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -97,11 +172,36 @@ export default {
|
||||
v-for="edge of group.edges"
|
||||
:id="edge.id"
|
||||
:key="edge.id"
|
||||
:edge="edge"
|
||||
:type="getType(edge.type, edge.template)"
|
||||
:name="edge.type || 'default'"
|
||||
:type="getType(edge)"
|
||||
:selectable="typeof edge.selectable === 'undefined' ? elementsSelectable : edge.selectable"
|
||||
:updatable="typeof edge.updatable === 'undefined' ? edgesUpdatable : edge.updatable"
|
||||
:source="edge.source"
|
||||
:target="edge.target"
|
||||
:target-handle-id="edge.targetHandle"
|
||||
:source-handle-id="edge.sourceHandle"
|
||||
:source-node="getNode(edge.source)"
|
||||
:target-node="getNode(edge.target)"
|
||||
:label="edge.label"
|
||||
:data="edge.data"
|
||||
:animated="edge.animated"
|
||||
:selectable="selectable(edge.selectable)"
|
||||
:updatable="updatable(edge.updatable)"
|
||||
:label-style="edge.labelStyle"
|
||||
:label-show-bg="edge.labelShowBg"
|
||||
:label-bg-style="edge.labelBgStyle"
|
||||
:label-bg-padding="edge.labelBgPadding"
|
||||
:label-bg-border-radius="edge.labelBgBorderRadius"
|
||||
:connection-mode="connectionMode"
|
||||
:edge-updater-radius="edgeUpdaterRadius"
|
||||
:style="getStyle(edge)"
|
||||
:class="getClass(edge)"
|
||||
@click="(e: MouseEvent) => onEdgeClick(e, edge)"
|
||||
@dblClick="(e: MouseEvent) => onDoubleClick(e, edge)"
|
||||
@contextmenu="(e: MouseEvent) => onEdgeContextMenu(e, edge)"
|
||||
@mouseenter="(e: MouseEvent) => onEdgeMouseEnter(e, edge)"
|
||||
@mousemove="(e: MouseEvent) => onEdgeMouseMove(e, edge)"
|
||||
@mouseleave="(e: MouseEvent) => onEdgeMouseLeave(e, edge)"
|
||||
@source-mousedown="(e: MouseEvent) => onEdgeUpdaterSourceMouseDown(e, edge)"
|
||||
@target-mousedown="(e: MouseEvent) => onEdgeUpdaterTargetMouseDown(e, edge)"
|
||||
/>
|
||||
<ConnectionLine v-if="connectionLineVisible && !!sourceNode" :source-node="sourceNode" />
|
||||
</g>
|
||||
|
||||
Reference in New Issue
Block a user