From 730f49e8792df3b228ebe41a5cac09b39fa24dbf Mon Sep 17 00:00:00 2001 From: bcakmakoglu <78412429+bcakmakoglu@users.noreply.github.com> Date: Thu, 23 Jun 2022 17:13:59 +0200 Subject: [PATCH] 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 --- .../vue-flow/src/components/Edges/Wrapper.ts | 6 +- packages/vue-flow/src/composables/index.ts | 1 + .../vue-flow/src/composables/useEdgeHooks.ts | 72 +++++++++++++++++++ .../container/EdgeRenderer/EdgeRenderer.vue | 31 +++++--- packages/vue-flow/src/types/edge.ts | 2 +- packages/vue-flow/src/types/hooks.ts | 20 ++++++ 6 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 packages/vue-flow/src/composables/useEdgeHooks.ts diff --git a/packages/vue-flow/src/components/Edges/Wrapper.ts b/packages/vue-flow/src/components/Edges/Wrapper.ts index fb03ad8f..896e3285 100644 --- a/packages/vue-flow/src/components/Edges/Wrapper.ts +++ b/packages/vue-flow/src/components/Edges/Wrapper.ts @@ -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 | Object data?: any + events: EdgeEventsOn labelStyle?: CSSProperties labelShowBg?: boolean labelBgStyle?: any @@ -38,6 +39,7 @@ const Wrapper: FunctionalComponent = function ( type, id, data, + events, labelBgBorderRadius, labelBgPadding, labelBgStyle, @@ -140,6 +142,7 @@ const Wrapper: FunctionalComponent = 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', diff --git a/packages/vue-flow/src/composables/index.ts b/packages/vue-flow/src/composables/index.ts index d3626d6c..826b4ef4 100644 --- a/packages/vue-flow/src/composables/index.ts +++ b/packages/vue-flow/src/composables/index.ts @@ -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' diff --git a/packages/vue-flow/src/composables/useEdgeHooks.ts b/packages/vue-flow/src/composables/useEdgeHooks.ts new file mode 100644 index 00000000..d00b8b3c --- /dev/null +++ b/packages/vue-flow/src/composables/useEdgeHooks.ts @@ -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 }, + ) +} diff --git a/packages/vue-flow/src/container/EdgeRenderer/EdgeRenderer.vue b/packages/vue-flow/src/container/EdgeRenderer/EdgeRenderer.vue index 3c1c2058..99ab0992 100644 --- a/packages/vue-flow/src/container/EdgeRenderer/EdgeRenderer.vue +++ b/packages/vue-flow/src/container/EdgeRenderer/EdgeRenderer.vue @@ -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>>({}) + let groups = $ref>([]) 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)" diff --git a/packages/vue-flow/src/types/edge.ts b/packages/vue-flow/src/types/edge.ts index f8df1997..7c11b92c 100644 --- a/packages/vue-flow/src/types/edge.ts +++ b/packages/vue-flow/src/types/edge.ts @@ -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 { diff --git a/packages/vue-flow/src/types/hooks.ts b/packages/vue-flow/src/types/hooks.ts index 788efda1..80e15a3c 100644 --- a/packages/vue-flow/src/types/hooks.ts +++ b/packages/vue-flow/src/types/hooks.ts @@ -175,3 +175,23 @@ export type NodeEventsOn = { export type NodeEventsEmit = { [key in keyof NodeEventsHandler]: EventHookTrigger any ? Event : never> } & CustomEventHandlers + +export type EdgeEventsHandler = { + 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 + +export type EdgeEventsOn = { + [key in keyof EdgeEventsHandler]: EventHookOn any ? Event : never> +} & CustomEventHandlers + +export type EdgeEventsEmit = { + [key in keyof EdgeEventsHandler]: EventHookTrigger any ? Event : never> +} & CustomEventHandlers