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>
|
<script lang="ts" setup>
|
||||||
import { ConnectionLine, EdgeWrapper } from '../../components'
|
import { CSSProperties } from 'vue'
|
||||||
import { useVueFlow } from '../../composables'
|
import EdgeWrapper from '../../components/Edges/Wrapper'
|
||||||
import { groupEdgesByZLevel } from '../../utils'
|
import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue'
|
||||||
import type { EdgeComponent, GraphEdge } from '../../types'
|
import { useHandle, useVueFlow } from '../../composables'
|
||||||
|
import { connectionExists, groupEdgesByZLevel } from '../../utils'
|
||||||
|
import { EdgeComponent, GraphEdge } from '../../types'
|
||||||
import { Slots } from '../../context'
|
import { Slots } from '../../context'
|
||||||
import MarkerDefinitions from './MarkerDefinitions.vue'
|
import MarkerDefinitions from './MarkerDefinitions.vue'
|
||||||
|
|
||||||
const slots = inject(Slots)
|
const slots = inject(Slots)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
emits,
|
||||||
|
connectionMode,
|
||||||
|
edgeUpdaterRadius,
|
||||||
onPaneReady,
|
onPaneReady,
|
||||||
connectionNodeId,
|
connectionNodeId,
|
||||||
nodesConnectable,
|
nodesConnectable,
|
||||||
@@ -21,8 +26,14 @@ const {
|
|||||||
getNodes,
|
getNodes,
|
||||||
getEdges,
|
getEdges,
|
||||||
getEdgeTypes,
|
getEdgeTypes,
|
||||||
|
setState,
|
||||||
|
addSelectedEdges,
|
||||||
|
noPanClassName,
|
||||||
} = $(useVueFlow())
|
} = $(useVueFlow())
|
||||||
|
|
||||||
|
const selectable = (s?: boolean) => (typeof s === 'undefined' ? elementsSelectable : s)
|
||||||
|
const updatable = (u?: boolean) => (typeof u === 'undefined' ? edgesUpdatable : u)
|
||||||
|
|
||||||
const sourceNode = $(
|
const sourceNode = $(
|
||||||
controlledComputed($$(connectionNodeId), () => {
|
controlledComputed($$(connectionNodeId), () => {
|
||||||
if (connectionNodeId) return getNode(connectionNodeId)
|
if (connectionNodeId) return getNode(connectionNodeId)
|
||||||
@@ -58,9 +69,9 @@ onPaneReady(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const getType = (edge: GraphEdge) => {
|
const getType = (type?: string, template?: GraphEdge['template']) => {
|
||||||
const name = edge.type || 'default'
|
const name = type || 'default'
|
||||||
let edgeType = edge.template ?? getEdgeTypes[name]
|
let edgeType = template ?? getEdgeTypes[name]
|
||||||
const instance = getCurrentInstance()
|
const instance = getCurrentInstance()
|
||||||
|
|
||||||
if (typeof edgeType === 'string') {
|
if (typeof edgeType === 'string') {
|
||||||
@@ -75,12 +86,76 @@ const getType = (edge: GraphEdge) => {
|
|||||||
|
|
||||||
const slot = slots?.[`edge-${name}`]
|
const slot = slots?.[`edge-${name}`]
|
||||||
if (!slot?.({})) {
|
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 false
|
||||||
}
|
}
|
||||||
|
|
||||||
return slot
|
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>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -97,11 +172,36 @@ export default {
|
|||||||
v-for="edge of group.edges"
|
v-for="edge of group.edges"
|
||||||
:id="edge.id"
|
:id="edge.id"
|
||||||
:key="edge.id"
|
:key="edge.id"
|
||||||
:edge="edge"
|
:type="getType(edge.type, edge.template)"
|
||||||
:name="edge.type || 'default'"
|
:name="edge.type || 'default'"
|
||||||
:type="getType(edge)"
|
:source="edge.source"
|
||||||
:selectable="typeof edge.selectable === 'undefined' ? elementsSelectable : edge.selectable"
|
:target="edge.target"
|
||||||
:updatable="typeof edge.updatable === 'undefined' ? edgesUpdatable : edge.updatable"
|
: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" />
|
<ConnectionLine v-if="connectionLineVisible && !!sourceNode" :source-node="sourceNode" />
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
Reference in New Issue
Block a user