diff --git a/src/components/Handle/Handle.vue b/src/components/Handle/Handle.vue index 323a6452..15013294 100644 --- a/src/components/Handle/Handle.vue +++ b/src/components/Handle/Handle.vue @@ -2,11 +2,10 @@ import { useHandle, useVueFlow } from '../../composables' import { Position } from '../../types' import { NodeId } from '../../context' -import type { HandleProps } from '../../types/components' +import type { HandleProps } from '../../types/handle' const { id, hooks } = useVueFlow() const props = withDefaults(defineProps(), { - id: '', type: 'source', position: 'top' as Position, connectable: true, diff --git a/src/composables/useHandle.ts b/src/composables/useHandle.ts index 3a72531d..dce19996 100644 --- a/src/composables/useHandle.ts +++ b/src/composables/useHandle.ts @@ -26,7 +26,7 @@ export const checkElementBelowIsValid = ( const result: Result = { elementBelow, isValid: false, - connection: { source: '', target: '', sourceHandle: '', targetHandle: '' }, + connection: { source: null, target: null, sourceHandle: null, targetHandle: null }, isHoveringHandle: false, } @@ -99,14 +99,16 @@ export default (store: FlowStore = useVueFlow().store) => const containerBounds = flowNode.getBoundingClientRect() let recentHoveredHandle: Element - store.connectionPosition.x = event.clientX - containerBounds.left - store.connectionPosition.y = event.clientY - containerBounds.top - - store.setConnectionNodeId({ + store.setState({ + connectionPosition: { + x: event.clientX - containerBounds.left, + y: event.clientY - containerBounds.top, + }, connectionNodeId: nodeId, connectionHandleId: handleId, connectionHandleType: handleType, }) + store.hooks.connectStart.trigger({ event, nodeId, handleId, handleType }) function onMouseMove(event: MouseEvent) { @@ -157,8 +159,12 @@ export default (store: FlowStore = useVueFlow().store) => if (elementEdgeUpdaterType) onEdgeUpdateEnd?.() resetRecentHandle(recentHoveredHandle) - store.setConnectionNodeId({ connectionNodeId: undefined, connectionHandleId: undefined, connectionHandleType: undefined }) - store.connectionPosition = { x: NaN, y: NaN } + store.setState({ + connectionNodeId: null, + connectionHandleId: null, + connectionHandleType: null, + connectionPosition: { x: NaN, y: NaN }, + }) doc.removeEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject) doc.removeEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject) diff --git a/src/container/VueFlow/VueFlow.vue b/src/container/VueFlow/VueFlow.vue index dd0238b1..1a2a0ffa 100644 --- a/src/container/VueFlow/VueFlow.vue +++ b/src/container/VueFlow/VueFlow.vue @@ -21,6 +21,8 @@ const props = withDefaults(defineProps(), { paneMovable: undefined, applyDefault: undefined, fitViewOnInit: undefined, + zoomActivationKeyCode: () => null, + connectionLineStyle: () => null, }) const emit = defineEmits([...Object.keys(createHooks()), 'update:modelValue', 'update:edges', 'update:nodes']) diff --git a/src/store/actions.ts b/src/store/actions.ts index ea579345..2cb9b1a6 100644 --- a/src/store/actions.ts +++ b/src/store/actions.ts @@ -199,15 +199,6 @@ export default (state: State, getters: ComputedGetters): Actions => { addSelectedNodes([]) addSelectedEdges([]) } - const setConnectionNodeId: Actions['setConnectionNodeId'] = ({ - connectionHandleId, - connectionHandleType, - connectionNodeId, - }) => { - state.connectionNodeId = connectionNodeId - state.connectionHandleId = connectionHandleId - state.connectionHandleType = connectionHandleType - } const setInteractive: Actions['setInteractive'] = (isInteractive) => { state.nodesDraggable = isInteractive state.nodesConnectable = isInteractive @@ -327,7 +318,6 @@ export default (state: State, getters: ComputedGetters): Actions => { setMaxZoom, setTranslateExtent, resetSelectedElements, - setConnectionNodeId, setInteractive, setState, $reset: () => { diff --git a/src/store/state.ts b/src/store/state.ts index c3f3c56c..7571be3a 100644 --- a/src/store/state.ts +++ b/src/store/state.ts @@ -31,7 +31,7 @@ export default (opts?: FlowOptions): State => { paneReady: false, initialized: false, - instance: undefined, + instance: null, dimensions: { width: 0, @@ -39,9 +39,9 @@ export default (opts?: FlowOptions): State => { }, transform: [0, 0, 1], - d3Zoom: undefined, - d3Selection: undefined, - d3ZoomHandler: undefined, + d3Zoom: null, + d3Selection: null, + d3ZoomHandler: null, minZoom: 0.5, maxZoom: 2, translateExtent: [ @@ -73,11 +73,13 @@ export default (opts?: FlowOptions): State => { defaultMarkerColor: '#b1b1b7', connectionLineStyle: {}, connectionLineType: ConnectionLineType.Bezier, - connectionNodeId: undefined, - connectionHandleId: undefined, - connectionHandleType: 'source', + connectionNodeId: null, + connectionHandleId: null, + connectionHandleType: null, connectionPosition: { x: NaN, y: NaN }, connectionMode: ConnectionMode.Loose, + connectionStartHandle: null, + connectOnClick: true, snapGrid: [15, 15], snapToGrid: false, @@ -90,7 +92,7 @@ export default (opts?: FlowOptions): State => { multiSelectionActive: false, selectionKeyCode: 'Shift', multiSelectionKeyCode: 'Meta', - zoomActivationKeyCode: undefined, + zoomActivationKeyCode: null, deleteKeyCode: 'Backspace', hooks: createHooks(), diff --git a/src/types/components.ts b/src/types/components.ts index 089ce2f6..67e50657 100644 --- a/src/types/components.ts +++ b/src/types/components.ts @@ -1,6 +1,5 @@ import { Component, CSSProperties, DefineComponent } from 'vue' -import { BackgroundVariant, Dimensions, Position, XYPosition } from './flow' -import { Connection, ConnectionLineType } from './connection' +import { BackgroundVariant, Dimensions, XYPosition } from './flow' import { GraphNode, Node, NodeProps } from './node' import { EdgeProps } from './edge' import { FitViewParams } from './zoom' @@ -23,29 +22,6 @@ export type EdgeComponent = export type DefaultEdgeTypes = { [key in 'default' | 'straight' | 'smoothstep' | 'step']: EdgeComponent } export type DefaultNodeTypes = { [key in 'input' | 'output' | 'default']: NodeComponent } -export type HandleType = 'source' | 'target' - -export interface HandleElement extends XYPosition, Dimensions { - id?: string - position: Position -} - -/** A valid connection function can determine if an attempted connection is valid or not, i.e. abort creating a new edge */ -export type ValidConnectionFunc = (connection: Connection) => boolean - -export interface HandleProps { - /** Unique id of handle element */ - id?: string - /** Handle type (source / target) {@link HandleType} */ - type?: string - /** Handle position (top, bottom, left, right) {@link Position} */ - position?: Position - /** A valid connection func {@link ValidConnectionFunc} */ - isValidConnection?: ValidConnectionFunc - /** Enable/disable connecting to handle */ - connectable?: boolean -} - export interface BackgroundProps { /** The background pattern */ variant?: BackgroundVariant @@ -128,26 +104,3 @@ export interface EdgeTextProps { labelBgPadding?: [number, number] labelBgBorderRadius?: number } - -export interface ConnectionLineProps { - /** Source X position of the connection line */ - sourceX: number - /** Source Y position of the connection line */ - sourceY: number - /** Source position of the connection line */ - sourcePosition: Position - /** Target X position of the connection line */ - targetX: number - /** Target Y position of the connection line */ - targetY: number - /** Target position of the connection line */ - targetPosition: Position - connectionLineType: ConnectionLineType - connectionLineStyle: CSSProperties - /** All currently stored nodes */ - nodes: GraphNode[] - /** The source node of the connection line */ - sourceNode: GraphNode - /** The source handle element of the connection line */ - sourceHandle: HandleElement -} diff --git a/src/types/connection.ts b/src/types/connection.ts index 8429a934..361108b9 100644 --- a/src/types/connection.ts +++ b/src/types/connection.ts @@ -1,4 +1,7 @@ -import { HandleType } from './components' +import { CSSProperties } from 'vue' +import { Position } from './flow' +import { GraphNode } from './node' +import { HandleElement, HandleType } from './handle' /** Connection line types (same as default edge types */ export enum ConnectionLineType { @@ -11,13 +14,13 @@ export enum ConnectionLineType { /** Connection params that are passed when onConnect is called */ export interface Connection { /** Source node id */ - source: string + source: string | null /** Target node id */ - target: string + target: string | null /** Source handle id */ - sourceHandle?: string + sourceHandle: string | null /** Target handle id */ - targetHandle?: string + targetHandle: string | null } /** The source nodes params when connection is initiated */ @@ -41,3 +44,26 @@ export type SetConnectionId = { connectionHandleId: string | undefined connectionHandleType: HandleType | undefined } + +export interface ConnectionLineProps { + /** Source X position of the connection line */ + sourceX: number + /** Source Y position of the connection line */ + sourceY: number + /** Source position of the connection line */ + sourcePosition: Position + /** Target X position of the connection line */ + targetX: number + /** Target Y position of the connection line */ + targetY: number + /** Target position of the connection line */ + targetPosition: Position + connectionLineType: ConnectionLineType + connectionLineStyle: CSSProperties + /** All currently stored nodes */ + nodes: GraphNode[] + /** The source node of the connection line */ + sourceNode: GraphNode + /** The source handle element of the connection line */ + sourceHandle: HandleElement +} diff --git a/src/types/edge.ts b/src/types/edge.ts index 105d787b..fdc6ea38 100644 --- a/src/types/edge.ts +++ b/src/types/edge.ts @@ -47,9 +47,9 @@ export interface Edge extends Element { /** Target node id */ target: string /** Source handle id */ - sourceHandle?: string + sourceHandle: string | null /** Target handle id */ - targetHandle?: string + targetHandle: string | null /** Source position */ sourcePosition?: Position /** Target position */ diff --git a/src/types/flow.ts b/src/types/flow.ts index 3ec06531..1fc02f54 100644 --- a/src/types/flow.ts +++ b/src/types/flow.ts @@ -1,5 +1,5 @@ import { CSSProperties, ToRefs } from 'vue' -import { GraphEdge, Edge } from './edge' +import { GraphEdge, Edge, DefaultEdgeOptions } from "./edge"; import { GraphNode, CoordinateExtent, Node } from './node' import { ConnectionLineType, ConnectionMode } from './connection' import { KeyCode, PanOnScrollMode, UseZoomPanHelper } from './zoom' @@ -93,11 +93,11 @@ export interface FlowProps { nodeTypes?: { [key in keyof DefaultNodeTypes]?: NodeComponent } & { [key: string]: NodeComponent } connectionMode?: ConnectionMode connectionLineType?: ConnectionLineType - connectionLineStyle?: CSSProperties + connectionLineStyle?: CSSProperties | null deleteKeyCode?: KeyCode selectionKeyCode?: KeyCode multiSelectionKeyCode?: KeyCode - zoomActivationKeyCode?: KeyCode + zoomActivationKeyCode?: KeyCode | null snapToGrid?: boolean snapGrid?: SnapGrid onlyRenderVisibleElements?: boolean @@ -116,6 +116,7 @@ export interface FlowProps { defaultMarkerColor?: string zoomOnScroll?: boolean zoomOnPinch?: boolean + panOnDrag?: boolean panOnScroll?: boolean panOnScrollSpeed?: number panOnScrollMode?: PanOnScrollMode @@ -123,7 +124,13 @@ export interface FlowProps { preventScrolling?: boolean edgeUpdaterRadius?: number fitViewOnInit?: boolean + connectOnClick?: boolean + /** apply default change handlers for position, dimensions, adding/removing nodes. set this to false if you want to apply the changes manually */ applyDefault?: boolean + noDragClassName?: string + noWheelClassName?: string + noPanClassName?: string + defaultEdgeOptions?: DefaultEdgeOptions } export type FlowOptions = FlowProps diff --git a/src/types/handle.ts b/src/types/handle.ts new file mode 100644 index 00000000..bf33961d --- /dev/null +++ b/src/types/handle.ts @@ -0,0 +1,31 @@ +import { Dimensions, Position, XYPosition } from './flow' +import { Connection } from './connection' + +export type HandleType = 'source' | 'target' + +export interface HandleElement extends XYPosition, Dimensions { + id?: string | null + position: Position +} + +export interface StartHandle { + nodeId: string + type: HandleType + handleId?: string | null +} + +/** A valid connection function can determine if an attempted connection is valid or not, i.e. abort creating a new edge */ +export type ValidConnectionFunc = (connection: Connection) => boolean + +export interface HandleProps { + /** Unique id of handle element */ + id?: string + /** Handle type (source / target) {@link HandleType} */ + type?: string + /** Handle position (top, bottom, left, right) {@link Position} */ + position?: Position + /** A valid connection func {@link ValidConnectionFunc} */ + isValidConnection?: ValidConnectionFunc + /** Enable/disable connecting to handle */ + connectable?: boolean +} diff --git a/src/types/index.ts b/src/types/index.ts index 0bf34616..186ab1a1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -7,3 +7,4 @@ export * from './zoom' export * from './store' export * from './hooks' export * from './changes' +export * from './handle' diff --git a/src/types/node.ts b/src/types/node.ts index e256840f..cba58609 100644 --- a/src/types/node.ts +++ b/src/types/node.ts @@ -1,6 +1,7 @@ import { CSSProperties } from 'vue' import { XYPosition, Position, SnapGrid, Element, XYZPosition, Dimensions } from './flow' -import { DefaultNodeTypes, HandleElement, ValidConnectionFunc } from './components' +import { DefaultNodeTypes } from './components' +import { HandleElement, ValidConnectionFunc } from './handle' export type CoordinateExtent = [[number, number], [number, number]] diff --git a/src/types/store.ts b/src/types/store.ts index 9b8efa1e..767771a4 100644 --- a/src/types/store.ts +++ b/src/types/store.ts @@ -1,23 +1,24 @@ import { ComputedRef, CSSProperties, ToRefs } from 'vue' import { Dimensions, Elements, FlowElements, FlowInstance, FlowOptions, Rect, SnapGrid, Transform, XYPosition } from './flow' -import { HandleType, EdgeComponent, NodeComponent, DefaultNodeTypes, DefaultEdgeTypes } from './components' -import { Connection, ConnectionLineType, ConnectionMode, SetConnectionId } from './connection' +import { EdgeComponent, NodeComponent, DefaultNodeTypes, DefaultEdgeTypes } from './components' +import { Connection, ConnectionLineType, ConnectionMode } from './connection' import { Edge, GraphEdge } from './edge' import { GraphNode, CoordinateExtent, Node } from './node' import { D3Selection, D3Zoom, D3ZoomHandler, KeyCode, PanOnScrollMode } from './zoom' import { FlowHooks, FlowHooksOn } from './hooks' import { NodeChange, EdgeChange } from './changes' +import { StartHandle, HandleType } from './handle' export interface State extends Omit, 'id' | 'modelValue'> { hooks: FlowHooks - instance: FlowInstance | undefined + instance: FlowInstance | null nodes: GraphNode[] edges: GraphEdge[] - d3Zoom: D3Zoom | undefined - d3Selection: D3Selection | undefined - d3ZoomHandler: D3ZoomHandler | undefined + d3Zoom: D3Zoom | null + d3Selection: D3Selection | null + d3ZoomHandler: D3ZoomHandler | null minZoom: number maxZoom: number defaultZoom: number @@ -35,15 +36,17 @@ export interface State extends Omit, 'id' | 'm deleteKeyCode: KeyCode selectionKeyCode: KeyCode multiSelectionKeyCode: KeyCode - zoomActivationKeyCode: KeyCode | undefined + zoomActivationKeyCode: KeyCode | null - connectionNodeId: string | undefined - connectionHandleId: string | undefined - connectionHandleType: HandleType | undefined + connectionNodeId: string | null + connectionHandleId: string | null + connectionHandleType: HandleType | null connectionPosition: XYPosition connectionMode: ConnectionMode connectionLineType: ConnectionLineType - connectionLineStyle: CSSProperties | undefined + connectionLineStyle: CSSProperties | null + connectionStartHandle: StartHandle | null + connectOnClick: boolean edgeUpdaterRadius: number snapToGrid: boolean @@ -88,9 +91,8 @@ export interface Actions { setMaxZoom: (zoom: number) => void setTranslateExtent: (translateExtent: CoordinateExtent) => void resetSelectedElements: () => void - setConnectionNodeId: (payload: SetConnectionId) => void setInteractive: (isInteractive: boolean) => void - setState: (state: Partial>) => void + setState: (state: Partial & Omit>) => void updateNodePosition: ({ id, diff, dragging }: { id?: string; diff?: XYPosition; dragging?: boolean }) => void updateNodeDimensions: ( update: {