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:
bcakmakoglu
2022-06-23 17:13:59 +02:00
committed by Braks
parent fb51f8d8bd
commit 730f49e879
6 changed files with 119 additions and 13 deletions

View File

@@ -1,6 +1,6 @@
import type { CSSProperties, Component, FunctionalComponent, VNode } from 'vue'
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 { getEdgePositions, getHandle, getMarkerId } from '~/utils'
@@ -18,6 +18,7 @@ interface Props {
updatable?: boolean
label?: string | VNode | Component<EdgeTextProps> | Object
data?: any
events: EdgeEventsOn
labelStyle?: CSSProperties
labelShowBg?: boolean
labelBgStyle?: any
@@ -38,6 +39,7 @@ const Wrapper: FunctionalComponent<Props> = function (
type,
id,
data,
events,
labelBgBorderRadius,
labelBgPadding,
labelBgStyle,
@@ -140,6 +142,7 @@ const Wrapper: FunctionalComponent<Props> = function (
labelBgPadding,
labelBgBorderRadius,
data,
events,
style,
markerStart: `url(#${getMarkerId(markerStart)})`,
markerEnd: `url(#${getMarkerId(markerEnd)})`,
@@ -193,6 +196,7 @@ Wrapper.props = [
'type',
'id',
'data',
'events',
'labelBgBorderRadius',
'labelBgPadding',
'labelBgStyle',

View File

@@ -5,3 +5,4 @@ export { default as useWindow } from './useWindow'
export { default as useVueFlow } from './useVueFlow'
export { default as useDrag } from './useDrag'
export { default as useNodeHooks } from './useNodeHooks'
export { default as useEdgeHooks } from './useEdgeHooks'

View File

@@ -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 },
)
}

View File

@@ -2,7 +2,7 @@
import type { CSSProperties } from 'vue'
import EdgeWrapper from '../../components/Edges/Wrapper'
import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue'
import { useHandle, useVueFlow } from '../../composables'
import { useEdgeHooks, useHandle, useVueFlow } from '../../composables'
import { connectionExists, groupEdgesByZLevel } from '../../utils'
import type { EdgeComponent, GraphEdge } from '../../types'
import { Slots } from '../../context'
@@ -55,6 +55,8 @@ const connectionLineVisible = $(
),
)
const hooks = $ref<Record<string, ReturnType<typeof useEdgeHooks>>>({})
let groups = $ref<ReturnType<typeof groupEdgesByZLevel>>([])
const scope = effectScope()
@@ -64,6 +66,12 @@ onPaneReady(() => {
watch(
[$$(getSelectedNodes), $$(getEdges), $$(getNodes)],
() => {
getEdges.reduce((acc, edge) => {
if (hooks[edge.id]) return acc
hooks[edge.id] = useEdgeHooks(edge, emits)
return acc
}, hooks)
if (elevateEdgesOnSelect) {
nextTick(() => (groups = groupEdgesByZLevel(getEdges, getNode)))
} else {
@@ -117,18 +125,18 @@ const onEdgeClick = (event: MouseEvent, edge: GraphEdge) => {
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)
@@ -140,7 +148,7 @@ const handleEdgeUpdater = (event: MouseEvent, edge: GraphEdge, isSourceHandle: b
const nodeId = isSourceHandle ? edge.target : edge.source
const handleId = (isSourceHandle ? edge.targetHandle : edge.sourceHandle) ?? ''
emits.edgeUpdateStart({ event, edge })
hooks[edge.id].emit.updateStart({ event, edge })
onMouseDown(
event,
@@ -150,9 +158,9 @@ const handleEdgeUpdater = (event: MouseEvent, edge: GraphEdge, isSourceHandle: b
undefined,
isSourceHandle ? 'target' : 'source',
(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)"
:label="edge.label"
:data="edge.data"
:events="{ ...edge.events, ...hooks[edge.id].on }"
:animated="edge.animated"
:selectable="selectable(edge.selectable)"
:selected="edge.selected"
@@ -204,7 +213,7 @@ export default {
:style="getStyle(edge)"
:class="getClass(edge)"
@click="onEdgeClick($event, edge)"
@dbl-click="onDoubleClick($event, edge)"
@dblclick="onDoubleClick($event, edge)"
@contextmenu="onEdgeContextMenu($event, edge)"
@mouseenter="onEdgeMouseEnter($event, edge)"
@mousemove="onEdgeMouseMove($event, edge)"

View File

@@ -2,7 +2,7 @@ import type { CSSProperties, Component, VNode } from 'vue'
import type { ClassFunc, ElementData, Position, StyleFunc, Styles } from './flow'
import type { GraphNode } from './node'
import type { DefaultEdgeTypes, EdgeComponent, EdgeTextProps } from './components'
import type { CustomEvent, EdgeEventsHandler, EdgeEventsOn } from "./hooks";
import type { CustomEvent, EdgeEventsHandler, EdgeEventsOn } from './hooks'
/** Edge markers */
export enum MarkerType {

View File

@@ -175,3 +175,23 @@ export type NodeEventsOn<CustomEvents = {}> = {
export type NodeEventsEmit<CustomEvents = {}> = {
[key in keyof NodeEventsHandler]: EventHookTrigger<NodeEventsHandler[key] extends (event: infer Event) => any ? Event : never>
} & 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>