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(