fix(core): support hover line or port in deep layer container (#443)

This commit is contained in:
Louis Young 2025-07-02 16:46:38 +08:00 committed by GitHub
parent 522bc0770d
commit 0a9c3a0167
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 50 additions and 49 deletions

View File

@ -66,6 +66,8 @@ export class WorkflowLineEntity extends Entity<WorkflowLineEntityOpts> {
private _hasError = false; private _hasError = false;
public stackIndex = 0;
/** /**
* 线 * 线
*/ */

View File

@ -7,7 +7,7 @@ import { last } from 'lodash-es';
import { inject, injectable } from 'inversify'; import { inject, injectable } from 'inversify';
import { DisposableCollection, Emitter, type IPoint } from '@flowgram.ai/utils'; import { DisposableCollection, Emitter, type IPoint } from '@flowgram.ai/utils';
import { FlowNodeRenderData, FlowNodeTransformData } from '@flowgram.ai/document'; import { FlowNodeRenderData, FlowNodeTransformData } from '@flowgram.ai/document';
import { EntityManager, PlaygroundConfigEntity, TransformData } from '@flowgram.ai/core'; import { EntityManager, PlaygroundConfigEntity } from '@flowgram.ai/core';
import { WorkflowDocumentOptions } from './workflow-document-option'; import { WorkflowDocumentOptions } from './workflow-document-option';
import { type WorkflowDocument } from './workflow-document'; import { type WorkflowDocument } from './workflow-document';
@ -416,13 +416,8 @@ export class WorkflowLinesManager {
.filter((port) => port.node.flowNodeType !== 'root'); .filter((port) => port.node.flowNodeType !== 'root');
const targetPort = allPorts.find((port) => port.isHovered(pos.x, pos.y)); const targetPort = allPorts.find((port) => port.isHovered(pos.x, pos.y));
if (targetPort) { if (targetPort) {
// 后创建的要先校验 const containNodes = this.getContainNodesFromMousePos(pos);
const targetNode = this.document const targetNode = last(containNodes);
.getAllNodes()
.slice()
.reverse()
.filter((node) => targetPort.node?.parent?.id !== node.id)
.find((node) => node.getData(TransformData)!.contains(pos.x, pos.y));
// 点位可能会被节点覆盖 // 点位可能会被节点覆盖
if (targetNode && targetNode !== targetPort.node) { if (targetNode && targetNode !== targetPort.node) {
return; return;
@ -436,27 +431,9 @@ export class WorkflowLinesManager {
* @param pos - * @param pos -
*/ */
getNodeFromMousePos(pos: IPoint): WorkflowNodeEntity | undefined { getNodeFromMousePos(pos: IPoint): WorkflowNodeEntity | undefined {
const allNodes = this.document
.getAllNodes()
.sort((a, b) => this.getNodeIndex(a) - this.getNodeIndex(b));
// 先挑选出 bounds 区域符合的 node // 先挑选出 bounds 区域符合的 node
const containNodes: WorkflowNodeEntity[] = [];
const { selection } = this.selectService; const { selection } = this.selectService;
const zoom = const containNodes = this.getContainNodesFromMousePos(pos);
this.entityManager.getEntity<PlaygroundConfigEntity>(PlaygroundConfigEntity)?.config?.zoom ||
1;
allNodes.forEach((node) => {
const { bounds } = node.getData<FlowNodeTransformData>(FlowNodeTransformData);
// 交互要求,节点边缘 4px 的时候就生效连线逻辑
if (
bounds
.clone()
.pad(4 / zoom)
.contains(pos.x, pos.y)
) {
containNodes.push(node);
}
});
// 当有元素被选中的时候选中元素在顶层 // 当有元素被选中的时候选中元素在顶层
if (selection?.length) { if (selection?.length) {
const filteredNodes = containNodes.filter((node) => const filteredNodes = containNodes.filter((node) =>
@ -479,6 +456,31 @@ export class WorkflowLinesManager {
line.addData(WorkflowLineRenderData); line.addData(WorkflowLineRenderData);
} }
/** 获取鼠标坐标位置的所有节点stackIndex 从小到大排序) */
private getContainNodesFromMousePos(pos: IPoint): WorkflowNodeEntity[] {
const allNodes = this.document
.getAllNodes()
.sort((a, b) => this.getNodeIndex(a) - this.getNodeIndex(b));
const zoom =
this.entityManager.getEntity<PlaygroundConfigEntity>(PlaygroundConfigEntity)?.config?.zoom ||
1;
const containNodes = allNodes
.map((node) => {
const { bounds } = node.getData<FlowNodeTransformData>(FlowNodeTransformData);
// 交互要求,节点边缘 4px 的时候就认为选中节点
if (
bounds
.clone()
.pad(4 / zoom)
.contains(pos.x, pos.y)
) {
return node;
}
})
.filter(Boolean) as WorkflowNodeEntity[];
return containNodes;
}
private getNodeIndex(node: WorkflowNodeEntity): number { private getNodeIndex(node: WorkflowNodeEntity): number {
const nodeRenderData = node.getData(FlowNodeRenderData); const nodeRenderData = node.getData(FlowNodeRenderData);
return nodeRenderData.stackIndex; return nodeRenderData.stackIndex;

View File

@ -236,38 +236,32 @@ export class HoverLayer extends Layer<HoverLayerOptions> {
} }
} }
const nodeInContainer = !!(nodeHovered?.parent && nodeHovered.parent.flowNodeType !== 'root');
// 获取最接近的线条 // 获取最接近的线条
// 线条会相交需要获取最接近点位的线条,不能删除的线条不能被选中 // 线条会相交需要获取最接近点位的线条,不能删除的线条不能被选中
const lineHovered = checkTargetFromLine const lineHovered = checkTargetFromLine
? this.linesManager.getCloseInLineFromMousePos(mousePos) ? this.linesManager.getCloseInLineFromMousePos(mousePos)
: undefined; : undefined;
const lineInContainer = !!lineHovered?.inContainer;
// 判断容器内节点是否 hover if (nodeHovered && lineHovered) {
if (nodeHovered && nodeInContainer) { const nodeStackIndex = nodeHovered.renderData.stackIndex;
this.updateHoveredKey(nodeHovered.id); const lineStackIndex = lineHovered.stackIndex;
return; if (nodeStackIndex > lineStackIndex) {
} return this.updateHoveredKey(nodeHovered.id);
// 判断容器内线条是否 hover } else {
if (lineHovered && lineInContainer) { return this.updateHoveredKey(lineHovered.id);
this.updateHoveredKey(lineHovered.id); }
return;
} }
// 判断节点是否 hover // 判断节点是否 hover
if (nodeHovered) { if (nodeHovered) {
this.updateHoveredKey(nodeHovered.id); return this.updateHoveredKey(nodeHovered.id);
return;
} }
// 判断线条是否 hover // 判断线条是否 hover
if (lineHovered) { if (lineHovered) {
this.hoverService.updateHoveredKey(lineHovered.id); return this.updateHoveredKey(lineHovered.id);
return;
} }
// 上述逻辑都未命中 则清空 hoverd // 上述逻辑都未命中 则清空 hovered
hoverService.clearHovered(); hoverService.clearHovered();
const currentState = this.editorStateConfig.getCurrentState(); const currentState = this.editorStateConfig.getCurrentState();

View File

@ -80,23 +80,26 @@ export class StackingContextManager {
const element = nodeRenderData.node; const element = nodeRenderData.node;
element.style.position = 'absolute'; element.style.position = 'absolute';
if (level === undefined) { if (level === undefined) {
element.style.zIndex = 'auto';
nodeRenderData.stackIndex = 0; nodeRenderData.stackIndex = 0;
element.style.zIndex = 'auto';
return; return;
} }
const stackIndex = StackingConfig.startIndex + level; nodeRenderData.stackIndex = level;
element.style.zIndex = String(stackIndex); const zIndex = StackingConfig.startIndex + level;
nodeRenderData.stackIndex = stackIndex; element.style.zIndex = String(zIndex);
}); });
this.lines.forEach((line) => { this.lines.forEach((line) => {
const level = lineLevel.get(line.id); const level = lineLevel.get(line.id);
const element = line.node; const element = line.node;
element.style.position = 'absolute'; element.style.position = 'absolute';
if (level === undefined) { if (level === undefined) {
line.stackIndex = 0;
element.style.zIndex = 'auto'; element.style.zIndex = 'auto';
return; return;
} }
element.style.zIndex = String(StackingConfig.startIndex + level); line.stackIndex = level;
const zIndex = StackingConfig.startIndex + level;
element.style.zIndex = String(zIndex);
}); });
} }