diff --git a/packages/core/src/model/EditConfigModel.ts b/packages/core/src/model/EditConfigModel.ts index 85f98eabc..27a809699 100644 --- a/packages/core/src/model/EditConfigModel.ts +++ b/packages/core/src/model/EditConfigModel.ts @@ -126,6 +126,7 @@ export interface IEditConfigType { // 开启网格对齐 snapGrid: boolean isPinching: boolean + anchorProximityValidate: boolean } export type IConfigKeys = keyof IEditConfigType @@ -188,6 +189,7 @@ const allKeys = [ 'nodeTextVertical', // 节点文本是否纵向显示 'edgeTextVertical', // 边文本是否纵向显示 'isPinching', //是否是双指捏合态 + 'anchorProximityValidate', // 仅在靠近锚点时触发连接校验 ] as const /** @@ -223,6 +225,7 @@ export class EditConfigModel { @observable edgeTextDraggable = false @observable edgeTextMultiple = false // 是否支持多个边文本 @observable edgeTextVertical = false // 边文本朝向是否是纵向 + @observable anchorProximityValidate = false // 仅在靠近锚点时触发连接校验 /********************************************************* * 节点相关配置 ********************************************************/ diff --git a/packages/core/src/model/GraphModel.ts b/packages/core/src/model/GraphModel.ts index 4c8883090..0025d337f 100644 --- a/packages/core/src/model/GraphModel.ts +++ b/packages/core/src/model/GraphModel.ts @@ -89,6 +89,8 @@ export class GraphModel { idGenerator?: (type?: string) => string | undefined // 节点间连线、连线变更时的边的生成规则 edgeGenerator: LFOptions.Definition['edgeGenerator'] + // 自定义目标锚点连接规则 + customTargetAnchor?: LFOptions.Definition['customTargetAnchor'] // Remind:用于记录当前画布上所有节点和边的 model 的 Map // 现在的处理方式,用 this.nodes.map 生成的方式,如果在 new Model 的过程中依赖于其它节点的 model,会出现找不到的情况 @@ -163,6 +165,7 @@ export class GraphModel { edgeGenerator, animation, customTrajectory, + customTargetAnchor, } = options this.rootEl = container this.partial = !!partial @@ -216,6 +219,7 @@ export class GraphModel { this.idGenerator = idGenerator this.edgeGenerator = createEdgeGenerator(this, edgeGenerator) this.customTrajectory = customTrajectory + this.customTargetAnchor = customTargetAnchor } @computed get nodesMap(): GraphModel.NodesMapType { diff --git a/packages/core/src/model/node/BaseNodeModel.ts b/packages/core/src/model/node/BaseNodeModel.ts index 112ce268c..3972ec9d6 100644 --- a/packages/core/src/model/node/BaseNodeModel.ts +++ b/packages/core/src/model/node/BaseNodeModel.ts @@ -635,7 +635,10 @@ export class BaseNodeModel

* 手动连接边到节点时,需要连接的锚点 */ public getTargetAnchor(position: Point): Model.AnchorInfo { - return getClosestAnchor(position, this) + const { customTargetAnchor } = this.graphModel + return ( + customTargetAnchor?.(this, position) ?? getClosestAnchor(position, this) + ) } /** diff --git a/packages/core/src/options.ts b/packages/core/src/options.ts index 61c7565d6..18a7658f7 100644 --- a/packages/core/src/options.ts +++ b/packages/core/src/options.ts @@ -1,4 +1,4 @@ -import type { TransformModel } from './model' +import type { TransformModel, BaseNodeModel, Model } from './model' import { assign } from 'lodash-es' import { createElement as h } from 'preact/compat' @@ -41,6 +41,10 @@ export namespace Options { currentEdge?: Partial, ) => any + export type customTargetAnchorType = ( + nodeModel: BaseNodeModel, + position: LogicFlow.Point, + ) => Model.AnchorInfo | undefined export interface CustomAnchorLineProps { sourcePoint: LogicFlow.Point targetPoint: LogicFlow.Point @@ -104,6 +108,7 @@ export namespace Options { idGenerator?: (type?: string) => string edgeGenerator?: EdgeGeneratorType + customTargetAnchor?: customTargetAnchorType customTrajectory?: (props: CustomAnchorLineProps) => h.JSX.Element themeMode?: 'radius' | 'dark' | 'colorful' // 主题模式 diff --git a/packages/core/src/util/drag.ts b/packages/core/src/util/drag.ts index 26a3dfa61..c6bc446ba 100644 --- a/packages/core/src/util/drag.ts +++ b/packages/core/src/util/drag.ts @@ -103,7 +103,6 @@ export class StepDrag { const DOC: any = window?.document if (e.button !== LEFT_MOUSE_BUTTON_CODE) return if (this.isStopPropagation) e.stopPropagation() - e.preventDefault() this.isStartDragging = true this.startX = e.clientX this.startY = e.clientY diff --git a/packages/core/src/view/Anchor.tsx b/packages/core/src/view/Anchor.tsx index 2f7bd7a51..6d23f2667 100644 --- a/packages/core/src/view/Anchor.tsx +++ b/packages/core/src/view/Anchor.tsx @@ -314,41 +314,21 @@ class Anchor extends Component { return } this.preTargetNode = targetNode - // 支持节点的每个锚点单独设置是否可连接,因此规则key去nodeId + anchorId作为唯一值 - const targetInfoId = `${nodeModel.id}_${targetNode.id}_${anchorId}_${anchorData.id}` - - // 查看鼠标是否进入过target,若有检验结果,表示进入过, 就不重复计算了。 - if (!this.targetRuleResults.has(targetInfoId)) { - const targetAnchor = info.anchor - const sourceRuleResult = nodeModel.isAllowConnectedAsSource( + const anchorDist = distance(endX, endY, info.anchor.x, info.anchor.y) + const validateDistance = 10 + const { editConfigModel } = graphModel + if ( + !editConfigModel.anchorProximityValidate || + anchorDist <= validateDistance + ) { + this.validateAndSetState( targetNode, - anchorData, - targetAnchor, - ) - const targetRuleResult = targetNode.isAllowConnectedAsTarget( + anchorId, + info.anchor, nodeModel, anchorData, - targetAnchor, - ) - this.sourceRuleResults.set( - targetInfoId, - formatAnchorConnectValidateData(sourceRuleResult), - ) - this.targetRuleResults.set( - targetInfoId, - formatAnchorConnectValidateData(targetRuleResult), ) } - const { isAllPass: isSourcePass } = - this.sourceRuleResults.get(targetInfoId) ?? {} - const { isAllPass: isTargetPass } = - this.targetRuleResults.get(targetInfoId) ?? {} - // 实时提示出即将链接的锚点 - if (isSourcePass && isTargetPass) { - targetNode.setElementState(ElementState.ALLOW_CONNECT) - } else { - targetNode.setElementState(ElementState.NOT_ALLOW_CONNECT) - } // 人工触发进入目标节点事件,同步设置 hovered 以驱动锚点显隐和样式 if (!targetNode.isHovered) { const nodeData = targetNode.getData() @@ -379,6 +359,49 @@ class Anchor extends Component { } } + // 校验 source/target 连接规则并设置目标节点状态 + validateAndSetState( + targetNode: BaseNodeModel, + anchorId: string | undefined, + targetAnchor: AnchorConfig, + nodeModel: BaseNodeModel, + anchorData: AnchorConfig, + ) { + const targetInfoId = `${nodeModel.id}_${targetNode.id}_${anchorId}_${anchorData.id}` + if (!this.targetRuleResults.has(targetInfoId)) { + // 首次计算并缓存源/目标两侧的规则校验结果 + const sourceRuleResult = nodeModel.isAllowConnectedAsSource( + targetNode, + anchorData, + targetAnchor, + ) + const targetRuleResult = targetNode.isAllowConnectedAsTarget( + nodeModel, + anchorData, + targetAnchor, + ) + this.sourceRuleResults.set( + targetInfoId, + formatAnchorConnectValidateData(sourceRuleResult), + ) + this.targetRuleResults.set( + targetInfoId, + formatAnchorConnectValidateData(targetRuleResult), + ) + } + // 读取缓存的校验结果 + const { isAllPass: isSourcePass } = + this.sourceRuleResults.get(targetInfoId) ?? {} + const { isAllPass: isTargetPass } = + this.targetRuleResults.get(targetInfoId) ?? {} + // 两侧都通过则允许连接,否则标记为不允许连接 + if (isSourcePass && isTargetPass) { + targetNode.setElementState(ElementState.ALLOW_CONNECT) + } else { + targetNode.setElementState(ElementState.NOT_ALLOW_CONNECT) + } + } + isShowLine() { const { startX, startY, endX, endY } = this.state const v = distance(startX, startY, endX, endY) diff --git a/packages/extension/src/insert-node-in-polyline/index.ts b/packages/extension/src/insert-node-in-polyline/index.ts index 66f60df9f..c521f5315 100644 --- a/packages/extension/src/insert-node-in-polyline/index.ts +++ b/packages/extension/src/insert-node-in-polyline/index.ts @@ -3,6 +3,7 @@ import LogicFlow, { PolylineEdgeModel, EventType, formatAnchorConnectValidateData, + getClosestAnchor, } from '@logicflow/core' import { cloneDeep } from 'lodash-es' import { isNodeInSegment } from './edge' @@ -132,7 +133,6 @@ export class InsertNodeInPolyline { } = edges[i] // fix https://github.com/didi/LogicFlow/issues/996 const startPoint = cloneDeep(pointsList[0]) - const endPoint = cloneDeep(crossPoints.startCrossPoint) this._lf.deleteEdge(id) const checkResult = this.checkRuleBeforeInsetNode( sourceNodeId, @@ -141,27 +141,33 @@ export class InsertNodeInPolyline { targetAnchorId!, nodeData, ) + // 基于插入节点的进入交点计算出最近的“进入锚点”,用于重连原边的前半段 + const entryAnchorInfo = getClosestAnchor( + crossPoints.startCrossPoint, + nodeModel, + ) + const entryAnchor = entryAnchorInfo.anchor + // 构造第一条边:原 source → 插入节点(终点为进入锚点) this._lf.addEdge({ type, sourceNodeId, targetNodeId: nodeData.id, startPoint, - endPoint, - pointsList: [ - ...pointsList.slice(0, crossIndex), - crossPoints.startCrossPoint, - ], + endPoint: entryAnchor, }) + // 基于插入节点的离开交点计算出最近的“离开锚点”,用于重连原边的后半段 + const exitAnchorInfo = getClosestAnchor( + crossPoints.endCrossPoint, + nodeModel, + ) + const exitAnchor = exitAnchorInfo.anchor + // 构造第二条边:插入节点 → 原 target(起点为离开锚点) this._lf.addEdge({ type, sourceNodeId: nodeData.id, targetNodeId, - startPoint: cloneDeep(crossPoints.endCrossPoint), + startPoint: cloneDeep(exitAnchor), endPoint: cloneDeep(pointsList[pointsList.length - 1]), - pointsList: [ - crossPoints.endCrossPoint, - ...pointsList.slice(crossIndex), - ], }) if (!checkResult.isPass) { this._lf.graphModel.eventCenter.emit(