feat(edges): add contextual edge events
# What's changed? * allow event handlers to be passed in initial edge definition * allow event listeners to be forwarded to edge components
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import type { CSSProperties, Component, FunctionalComponent, VNode } from 'vue'
|
import type { CSSProperties, Component, FunctionalComponent, VNode } from 'vue'
|
||||||
import EdgeAnchor from './EdgeAnchor'
|
import EdgeAnchor from './EdgeAnchor'
|
||||||
import type { EdgeComponent, EdgeMarkerType, EdgeTextProps, GraphNode } from '~/types'
|
import type { EdgeComponent, EdgeEventsOn, EdgeMarkerType, EdgeTextProps, GraphNode } from '~/types'
|
||||||
import { ConnectionMode, Position } from '~/types'
|
import { ConnectionMode, Position } from '~/types'
|
||||||
import { getEdgePositions, getHandle, getMarkerId } from '~/utils'
|
import { getEdgePositions, getHandle, getMarkerId } from '~/utils'
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ interface Props {
|
|||||||
updatable?: boolean
|
updatable?: boolean
|
||||||
label?: string | VNode | Component<EdgeTextProps> | Object
|
label?: string | VNode | Component<EdgeTextProps> | Object
|
||||||
data?: any
|
data?: any
|
||||||
|
events: EdgeEventsOn
|
||||||
labelStyle?: CSSProperties
|
labelStyle?: CSSProperties
|
||||||
labelShowBg?: boolean
|
labelShowBg?: boolean
|
||||||
labelBgStyle?: any
|
labelBgStyle?: any
|
||||||
@@ -38,6 +39,7 @@ const Wrapper: FunctionalComponent<Props> = function (
|
|||||||
type,
|
type,
|
||||||
id,
|
id,
|
||||||
data,
|
data,
|
||||||
|
events,
|
||||||
labelBgBorderRadius,
|
labelBgBorderRadius,
|
||||||
labelBgPadding,
|
labelBgPadding,
|
||||||
labelBgStyle,
|
labelBgStyle,
|
||||||
@@ -140,6 +142,7 @@ const Wrapper: FunctionalComponent<Props> = function (
|
|||||||
labelBgPadding,
|
labelBgPadding,
|
||||||
labelBgBorderRadius,
|
labelBgBorderRadius,
|
||||||
data,
|
data,
|
||||||
|
events,
|
||||||
style,
|
style,
|
||||||
markerStart: `url(#${getMarkerId(markerStart)})`,
|
markerStart: `url(#${getMarkerId(markerStart)})`,
|
||||||
markerEnd: `url(#${getMarkerId(markerEnd)})`,
|
markerEnd: `url(#${getMarkerId(markerEnd)})`,
|
||||||
@@ -193,6 +196,7 @@ Wrapper.props = [
|
|||||||
'type',
|
'type',
|
||||||
'id',
|
'id',
|
||||||
'data',
|
'data',
|
||||||
|
'events',
|
||||||
'labelBgBorderRadius',
|
'labelBgBorderRadius',
|
||||||
'labelBgPadding',
|
'labelBgPadding',
|
||||||
'labelBgStyle',
|
'labelBgStyle',
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ export { default as useWindow } from './useWindow'
|
|||||||
export { default as useVueFlow } from './useVueFlow'
|
export { default as useVueFlow } from './useVueFlow'
|
||||||
export { default as useDrag } from './useDrag'
|
export { default as useDrag } from './useDrag'
|
||||||
export { default as useNodeHooks } from './useNodeHooks'
|
export { default as useNodeHooks } from './useNodeHooks'
|
||||||
|
export { default as useEdgeHooks } from './useEdgeHooks'
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import type { EdgeEventsEmit, EdgeEventsOn, GraphEdge, VueFlowStore } from '~/types'
|
||||||
|
|
||||||
|
const createEdgeHooks = () => ({
|
||||||
|
doubleClick: createEventHook(),
|
||||||
|
click: createEventHook(),
|
||||||
|
mouseEnter: createEventHook(),
|
||||||
|
mouseMove: createEventHook(),
|
||||||
|
mouseLeave: createEventHook(),
|
||||||
|
contextMenu: createEventHook(),
|
||||||
|
updateStart: createEventHook(),
|
||||||
|
update: createEventHook(),
|
||||||
|
updateEnd: createEventHook(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function useEdgeHooks(edge: GraphEdge, emits: VueFlowStore['emits']): { emit: EdgeEventsEmit; on: EdgeEventsOn } {
|
||||||
|
const edgeHooks = createEdgeHooks()
|
||||||
|
|
||||||
|
edgeHooks.doubleClick.on((event) => {
|
||||||
|
emits.edgeDoubleClick(event)
|
||||||
|
edge.events?.doubleClick?.(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
edgeHooks.click.on((event) => {
|
||||||
|
emits.edgeClick(event)
|
||||||
|
edge.events?.click?.(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
edgeHooks.mouseEnter.on((event) => {
|
||||||
|
emits.edgeMouseEnter(event)
|
||||||
|
edge.events?.mouseEnter?.(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
edgeHooks.mouseMove.on((event) => {
|
||||||
|
emits.edgeMouseMove(event)
|
||||||
|
edge.events?.mouseMove?.(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
edgeHooks.mouseLeave.on((event) => {
|
||||||
|
emits.edgeMouseLeave(event)
|
||||||
|
edge.events?.mouseLeave?.(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
edgeHooks.contextMenu.on((event) => {
|
||||||
|
emits.edgeContextMenu(event)
|
||||||
|
edge.events?.contextMenu?.(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
edgeHooks.updateStart.on((event) => {
|
||||||
|
emits.edgeUpdateStart(event)
|
||||||
|
edge.events?.updateStart?.(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
edgeHooks.update.on((event) => {
|
||||||
|
emits.edgeUpdate(event)
|
||||||
|
edge.events?.update?.(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
edgeHooks.updateEnd.on((event) => {
|
||||||
|
emits.edgeUpdateEnd(event)
|
||||||
|
edge.events?.updateEnd?.(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
return Object.entries(edgeHooks).reduce(
|
||||||
|
(hooks, [key, value]) => {
|
||||||
|
hooks.emit[key as keyof EdgeEventsEmit] = value.trigger
|
||||||
|
hooks.on[key as keyof EdgeEventsOn] = value.on
|
||||||
|
|
||||||
|
return hooks
|
||||||
|
},
|
||||||
|
{ emit: {} as EdgeEventsEmit, on: {} as EdgeEventsOn },
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { CSSProperties } from 'vue'
|
import type { CSSProperties } from 'vue'
|
||||||
import EdgeWrapper from '../../components/Edges/Wrapper'
|
import EdgeWrapper from '../../components/Edges/Wrapper'
|
||||||
import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue'
|
import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue'
|
||||||
import { useHandle, useVueFlow } from '../../composables'
|
import { useEdgeHooks, useHandle, useVueFlow } from '../../composables'
|
||||||
import { connectionExists, groupEdgesByZLevel } from '../../utils'
|
import { connectionExists, groupEdgesByZLevel } from '../../utils'
|
||||||
import type { EdgeComponent, GraphEdge } from '../../types'
|
import type { EdgeComponent, GraphEdge } from '../../types'
|
||||||
import { Slots } from '../../context'
|
import { Slots } from '../../context'
|
||||||
@@ -55,6 +55,8 @@ const connectionLineVisible = $(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const hooks = $ref<Record<string, ReturnType<typeof useEdgeHooks>>>({})
|
||||||
|
|
||||||
let groups = $ref<ReturnType<typeof groupEdgesByZLevel>>([])
|
let groups = $ref<ReturnType<typeof groupEdgesByZLevel>>([])
|
||||||
|
|
||||||
const scope = effectScope()
|
const scope = effectScope()
|
||||||
@@ -64,6 +66,12 @@ onPaneReady(() => {
|
|||||||
watch(
|
watch(
|
||||||
[$$(getSelectedNodes), $$(getEdges), $$(getNodes)],
|
[$$(getSelectedNodes), $$(getEdges), $$(getNodes)],
|
||||||
() => {
|
() => {
|
||||||
|
getEdges.reduce((acc, edge) => {
|
||||||
|
if (hooks[edge.id]) return acc
|
||||||
|
hooks[edge.id] = useEdgeHooks(edge, emits)
|
||||||
|
return acc
|
||||||
|
}, hooks)
|
||||||
|
|
||||||
if (elevateEdgesOnSelect) {
|
if (elevateEdgesOnSelect) {
|
||||||
nextTick(() => (groups = groupEdgesByZLevel(getEdges, getNode)))
|
nextTick(() => (groups = groupEdgesByZLevel(getEdges, getNode)))
|
||||||
} else {
|
} else {
|
||||||
@@ -117,18 +125,18 @@ const onEdgeClick = (event: MouseEvent, edge: GraphEdge) => {
|
|||||||
|
|
||||||
addSelectedEdges([edge])
|
addSelectedEdges([edge])
|
||||||
}
|
}
|
||||||
emits.edgeClick(data)
|
hooks[edge.id].emit.click(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onEdgeContextMenu = (event: MouseEvent, edge: GraphEdge) => emits.edgeContextMenu({ event, edge })
|
const onEdgeContextMenu = (event: MouseEvent, edge: GraphEdge) => hooks[edge.id].emit.contextMenu({ event, edge })
|
||||||
|
|
||||||
const onDoubleClick = (event: MouseEvent, edge: GraphEdge) => emits.edgeDoubleClick({ event, edge })
|
const onDoubleClick = (event: MouseEvent, edge: GraphEdge) => hooks[edge.id].emit.doubleClick({ event, edge })
|
||||||
|
|
||||||
const onEdgeMouseEnter = (event: MouseEvent, edge: GraphEdge) => emits.edgeMouseEnter({ event, edge })
|
const onEdgeMouseEnter = (event: MouseEvent, edge: GraphEdge) => hooks[edge.id].emit.mouseEnter({ event, edge })
|
||||||
|
|
||||||
const onEdgeMouseMove = (event: MouseEvent, edge: GraphEdge) => emits.edgeMouseMove({ event, edge })
|
const onEdgeMouseMove = (event: MouseEvent, edge: GraphEdge) => hooks[edge.id].emit.mouseMove({ event, edge })
|
||||||
|
|
||||||
const onEdgeMouseLeave = (event: MouseEvent, edge: GraphEdge) => emits.edgeMouseLeave({ event, edge })
|
const onEdgeMouseLeave = (event: MouseEvent, edge: GraphEdge) => hooks[edge.id].emit.mouseLeave({ event, edge })
|
||||||
|
|
||||||
const onEdgeUpdaterSourceMouseDown = (event: MouseEvent, edge: GraphEdge) => handleEdgeUpdater(event, edge, true)
|
const onEdgeUpdaterSourceMouseDown = (event: MouseEvent, edge: GraphEdge) => handleEdgeUpdater(event, edge, true)
|
||||||
|
|
||||||
@@ -140,7 +148,7 @@ const handleEdgeUpdater = (event: MouseEvent, edge: GraphEdge, isSourceHandle: b
|
|||||||
const nodeId = isSourceHandle ? edge.target : edge.source
|
const nodeId = isSourceHandle ? edge.target : edge.source
|
||||||
const handleId = (isSourceHandle ? edge.targetHandle : edge.sourceHandle) ?? ''
|
const handleId = (isSourceHandle ? edge.targetHandle : edge.sourceHandle) ?? ''
|
||||||
|
|
||||||
emits.edgeUpdateStart({ event, edge })
|
hooks[edge.id].emit.updateStart({ event, edge })
|
||||||
|
|
||||||
onMouseDown(
|
onMouseDown(
|
||||||
event,
|
event,
|
||||||
@@ -150,9 +158,9 @@ const handleEdgeUpdater = (event: MouseEvent, edge: GraphEdge, isSourceHandle: b
|
|||||||
undefined,
|
undefined,
|
||||||
isSourceHandle ? 'target' : 'source',
|
isSourceHandle ? 'target' : 'source',
|
||||||
(connection) => {
|
(connection) => {
|
||||||
if (!connectionExists(connection, getEdges)) emits.edgeUpdate({ edge, connection })
|
if (!connectionExists(connection, getEdges)) hooks[edge.id].emit.update({ edge, connection })
|
||||||
},
|
},
|
||||||
() => emits.edgeUpdateEnd({ event, edge }),
|
() => hooks[edge.id].emit.updateEnd({ event, edge }),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +196,7 @@ export default {
|
|||||||
:target-node="getNode(edge.target)"
|
:target-node="getNode(edge.target)"
|
||||||
:label="edge.label"
|
:label="edge.label"
|
||||||
:data="edge.data"
|
:data="edge.data"
|
||||||
|
:events="{ ...edge.events, ...hooks[edge.id].on }"
|
||||||
:animated="edge.animated"
|
:animated="edge.animated"
|
||||||
:selectable="selectable(edge.selectable)"
|
:selectable="selectable(edge.selectable)"
|
||||||
:selected="edge.selected"
|
:selected="edge.selected"
|
||||||
@@ -204,7 +213,7 @@ export default {
|
|||||||
:style="getStyle(edge)"
|
:style="getStyle(edge)"
|
||||||
:class="getClass(edge)"
|
:class="getClass(edge)"
|
||||||
@click="onEdgeClick($event, edge)"
|
@click="onEdgeClick($event, edge)"
|
||||||
@dbl-click="onDoubleClick($event, edge)"
|
@dblclick="onDoubleClick($event, edge)"
|
||||||
@contextmenu="onEdgeContextMenu($event, edge)"
|
@contextmenu="onEdgeContextMenu($event, edge)"
|
||||||
@mouseenter="onEdgeMouseEnter($event, edge)"
|
@mouseenter="onEdgeMouseEnter($event, edge)"
|
||||||
@mousemove="onEdgeMouseMove($event, edge)"
|
@mousemove="onEdgeMouseMove($event, edge)"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { CSSProperties, Component, VNode } from 'vue'
|
|||||||
import type { ClassFunc, ElementData, Position, StyleFunc, Styles } from './flow'
|
import type { ClassFunc, ElementData, Position, StyleFunc, Styles } from './flow'
|
||||||
import type { GraphNode } from './node'
|
import type { GraphNode } from './node'
|
||||||
import type { DefaultEdgeTypes, EdgeComponent, EdgeTextProps } from './components'
|
import type { DefaultEdgeTypes, EdgeComponent, EdgeTextProps } from './components'
|
||||||
import type { CustomEvent, EdgeEventsHandler, EdgeEventsOn } from "./hooks";
|
import type { CustomEvent, EdgeEventsHandler, EdgeEventsOn } from './hooks'
|
||||||
|
|
||||||
/** Edge markers */
|
/** Edge markers */
|
||||||
export enum MarkerType {
|
export enum MarkerType {
|
||||||
|
|||||||
@@ -175,3 +175,23 @@ export type NodeEventsOn<CustomEvents = {}> = {
|
|||||||
export type NodeEventsEmit<CustomEvents = {}> = {
|
export type NodeEventsEmit<CustomEvents = {}> = {
|
||||||
[key in keyof NodeEventsHandler]: EventHookTrigger<NodeEventsHandler[key] extends (event: infer Event) => any ? Event : never>
|
[key in keyof NodeEventsHandler]: EventHookTrigger<NodeEventsHandler[key] extends (event: infer Event) => any ? Event : never>
|
||||||
} & CustomEventHandlers<CustomEvents>
|
} & CustomEventHandlers<CustomEvents>
|
||||||
|
|
||||||
|
export type EdgeEventsHandler<CustomEvents = {}> = {
|
||||||
|
doubleClick: (event: EdgeMouseEvent) => void | { off: () => void }
|
||||||
|
click: (event: EdgeMouseEvent) => void | { off: () => void }
|
||||||
|
mouseEnter: (event: EdgeMouseEvent) => void | { off: () => void }
|
||||||
|
mouseMove: (event: EdgeMouseEvent) => void | { off: () => void }
|
||||||
|
mouseLeave: (event: EdgeMouseEvent) => void | { off: () => void }
|
||||||
|
contextMenu: (event: EdgeMouseEvent) => void | { off: () => void }
|
||||||
|
updateStart: (event: EdgeMouseEvent) => void | { off: () => void }
|
||||||
|
update: (event: EdgeUpdateEvent) => void | { off: () => void }
|
||||||
|
updateEnd: (event: EdgeMouseEvent) => void | { off: () => void }
|
||||||
|
} & CustomEventHandlers<CustomEvents>
|
||||||
|
|
||||||
|
export type EdgeEventsOn<CustomEvents = {}> = {
|
||||||
|
[key in keyof EdgeEventsHandler]: EventHookOn<EdgeEventsHandler[key] extends (event: infer Event) => any ? Event : never>
|
||||||
|
} & CustomEventHandlers<CustomEvents>
|
||||||
|
|
||||||
|
export type EdgeEventsEmit<CustomEvents = {}> = {
|
||||||
|
[key in keyof EdgeEventsHandler]: EventHookTrigger<EdgeEventsHandler[key] extends (event: infer Event) => any ? Event : never>
|
||||||
|
} & CustomEventHandlers<CustomEvents>
|
||||||
|
|||||||
Reference in New Issue
Block a user