refactor(core): add connection start and end handles to store state

Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
braks
2023-04-03 08:53:33 +02:00
committed by Braks
parent eab9991762
commit 4efc787067
8 changed files with 53 additions and 32 deletions
@@ -8,6 +8,7 @@ const { sourceNode } = defineProps<{ sourceNode: GraphNode }>()
const { const {
connectionMode, connectionMode,
connectionStartHandle, connectionStartHandle,
connectionEndHandle,
connectionPosition, connectionPosition,
connectionLineType, connectionLineType,
connectionLineStyle, connectionLineStyle,
@@ -30,9 +31,7 @@ const handleId = $computed(() => connectionStartHandle!.handleId)
const type = computed(() => connectionStartHandle!.type) const type = computed(() => connectionStartHandle!.type)
const targetNode = $computed( const targetNode = $computed(() => (connectionEndHandle?.handleId && findNode(connectionEndHandle.handleId)) || null)
() => (connectionStartHandle?.result && findNode(connectionStartHandle.result.connection.target)) || null,
)
const sourceHandle = $computed( const sourceHandle = $computed(
() => () =>
@@ -45,13 +44,13 @@ const sourceHandle = $computed(
const targetHandle = $computed(() => { const targetHandle = $computed(() => {
return ( return (
(targetNode && (targetNode &&
connectionStartHandle?.result?.handleId && connectionEndHandle?.handleId &&
((connectionMode === ConnectionMode.Strict ((connectionMode === ConnectionMode.Strict
? targetNode.handleBounds[type.value === 'source' ? 'target' : 'source']?.find( ? targetNode.handleBounds[type.value === 'source' ? 'target' : 'source']?.find(
(d) => d.id === connectionStartHandle?.result?.handleId, (d) => d.id === connectionEndHandle?.handleId,
) )
: [...(targetNode.handleBounds.source || []), ...(targetNode.handleBounds.target || [])]?.find( : [...(targetNode.handleBounds.source || []), ...(targetNode.handleBounds.target || [])]?.find(
(d) => d.id === connectionStartHandle?.result?.handleId, (d) => d.id === connectionEndHandle?.handleId,
)) || )) ||
targetNode.handleBounds[type.value ?? 'target']?.[0])) || targetNode.handleBounds[type.value ?? 'target']?.[0])) ||
null null
+18 -8
View File
@@ -21,7 +21,8 @@ const emits = defineEmits<{
const type = toRef(props, 'type', 'source') const type = toRef(props, 'type', 'source')
const { connectionStartHandle, vueFlowRef, nodesConnectable, noDragClassName } = useVueFlow() const { connectionStartHandle, connectionClickStartHandle, connectionEndHandle, vueFlowRef, nodesConnectable, noDragClassName } =
useVueFlow()
const { id: nodeId, node, nodeEl, connectedEdges } = useNode() const { id: nodeId, node, nodeEl, connectedEdges } = useNode()
@@ -58,10 +59,19 @@ const isConnectable = computed(() => {
const isConnecting = computed( const isConnecting = computed(
() => () =>
connectionStartHandle.value && (connectionStartHandle.value?.nodeId === nodeId &&
connectionStartHandle.value.nodeId === nodeId && connectionStartHandle.value?.handleId === handleId.value &&
connectionStartHandle.value.handleId === handleId.value && connectionStartHandle.value?.type === type.value) ||
connectionStartHandle.value.type === type.value, (connectionEndHandle.value?.nodeId === nodeId &&
connectionEndHandle.value?.handleId === handleId.value &&
connectionEndHandle.value?.type === type.value),
)
const isClickConnecting = computed(
() =>
connectionClickStartHandle.value?.nodeId === nodeId &&
connectionClickStartHandle.value?.handleId === handleId.value &&
connectionClickStartHandle.value?.type === type.value,
) )
// set up handle bounds if they don't exist yet and the node has been initialized (i.e. the handle was added after the node has already been mounted) // set up handle bounds if they don't exist yet and the node has been initialized (i.e. the handle was added after the node has already been mounted)
@@ -109,7 +119,7 @@ function onPointerDown(event: MouseEvent | TouchEvent) {
} }
function onClick(event: MouseEvent) { function onClick(event: MouseEvent) {
if (!connectionStartHandle && !connectableStart) { if (!nodeId || (!connectionStartHandle && !connectableStart)) {
return return
} }
@@ -141,10 +151,10 @@ export default {
type, type,
{ {
connectable: isConnectable, connectable: isConnectable,
connecting: isConnecting, connecting: isClickConnecting,
connectablestart: connectableStart, connectablestart: connectableStart,
connectableend: connectableEnd, connectableend: connectableEnd,
connectionindicator: (isConnectable && connectableStart && !isConnecting) || (connectableEnd && isConnecting), connectionindicator: isConnectable && ((connectableStart && !isConnecting) || (connectableEnd && isConnecting)),
}, },
]" ]"
@mousedown="onPointerDown" @mousedown="onPointerDown"
+5 -5
View File
@@ -60,7 +60,7 @@ export default function useHandle({
const doc = getHostForElement(event.target as HTMLElement) const doc = getHostForElement(event.target as HTMLElement)
if ((isMouseTriggered && event.button === 0) || !isMouseTriggered) { if ((isMouseTriggered && event.button === 0) || !isMouseTriggered) {
const node = findNode(unref(nodeId)) const node = findNode(nodeId.value)
let isValidConnectionHandler = isValidConnection || isValidConnectionProp.value || alwaysValid let isValidConnectionHandler = isValidConnection || isValidConnectionProp.value || alwaysValid
@@ -74,7 +74,7 @@ export default function useHandle({
const { x, y } = getEventPosition(event) const { x, y } = getEventPosition(event)
const clickedHandle = doc?.elementFromPoint(x, y) const clickedHandle = doc?.elementFromPoint(x, y)
const handleType = getHandleType(unref(edgeUpdaterType), clickedHandle) const handleType = getHandleType(edgeUpdaterType.value, clickedHandle)
const containerBounds = vueFlowRef.value?.getBoundingClientRect() const containerBounds = vueFlowRef.value?.getBoundingClientRect()
if (!containerBounds || !handleType) { if (!containerBounds || !handleType) {
@@ -160,7 +160,7 @@ export default function useHandle({
viewport.value, viewport.value,
) )
: connectionPosition, : connectionPosition,
result, result.endHandle,
getConnectionStatus(!!prevClosestHandle, isValid), getConnectionStatus(!!prevClosestHandle, isValid),
) )
@@ -229,11 +229,11 @@ export default function useHandle({
if (!connectionClickStartHandle.value) { if (!connectionClickStartHandle.value) {
emits.clickConnectStart({ event, nodeId: nodeId.value, handleId: handleId.value }) emits.clickConnectStart({ event, nodeId: nodeId.value, handleId: handleId.value })
startConnection({ nodeId: unref(nodeId), type: unref(type), handleId: unref(handleId) }, undefined, event, true) startConnection({ nodeId: nodeId.value, type: unref(type), handleId: handleId.value }, undefined, event, true)
} else { } else {
let isValidConnectionHandler = isValidConnection || isValidConnectionProp.value || alwaysValid let isValidConnectionHandler = isValidConnection || isValidConnectionProp.value || alwaysValid
const node = findNode(unref(nodeId)) const node = findNode(nodeId.value)
if (!isValidConnectionHandler && node) { if (!isValidConnectionHandler && node) {
isValidConnectionHandler = (!isTarget ? node.isValidTargetPos : node.isValidSourcePos) || alwaysValid isValidConnectionHandler = (!isTarget ? node.isValidTargetPos : node.isValidSourcePos) || alwaysValid
+3 -2
View File
@@ -483,15 +483,16 @@ export function useActions(state: State, getters: ComputedGetters): Actions {
state.connectionStartHandle = startHandle state.connectionStartHandle = startHandle
} }
state.connectionEndHandle = null
state.connectionStatus = null state.connectionStatus = null
if (position) state.connectionPosition = position if (position) state.connectionPosition = position
} }
const updateConnection: Actions['updateConnection'] = (position, result, status = null) => { const updateConnection: Actions['updateConnection'] = (position, result = null, status = null) => {
if (state.connectionStartHandle) { if (state.connectionStartHandle) {
state.connectionPosition = position state.connectionPosition = position
state.connectionStartHandle.result = result state.connectionEndHandle = result
state.connectionStatus = status state.connectionStatus = status
} }
} }
+1
View File
@@ -85,6 +85,7 @@ function defaultState(): State {
}, },
connectionMode: ConnectionMode.Loose, connectionMode: ConnectionMode.Loose,
connectionStartHandle: null, connectionStartHandle: null,
connectionEndHandle: null,
connectionClickStartHandle: null, connectionClickStartHandle: null,
connectionPosition: { x: NaN, y: NaN }, connectionPosition: { x: NaN, y: NaN },
connectionRadius: 20, connectionRadius: 20,
+2 -3
View File
@@ -19,17 +19,16 @@ export interface ConnectionHandle {
} }
export interface ValidHandleResult { export interface ValidHandleResult {
handleId: string | null endHandle: ConnectingHandle | null
handleDomNode: Element | null handleDomNode: Element | null
isValid: boolean isValid: boolean
connection: Connection connection: Connection
} }
export interface StartHandle { export interface ConnectingHandle {
nodeId: string nodeId: string
type: HandleType type: HandleType
handleId: string | null handleId: string | null
result?: ValidHandleResult
} }
/** A valid connection function can determine if an attempted connection is valid or not, i.e. abort creating a new edge */ /** A valid connection function can determine if an attempted connection is valid or not, i.e. abort creating a new edge */
+11 -5
View File
@@ -27,7 +27,7 @@ import type { CoordinateExtent, GraphNode, Node } from './node'
import type { D3Selection, D3Zoom, D3ZoomHandler, PanOnScrollMode, ViewportFunctions, ViewportTransform } from './zoom' import type { D3Selection, D3Zoom, D3ZoomHandler, PanOnScrollMode, ViewportFunctions, ViewportTransform } from './zoom'
import type { CustomEvent, FlowHooks, FlowHooksEmit, FlowHooksOn } from './hooks' import type { CustomEvent, FlowHooks, FlowHooksEmit, FlowHooksOn } from './hooks'
import type { EdgeChange, NodeChange, NodeDragItem } from './changes' import type { EdgeChange, NodeChange, NodeDragItem } from './changes'
import type { StartHandle, ValidConnectionFunc, ValidHandleResult } from './handle' import type { ConnectingHandle, ValidConnectionFunc } from './handle'
export interface UpdateNodeDimensionsParams { export interface UpdateNodeDimensionsParams {
id: string id: string
@@ -84,8 +84,9 @@ export interface State extends Omit<FlowOptions, 'id' | 'modelValue'> {
connectionLineType: ConnectionLineType | null connectionLineType: ConnectionLineType | null
/** @deprecated use {@link ConnectionLineOptions.style} */ /** @deprecated use {@link ConnectionLineOptions.style} */
connectionLineStyle: CSSProperties | null connectionLineStyle: CSSProperties | null
connectionStartHandle: StartHandle | null connectionStartHandle: ConnectingHandle | null
connectionClickStartHandle: StartHandle | null connectionEndHandle: ConnectingHandle | null
connectionClickStartHandle: ConnectingHandle | null
connectionPosition: XYPosition connectionPosition: XYPosition
connectionRadius: number connectionRadius: number
connectionStatus: ConnectionStatus | null connectionStatus: ConnectionStatus | null
@@ -247,9 +248,14 @@ export interface Actions extends ViewportFunctions {
/** force update node internal data, if handle bounds are incorrect, you might want to use this */ /** force update node internal data, if handle bounds are incorrect, you might want to use this */
updateNodeInternals: UpdateNodeInternals updateNodeInternals: UpdateNodeInternals
/** start a connection */ /** start a connection */
startConnection: (startHandle: StartHandle, position?: XYPosition, event?: MouseEvent | TouchEvent, isClick?: boolean) => void startConnection: (
startHandle: ConnectingHandle,
position?: XYPosition,
event?: MouseEvent | TouchEvent,
isClick?: boolean,
) => void
/** update connection position */ /** update connection position */
updateConnection: (position: XYPosition, result?: ValidHandleResult, status?: ConnectionStatus | null) => void updateConnection: (position: XYPosition, result?: ConnectingHandle | null, status?: ConnectionStatus | null) => void
/** end (or cancel) a connection */ /** end (or cancel) a connection */
endConnection: (event?: MouseEvent | TouchEvent, isClick?: boolean) => void endConnection: (event?: MouseEvent | TouchEvent, isClick?: boolean) => void
+8 -3
View File
@@ -78,7 +78,7 @@ export function isValidHandle(
connectionMode: ConnectionMode, connectionMode: ConnectionMode,
fromNodeId: string, fromNodeId: string,
fromHandleId: string | null, fromHandleId: string | null,
fromType: string, fromType: HandleType,
isValidConnection: ValidConnectionFunc, isValidConnection: ValidConnectionFunc,
doc: Document | ShadowRoot, doc: Document | ShadowRoot,
edges: GraphEdge[], edges: GraphEdge[],
@@ -92,10 +92,10 @@ export function isValidHandle(
const handleToCheck = handleBelow?.classList.contains('vue-flow__handle') ? handleBelow : handleDomNode const handleToCheck = handleBelow?.classList.contains('vue-flow__handle') ? handleBelow : handleDomNode
const result: ValidHandleResult = { const result: ValidHandleResult = {
handleId: null,
handleDomNode: handleToCheck, handleDomNode: handleToCheck,
isValid: false, isValid: false,
connection: { source: '', target: '', sourceHandle: null, targetHandle: null }, connection: { source: '', target: '', sourceHandle: null, targetHandle: null },
endHandle: null,
} }
if (handleToCheck) { if (handleToCheck) {
@@ -113,7 +113,6 @@ export function isValidHandle(
} }
result.connection = connection result.connection = connection
result.handleId = handleId
const isConnectable = connectable && connectableEnd const isConnectable = connectable && connectableEnd
@@ -125,6 +124,12 @@ export function isValidHandle(
: handleNodeId !== fromNodeId || handleId !== fromHandleId) : handleNodeId !== fromNodeId || handleId !== fromHandleId)
if (isValid) { if (isValid) {
result.endHandle = {
nodeId: handleNodeId,
handleId,
type: handleType as HandleType,
}
result.isValid = isValidConnection(connection, { result.isValid = isValidConnection(connection, {
edges, edges,
sourceNode: findNode(connection.source)!, sourceNode: findNode(connection.source)!,