diff --git a/package/src/components/Edges/EdgeWrapper.vue b/package/src/components/Edges/EdgeWrapper.vue index a6d2bd33..156a549b 100644 --- a/package/src/components/Edges/EdgeWrapper.vue +++ b/package/src/components/Edges/EdgeWrapper.vue @@ -18,7 +18,7 @@ interface EdgeWrapper { const { id, edge, name, sourceNode, targetNode, selectable, updatable, type } = defineProps() -const { hooks, connectionMode, edgeUpdaterRadius, noPanClassName, setState, getEdge, addSelectedEdges } = $(useVueFlow()) +const { emits, connectionMode, edgeUpdaterRadius, noPanClassName, setState, getEdge, addSelectedEdges } = $(useVueFlow()) let updating = $ref(false) @@ -33,18 +33,18 @@ const onEdgeClick = (event: MouseEvent) => { addSelectedEdges([edge]) } - hooks.edgeClick.trigger(data) + emits.edgeClick(data) } -const onEdgeContextMenu = (event: MouseEvent) => hooks.edgeContextMenu.trigger({ event, edge }) +const onEdgeContextMenu = (event: MouseEvent) => emits.edgeContextMenu({ event, edge }) -const onDoubleClick = (event: MouseEvent) => hooks.edgeDoubleClick.trigger({ event, edge }) +const onDoubleClick = (event: MouseEvent) => emits.edgeDoubleClick({ event, edge }) -const onEdgeMouseEnter = (event: MouseEvent) => hooks.edgeMouseEnter.trigger({ event, edge }) +const onEdgeMouseEnter = (event: MouseEvent) => emits.edgeMouseEnter({ event, edge }) -const onEdgeMouseMove = (event: MouseEvent) => hooks.edgeMouseMove.trigger({ event, edge }) +const onEdgeMouseMove = (event: MouseEvent) => emits.edgeMouseMove({ event, edge }) -const onEdgeMouseLeave = (event: MouseEvent) => hooks.edgeMouseLeave.trigger({ event, edge }) +const onEdgeMouseLeave = (event: MouseEvent) => emits.edgeMouseLeave({ event, edge }) const onEdgeUpdaterMouseEnter = () => (updating = true) @@ -58,7 +58,7 @@ const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => { const nodeId = isSourceHandle ? edge.target : edge.source const handleId = (isSourceHandle ? edge.targetHandle : edge.sourceHandle) ?? '' - hooks.edgeUpdateStart.trigger({ event, edge }) + emits.edgeUpdateStart({ event, edge }) onMouseDown( event, @@ -67,8 +67,8 @@ const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => { isSourceHandle, undefined, isSourceHandle ? 'target' : 'source', - (connection) => hooks.edgeUpdate.trigger({ edge, connection }), - () => hooks.edgeUpdateEnd.trigger({ event, edge }), + (connection) => emits.edgeUpdate({ edge, connection }), + () => emits.edgeUpdateEnd({ event, edge }), ) } diff --git a/package/src/components/Handle/Handle.vue b/package/src/components/Handle/Handle.vue index 248dcd99..224406e9 100644 --- a/package/src/components/Handle/Handle.vue +++ b/package/src/components/Handle/Handle.vue @@ -14,7 +14,7 @@ const { }, } = defineProps() -const { hooks, connectionStartHandle, connectionMode } = $(useVueFlow()) +const { connectionStartHandle, connectionMode } = $(useVueFlow()) const nodeId = inject(NodeId, '') diff --git a/package/src/components/Nodes/NodeWrapper.vue b/package/src/components/Nodes/NodeWrapper.vue index 98a0b59b..d27109af 100644 --- a/package/src/components/Nodes/NodeWrapper.vue +++ b/package/src/components/Nodes/NodeWrapper.vue @@ -26,7 +26,7 @@ const { viewport, noDragClassName, noPanClassName, - hooks, + emits, selectNodesOnDrag, setState, updateNodePosition, @@ -134,30 +134,30 @@ onUnmounted(() => { const onMouseEnter = (event: MouseEvent) => { if (!node.dragging) { - hooks.nodeMouseEnter.trigger({ event, node }) + emits.nodeMouseEnter({ event, node }) } } const onMouseMove = (event: MouseEvent) => { if (!node.dragging) { - hooks.nodeMouseMove.trigger({ event, node }) + emits.nodeMouseMove({ event, node }) } } const onMouseLeave = (event: MouseEvent) => { if (!node.dragging) { - hooks.nodeMouseLeave.trigger({ event, node }) + emits.nodeMouseLeave({ event, node }) } } const onContextMenu = (event: MouseEvent) => { - hooks.nodeContextMenu.trigger({ + emits.nodeContextMenu({ event, node, }) } -const onDoubleClick = (event: MouseEvent) => hooks.nodeDoubleClick.trigger({ event, node }) +const onDoubleClick = (event: MouseEvent) => emits.nodeDoubleClick({ event, node }) const onSelectNode = (event: MouseEvent) => { if (!draggable) { @@ -168,13 +168,13 @@ const onSelectNode = (event: MouseEvent) => { if (!node.selected) addSelectedNodes([node]) } - hooks.nodeClick.trigger({ event, node }) + emits.nodeClick({ event, node }) } } onDragStart(({ event }) => { addSelectedNodes([]) - hooks.nodeDragStart.trigger({ event, node }) + emits.nodeDragStart({ event, node }) if (selectNodesOnDrag && selectable) { setState({ @@ -193,7 +193,7 @@ onDragStart(({ event }) => { onDrag(({ event, data: { deltaX, deltaY } }) => { updateNodePosition({ id: node.id, diff: { x: deltaX, y: deltaY }, dragging: true }) - hooks.nodeDrag.trigger({ event, node }) + emits.nodeDrag({ event, node }) }) onDragStop(({ event, data: { deltaX, deltaY } }) => { @@ -203,11 +203,11 @@ onDragStop(({ event, data: { deltaX, deltaY } }) => { if (selectable && !selectNodesOnDrag && !node.selected) { addSelectedNodes([node]) } - hooks.nodeClick.trigger({ event, node }) + emits.nodeClick({ event, node }) return } updateNodePosition({ id: node.id, diff: { x: deltaX, y: deltaY }, dragging: false }) - hooks.nodeDragStop.trigger({ event, node }) + emits.nodeDragStop({ event, node }) }) const getClass = computed(() => { diff --git a/package/src/components/NodesSelection/NodesSelection.vue b/package/src/components/NodesSelection/NodesSelection.vue index 39b37c2e..8b6f3cc4 100644 --- a/package/src/components/NodesSelection/NodesSelection.vue +++ b/package/src/components/NodesSelection/NodesSelection.vue @@ -3,7 +3,7 @@ import { useDraggableCore } from '@braks/revue-draggable' import { useVueFlow } from '../../composables' import { getRectOfNodes } from '../../utils' -const { hooks, setState, viewport, getSelectedNodes, snapToGrid, snapGrid, updateNodePosition, noPanClassName } = $(useVueFlow()) +const { emits, setState, viewport, getSelectedNodes, snapToGrid, snapGrid, updateNodePosition, noPanClassName } = $(useVueFlow()) const el = templateRef('el', null) @@ -24,7 +24,7 @@ watch($$(selectedNodesBBox), (v) => { }) }) -const onContextMenu = (event: MouseEvent) => hooks.selectionContextMenu.trigger({ event, nodes: getSelectedNodes }) +const onContextMenu = (event: MouseEvent) => emits.selectionContextMenu({ event, nodes: getSelectedNodes }) const { onDragStart, onDrag, onDragStop, scale } = useDraggableCore(el, { grid: snapToGrid ? snapGrid : undefined, @@ -42,15 +42,15 @@ onMounted(() => { ) }) -onDragStart(({ event }) => hooks.selectionDragStart.trigger({ event, nodes: getSelectedNodes })) +onDragStart(({ event }) => emits.selectionDragStart({ event, nodes: getSelectedNodes })) onDrag(({ event, data: { deltaX, deltaY } }) => { - hooks.selectionDrag.trigger({ event, nodes: getSelectedNodes }) + emits.selectionDrag({ event, nodes: getSelectedNodes }) updateNodePosition({ diff: { x: deltaX, y: deltaY }, dragging: true }) }) onDragStop(({ event }) => { - hooks.selectionDragStop.trigger({ event, nodes: getSelectedNodes }) + emits.selectionDragStop({ event, nodes: getSelectedNodes }) getSelectedNodes.forEach((node) => (node.dragging = false)) }) diff --git a/package/src/composables/useHandle.ts b/package/src/composables/useHandle.ts index 84fc932f..2801326e 100644 --- a/package/src/composables/useHandle.ts +++ b/package/src/composables/useHandle.ts @@ -79,7 +79,7 @@ export default () => { connectionStartHandle, connectionPosition, connectionMode, - hooks, + emits, setState, getNode, } = $(useVueFlow()) @@ -131,7 +131,7 @@ export default () => { connectionHandleType: handleType, }) - hooks.connectStart.trigger({ event, nodeId, handleId, handleType }) + emits.connectStart({ event, nodeId, handleId, handleType }) function onMouseMove(event: MouseEvent) { connectionPosition.x = event.clientX - containerBounds.left @@ -173,16 +173,16 @@ export default () => { getNode, ) - hooks.connectStop.trigger(event) + emits.connectStop(event) const isOwnHandle = connection.source === connection.target if (isValid && !isOwnHandle) { - if (!onEdgeUpdate) hooks.connect.trigger(connection) + if (!onEdgeUpdate) emits.connect(connection) else onEdgeUpdate(connection) } - hooks.connectEnd.trigger(event) + emits.connectEnd(event) if (elementEdgeUpdaterType) onEdgeUpdateEnd?.() @@ -212,7 +212,7 @@ export default () => { ) => { if (!connectOnClick) return if (!connectionStartHandle) { - hooks.connectStart.trigger({ event, nodeId, handleId, handleType }) + emits.connectStart({ event, nodeId, handleId, handleType }) setState({ connectionStartHandle: { nodeId, type: handleType, handleId } }) } else { let validConnectFunc: ValidConnectionFunc = isValidConnection ?? (() => true) @@ -241,11 +241,11 @@ export default () => { const isOwnHandle = connection.source === connection.target - hooks.connectStop.trigger(event) + emits.connectStop(event) - if (isValid && !isOwnHandle) hooks.connect.trigger(connection) + if (isValid && !isOwnHandle) emits.connect(connection) - hooks.connectEnd.trigger(event) + emits.connectEnd(event) setState({ connectionStartHandle: null }) } diff --git a/package/src/composables/useVueFlow.ts b/package/src/composables/useVueFlow.ts index 598803e6..a1177290 100644 --- a/package/src/composables/useVueFlow.ts +++ b/package/src/composables/useVueFlow.ts @@ -1,6 +1,6 @@ import { EffectScope } from 'vue' import { MaybeRef } from '@vueuse/core' -import { FlowHooksOn, FlowOptions, FlowProps, State, VueFlowStore } from '~/types' +import { FlowOptions, FlowProps, State, VueFlowStore } from '~/types' import { VueFlow } from '~/context' import useState from '~/store/state' import useGetters from '~/store/getters' @@ -43,10 +43,15 @@ export class Storage { const actions = useActions(reactiveState, getters) - const hooksOn: FlowHooksOn = {} + const hooksOn = {} Object.entries(reactiveState.hooks).forEach(([n, h]) => { - const name = `on${n.charAt(0).toUpperCase() + n.slice(1)}` as keyof FlowHooksOn - hooksOn[name] = h.on as any + const name = `on${n.charAt(0).toUpperCase() + n.slice(1)}` + hooksOn[name] = h.on + }) + + const emits = {} + Object.entries(reactiveState.hooks).forEach(([n, h]) => { + emits[n] = h.trigger }) actions.setState(reactiveState) @@ -61,6 +66,7 @@ export class Storage { ...getters, ...actions, ...toRefs(reactiveState), + emits, id, } diff --git a/package/src/composables/useZoomPanHelper.ts b/package/src/composables/useZoomPanHelper.ts index 14a6e38f..d501d889 100644 --- a/package/src/composables/useZoomPanHelper.ts +++ b/package/src/composables/useZoomPanHelper.ts @@ -29,12 +29,23 @@ const untilDimensions = async (dimensions: Dimensions, getNodes: Getters['getNod } export default (): ViewportFuncs => { - const { hooks, d3Zoom, d3Selection, dimensions, translateExtent, minZoom, maxZoom, viewport, snapToGrid, snapGrid, getNodes } = - $(useVueFlow()) + const { + onPaneReady, + d3Zoom, + d3Selection, + dimensions, + translateExtent, + minZoom, + maxZoom, + viewport, + snapToGrid, + snapGrid, + getNodes, + } = $(useVueFlow()) let hasDimensions = $ref(false) - hooks.paneReady.on(() => (hasDimensions = true)) + onPaneReady(() => (hasDimensions = true)) const zoomTo: ViewportFuncs['zoomTo'] = async (zoomLevel, options) => { if (!hasDimensions) await untilDimensions(dimensions, getNodes) diff --git a/package/src/container/SelectionPane/SelectionPane.vue b/package/src/container/SelectionPane/SelectionPane.vue index 3ad2b3f4..97dca2f0 100644 --- a/package/src/container/SelectionPane/SelectionPane.vue +++ b/package/src/container/SelectionPane/SelectionPane.vue @@ -11,7 +11,7 @@ const { deleteKeyCode, selectionKeyCode, multiSelectionKeyCode, - hooks, + emits, nodesSelectionActive, userSelectionActive, elementsSelectable, @@ -22,16 +22,16 @@ const { } = $(useVueFlow()) const onClick = (event: MouseEvent) => { - hooks.paneClick.trigger(event) + emits.paneClick(event) setState({ nodesSelectionActive: false, }) resetSelectedElements() } -const onContextMenu = (event: MouseEvent) => hooks.paneContextMenu.trigger(event) +const onContextMenu = (event: MouseEvent) => emits.paneContextMenu(event) -const onWheel = (event: WheelEvent) => hooks.paneScroll.trigger(event) +const onWheel = (event: WheelEvent) => emits.paneScroll(event) useKeyPress($$(deleteKeyCode), (keyPressed) => { const selectedNodes = getSelectedNodes @@ -45,8 +45,8 @@ useKeyPress($$(deleteKeyCode), (keyPressed) => { type: 'remove', })) - hooks.nodesChange.trigger(nodeChanges) - hooks.edgesChange.trigger(edgeChanges) + emits.nodesChange(nodeChanges) + emits.edgesChange(edgeChanges) setState({ nodesSelectionActive: false, diff --git a/package/src/container/Viewport/Transform.vue b/package/src/container/Viewport/Transform.vue index fcd2fa77..792157a1 100644 --- a/package/src/container/Viewport/Transform.vue +++ b/package/src/container/Viewport/Transform.vue @@ -5,7 +5,7 @@ import { useVueFlow, useZoomPanHelper, useWindow } from '../../composables' import { Dimensions, FlowExportObject, FlowInstance, XYPosition } from '../../types' import { pointToRendererPoint } from '../../utils' -const { id, nodes, edges, viewport, snapToGrid, snapGrid, dimensions, setState, fitViewOnInit, hooks } = $(useVueFlow()) +const { id, nodes, edges, viewport, snapToGrid, snapGrid, dimensions, setState, fitViewOnInit, emits } = $(useVueFlow()) const untilDimensions = async (dim: Dimensions) => { // if ssr we can't wait for dimensions, they'll never really exist @@ -65,7 +65,7 @@ onMounted(async () => { }) fitViewOnInit && instance.fitView() - hooks.paneReady.trigger(instance) + emits.paneReady(instance) }) const transform = computed(() => `translate(${viewport.x}px,${viewport.y}px) scale(${viewport.zoom})`) diff --git a/package/src/container/Viewport/Viewport.vue b/package/src/container/Viewport/Viewport.vue index 359f800e..35e1ac46 100644 --- a/package/src/container/Viewport/Viewport.vue +++ b/package/src/container/Viewport/Viewport.vue @@ -28,7 +28,7 @@ const { noWheelClassName, noPanClassName, setState, - hooks, + emits, } = $(useVueFlow()) const viewportEl = templateRef('viewport', null) @@ -86,7 +86,7 @@ onMounted(() => { d3Zoom.on('zoom', (event: D3ZoomEvent) => { setState({ viewport: { x: event.transform.x, y: event.transform.y, zoom: event.transform.k } }) const flowTransform = eventToFlowTransform(event.transform) - hooks.move.trigger({ event, flowTransform }) + emits.move({ event, flowTransform }) }) } }) @@ -96,14 +96,14 @@ onMounted(() => { d3Zoom.on('start', (event: D3ZoomEvent) => { const flowTransform = eventToFlowTransform(event.transform) transform = flowTransform - hooks.moveStart.trigger({ event, flowTransform }) + emits.moveStart({ event, flowTransform }) }) d3Zoom.on('end', (event: D3ZoomEvent) => { if (viewChanged(transform, event.transform)) { const flowTransform = eventToFlowTransform(event.transform) transform = flowTransform - hooks.moveEnd.trigger({ event, flowTransform }) + emits.moveEnd({ event, flowTransform }) } }) diff --git a/package/src/types/hooks.ts b/package/src/types/hooks.ts index 24d2b57c..b57c9f4d 100644 --- a/package/src/types/hooks.ts +++ b/package/src/types/hooks.ts @@ -1,4 +1,4 @@ -import { EventHook, EventHookOn } from '@vueuse/core' +import { EventHook, EventHookOn, EventHookTrigger } from '@vueuse/core' import { MouseTouchEvent } from '@braks/revue-draggable' import { D3ZoomEvent } from 'd3-zoom' import { FlowInstance } from './flow' @@ -50,11 +50,16 @@ export interface FlowEvents { edgeUpdateEnd: { event: MouseEvent; edge: GraphEdge } } -export type FlowHooks = { +export type FlowHooks = Readonly<{ [key in keyof FlowEvents]: EventHook -} -export type FlowHooksOn = { +}> + +export type FlowHooksOn = Readonly<{ [key in keyof FlowEvents as `on${Capitalize}`]: EventHookOn -} +}> + +export type FlowHooksEmit = Readonly<{ + [key in keyof FlowEvents]: EventHookTrigger +}> export type EmitFunc = (name: keyof FlowHooks, ...args: FlowEvents[keyof FlowEvents][]) => void diff --git a/package/src/types/store.ts b/package/src/types/store.ts index f17e8bae..e6ddb7f8 100644 --- a/package/src/types/store.ts +++ b/package/src/types/store.ts @@ -5,7 +5,7 @@ import { Connection, ConnectionLineType, ConnectionMode } from './connection' import { DefaultEdgeOptions, Edge, GraphEdge } from './edge' import { GraphNode, CoordinateExtent, Node } from './node' import { D3Selection, D3Zoom, D3ZoomHandler, KeyCode, PanOnScrollMode, Viewport } from './zoom' -import { FlowHooks, FlowHooksOn } from './hooks' +import { FlowHooks, FlowHooksEmit, FlowHooksOn } from './hooks' import { NodeChange, EdgeChange } from './changes' import { StartHandle, HandleType } from './handle' @@ -19,29 +19,32 @@ export type UpdateNodePositionsParams = { id?: string; diff?: XYPosition; draggi export interface State extends Omit { /** Event hooks, you can manipulate the triggers at your own peril */ - hooks: FlowHooks - instance: FlowInstance | null + readonly hooks: FlowHooks + readonly instance: FlowInstance | null /** all stored nodes */ nodes: GraphNode[] /** all stored edges */ edges: GraphEdge[] - d3Zoom: D3Zoom | null - d3Selection: D3Selection | null - d3ZoomHandler: D3ZoomHandler | null + readonly d3Zoom: D3Zoom | null + readonly d3Selection: D3Selection | null + readonly d3ZoomHandler: D3ZoomHandler | null + /** use setMinZoom action to change minZoom */ minZoom: number + /** use setMaxZoom action to change maxZoom */ maxZoom: number defaultZoom: number + /** use setTranslateExtent action to change translateExtent */ translateExtent: CoordinateExtent nodeExtent: CoordinateExtent - /** viewport dimensions */ - dimensions: Dimensions - /** viewport transform x, y, z */ - viewport: Viewport - + /** viewport dimensions - do not change! */ + readonly dimensions: Dimensions + /** viewport transform x, y, z - do not change! */ + readonly viewport: Viewport + /** if true will skip rendering any elements currently not inside viewport until they become visible */ onlyRenderVisibleElements: boolean defaultPosition: [number, number] @@ -98,7 +101,8 @@ export interface State extends Omit { defaultEdgeOptions?: DefaultEdgeOptions - vueFlowVersion: string + /** current vue flow version you're using */ + readonly vueFlowVersion: string } export type SetElements = (elements: Elements | ((elements: FlowElements) => Elements), extent?: CoordinateExtent) => void @@ -146,7 +150,7 @@ export interface Actions { setMaxZoom: (zoom: number) => void /** apply translate extent to d3 */ setTranslateExtent: (translateExtent: CoordinateExtent) => void - /** enable node interaction (dragging, selecting etc) */ + /** enable/disable node interaction (dragging, selecting etc) */ setInteractive: (isInteractive: boolean) => void /** set new state */ setState: SetState @@ -186,8 +190,9 @@ export type ComputedGetters = { } export type VueFlowStore = { - id: string + readonly id: string + readonly emits: FlowHooksEmit } & FlowHooksOn & ToRefs & - ComputedGetters & - Actions + Readonly & + Readonly