@flowgram.ai/free-layout-core 0.1.0-alpha.35 → 0.1.0-alpha.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -318,7 +318,14 @@ var WorkflowPortEntity = class extends Entity {
318
318
  );
319
319
  }
320
320
  isHovered(x, y) {
321
- return this.bounds.contains(x, y);
321
+ const { point } = this;
322
+ const size = this._size || { width: PORT_SIZE, height: PORT_SIZE };
323
+ const halfWidth = size.width / 2;
324
+ const halfHeight = size.height / 2;
325
+ if (size.width <= 0 || size.height <= 0) {
326
+ return false;
327
+ }
328
+ return x >= point.x - halfWidth && x <= point.x + halfWidth && y >= point.y - halfHeight && y <= point.y + halfHeight;
322
329
  }
323
330
  /**
324
331
  * 相对节点左上角的位置
@@ -419,6 +426,7 @@ var WorkflowNodePortsData = class extends EntityData {
419
426
  this._staticPorts = [];
420
427
  /** 存储 port 实体的 id,用于判断 port 是否存在 */
421
428
  this._portIDSet = /* @__PURE__ */ new Set();
429
+ this._portDisposeIDSet = /* @__PURE__ */ new Set();
422
430
  this.entity = entity;
423
431
  const meta = entity.getNodeMeta();
424
432
  const defaultPorts = meta.useDynamicPort ? [] : [{ type: "input" }, { type: "output" }];
@@ -505,6 +513,7 @@ var WorkflowNodePortsData = class extends EntityData {
505
513
  });
506
514
  ports.forEach((port) => this.updatePortEntity(port));
507
515
  this._prePorts = ports;
516
+ this.clearPortsCache();
508
517
  this.fireChange();
509
518
  }
510
519
  this.allPorts.forEach((port) => {
@@ -517,19 +526,28 @@ var WorkflowNodePortsData = class extends EntityData {
517
526
  * 获取所有 port entities
518
527
  */
519
528
  get allPorts() {
520
- return Array.from(this._portIDSet).map((portId) => this.getPortEntity(portId)).filter(Boolean);
529
+ if (!this._allPortsCache) {
530
+ this._allPortsCache = Array.from(this._portIDSet).map((portId) => this.getPortEntity(portId)).filter(Boolean);
531
+ }
532
+ return this._allPortsCache;
521
533
  }
522
534
  /**
523
535
  * 获取输入点位
524
536
  */
525
537
  get inputPorts() {
526
- return this.allPorts.filter((port) => port.portType === "input");
538
+ if (!this._inputPortsCache) {
539
+ this._inputPortsCache = this.allPorts.filter((port) => port.portType === "input");
540
+ }
541
+ return this._inputPortsCache;
527
542
  }
528
543
  /**
529
544
  * 获取输出点位
530
545
  */
531
546
  get outputPorts() {
532
- return this.allPorts.filter((port) => port.portType === "output");
547
+ if (!this._outputPortsCache) {
548
+ this._outputPortsCache = this.allPorts.filter((port) => port.portType === "output");
549
+ }
550
+ return this._outputPortsCache;
533
551
  }
534
552
  /**
535
553
  * 获取输入点位置
@@ -570,6 +588,11 @@ var WorkflowNodePortsData = class extends EntityData {
570
588
  getPortId(portType, portKey = "") {
571
589
  return getPortEntityId(this.entity, portType, portKey);
572
590
  }
591
+ clearPortsCache() {
592
+ this._allPortsCache = void 0;
593
+ this._inputPortsCache = void 0;
594
+ this._outputPortsCache = void 0;
595
+ }
573
596
  /**
574
597
  * 创建 port 实体
575
598
  */
@@ -583,10 +606,18 @@ var WorkflowNodePortsData = class extends EntityData {
583
606
  ...portInfo
584
607
  });
585
608
  }
586
- portEntity.onDispose(() => {
587
- this._portIDSet.delete(id);
588
- });
589
- this._portIDSet.add(id);
609
+ if (!this._portDisposeIDSet.has(id)) {
610
+ this._portDisposeIDSet.add(id);
611
+ portEntity.onDispose(() => {
612
+ this._portIDSet.delete(id);
613
+ this._portDisposeIDSet.delete(id);
614
+ this.clearPortsCache();
615
+ });
616
+ }
617
+ if (!this._portIDSet.has(id)) {
618
+ this._portIDSet.add(id);
619
+ this.clearPortsCache();
620
+ }
590
621
  return portEntity;
591
622
  }
592
623
  /**
@@ -1448,7 +1479,7 @@ import {
1448
1479
  Point
1449
1480
  } from "@flowgram.ai/utils";
1450
1481
  import { FlowNodeTransformData as FlowNodeTransformData6 } from "@flowgram.ai/document";
1451
- import { FlowNodeBaseType as FlowNodeBaseType3 } from "@flowgram.ai/document";
1482
+ import { FlowNodeBaseType as FlowNodeBaseType4 } from "@flowgram.ai/document";
1452
1483
  import {
1453
1484
  CommandService,
1454
1485
  MouseTouchEvent,
@@ -1458,10 +1489,9 @@ import {
1458
1489
  } from "@flowgram.ai/core";
1459
1490
 
1460
1491
  // src/workflow-lines-manager.ts
1461
- import { last } from "lodash-es";
1462
1492
  import { inject as inject3, injectable as injectable3 } from "inversify";
1463
1493
  import { DisposableCollection, Emitter as Emitter4 } from "@flowgram.ai/utils";
1464
- import { FlowNodeRenderData as FlowNodeRenderData2, FlowNodeTransformData as FlowNodeTransformData3 } from "@flowgram.ai/document";
1494
+ import { FlowNodeBaseType as FlowNodeBaseType2, FlowNodeRenderData as FlowNodeRenderData2, FlowNodeTransformData as FlowNodeTransformData3 } from "@flowgram.ai/document";
1465
1495
  import { EntityManager as EntityManager2, PlaygroundConfigEntity as PlaygroundConfigEntity2 } from "@flowgram.ai/core";
1466
1496
 
1467
1497
  // src/workflow-document-option.ts
@@ -1568,6 +1598,10 @@ var WorkflowLinesManager = class {
1568
1598
  forceUpdate() {
1569
1599
  this.onForceUpdateEmitter.fire();
1570
1600
  }
1601
+ invalidateSortedNodesCache() {
1602
+ this.sortedNodesCache = void 0;
1603
+ this.sortedNodesCacheVersion = void 0;
1604
+ }
1571
1605
  get lineType() {
1572
1606
  return this._lineType;
1573
1607
  }
@@ -1754,13 +1788,18 @@ var WorkflowLinesManager = class {
1754
1788
  */
1755
1789
  getCloseInLineFromMousePos(mousePos, minDistance = LINE_HOVER_DISTANCE) {
1756
1790
  let targetLine, targetLineDist;
1757
- this.getAllLines().forEach((line) => {
1791
+ const lines = this.getAllLines();
1792
+ for (let i = 0; i < lines.length; i++) {
1793
+ const line = lines[i];
1794
+ if (!this.isPointInBounds(mousePos, line.bounds, minDistance)) {
1795
+ continue;
1796
+ }
1758
1797
  const dist = line.getHoverDist(mousePos);
1759
- if (dist <= minDistance && (!targetLineDist || targetLineDist >= dist)) {
1798
+ if (dist <= minDistance && (targetLineDist === void 0 || targetLineDist >= dist)) {
1760
1799
  targetLineDist = dist;
1761
1800
  targetLine = line;
1762
1801
  }
1763
- });
1802
+ }
1764
1803
  return targetLine;
1765
1804
  }
1766
1805
  dispose() {
@@ -1879,39 +1918,44 @@ var WorkflowLinesManager = class {
1879
1918
  * @param pos
1880
1919
  */
1881
1920
  getPortFromMousePos(pos, portType) {
1882
- const allNodes = this.getSortedNodes().reverse();
1883
- const allPorts = allNodes.map((node) => {
1884
- if (!portType) {
1885
- return node.ports.allPorts;
1886
- }
1887
- return portType === "input" ? node.ports.inputPorts : node.ports.outputPorts;
1888
- }).flat();
1889
- const targetPort = allPorts.find((port) => port.isHovered(pos.x, pos.y));
1890
- if (targetPort) {
1891
- const containNodes = this.getContainNodesFromMousePos(pos);
1892
- const targetNode = last(containNodes);
1893
- if (targetNode && targetNode !== targetPort.node) {
1894
- return;
1921
+ return this.getHoveredPortFromSortedNodes(pos, this.getSortedNodes(), portType);
1922
+ }
1923
+ getPortsFromMousePos(pos) {
1924
+ return this.getHoveredPortsFromSortedNodes(pos, this.getSortedNodes(), void 0, {
1925
+ collectBoth: true
1926
+ });
1927
+ }
1928
+ getNodeAndPortFromMousePos(pos, portType) {
1929
+ const sortedNodes = this.getSortedNodes();
1930
+ const nodeHitInfo = this.getNodeHitInfoFromSortedNodes(
1931
+ pos,
1932
+ sortedNodes,
1933
+ this.selectService.selection
1934
+ );
1935
+ const ports = this.getHoveredPortsFromSortedNodes(pos, sortedNodes, portType, {
1936
+ topCoverNode: nodeHitInfo.topCoverNode
1937
+ });
1938
+ return {
1939
+ node: nodeHitInfo.topNode,
1940
+ port: ports.port
1941
+ };
1942
+ }
1943
+ getHoverNodeFromMousePos(pos) {
1944
+ const nodes = this.getHoverNodes();
1945
+ for (let i = 0; i < nodes.length; i++) {
1946
+ const node = nodes[i];
1947
+ const { bounds } = node.getData(FlowNodeTransformData3);
1948
+ if (this.isPointInBounds(pos, bounds)) {
1949
+ return node;
1895
1950
  }
1896
1951
  }
1897
- return targetPort;
1898
1952
  }
1899
1953
  /**
1900
1954
  * 根据鼠标位置找到 node
1901
1955
  * @param pos - 鼠标位置
1902
1956
  */
1903
1957
  getNodeFromMousePos(pos) {
1904
- const { selection } = this.selectService;
1905
- const containNodes = this.getContainNodesFromMousePos(pos);
1906
- if (selection?.length) {
1907
- const filteredNodes = containNodes.filter(
1908
- (node) => selection.some((_node) => node.id === _node.id)
1909
- );
1910
- if (filteredNodes?.length) {
1911
- return last(filteredNodes);
1912
- }
1913
- }
1914
- return last(containNodes);
1958
+ return this.getTopNodeFromSortedNodes(pos, this.getSortedNodes(), this.selectService.selection);
1915
1959
  }
1916
1960
  registerContribution(factory) {
1917
1961
  this.contributionFactories.push(factory);
@@ -1921,19 +1965,125 @@ var WorkflowLinesManager = class {
1921
1965
  line.addData(WorkflowLineRenderData);
1922
1966
  }
1923
1967
  getSortedNodes() {
1924
- return this.document.getAllNodes().sort((a, b) => this.getNodeIndex(a) - this.getNodeIndex(b));
1968
+ const nodeVersion = this.entityManager.getEntityVersion(WorkflowNodeEntity);
1969
+ if (this.sortedNodesCache && this.sortedNodesCacheVersion === nodeVersion) {
1970
+ return this.sortedNodesCache;
1971
+ }
1972
+ this.sortedNodesCache = [...this.document.getAllNodes()].sort(
1973
+ (a, b) => this.getNodeIndex(a) - this.getNodeIndex(b)
1974
+ );
1975
+ this.sortedNodesCacheVersion = nodeVersion;
1976
+ return this.sortedNodesCache;
1977
+ }
1978
+ getHoveredPortFromSortedNodes(pos, sortedNodes, portType) {
1979
+ return this.getHoveredPortsFromSortedNodes(pos, sortedNodes, portType).port;
1980
+ }
1981
+ getHoveredPortsFromSortedNodes(pos, sortedNodes, portType, options = {}) {
1982
+ let inputPort;
1983
+ let outputPort;
1984
+ let anyPort;
1985
+ const { collectBoth = false } = options;
1986
+ for (let i = sortedNodes.length - 1; i >= 0; i--) {
1987
+ const node = sortedNodes[i];
1988
+ if (!portType && !anyPort) {
1989
+ anyPort = this.findHoveredPort(node.ports.allPorts, pos);
1990
+ }
1991
+ if ((!portType || portType === "output") && !outputPort) {
1992
+ outputPort = this.findHoveredPort(node.ports.outputPorts, pos);
1993
+ }
1994
+ if ((!portType || portType === "input") && !inputPort) {
1995
+ inputPort = this.findHoveredPort(node.ports.inputPorts, pos);
1996
+ }
1997
+ if (portType === "input" && inputPort || portType === "output" && outputPort || !portType && (collectBoth ? inputPort && outputPort : anyPort)) {
1998
+ break;
1999
+ }
2000
+ }
2001
+ const needCoverCheck = inputPort || outputPort || anyPort;
2002
+ const topCoverNode = needCoverCheck ? options.topCoverNode || this.getNodeHitInfoFromSortedNodes(pos, sortedNodes).topCoverNode : void 0;
2003
+ inputPort = this.filterCoveredPort(inputPort, topCoverNode);
2004
+ outputPort = this.filterCoveredPort(outputPort, topCoverNode);
2005
+ anyPort = this.filterCoveredPort(anyPort, topCoverNode);
2006
+ return {
2007
+ input: inputPort,
2008
+ output: outputPort,
2009
+ port: portType === "input" ? inputPort : portType === "output" ? outputPort : anyPort
2010
+ };
1925
2011
  }
1926
- /** 获取鼠标坐标位置的所有节点(stackIndex 从小到大排序) */
1927
- getContainNodesFromMousePos(pos) {
1928
- const allNodes = this.getSortedNodes();
2012
+ findHoveredPort(ports, pos) {
2013
+ for (let i = 0; i < ports.length; i++) {
2014
+ const port = ports[i];
2015
+ if (port.isHovered(pos.x, pos.y)) {
2016
+ return port;
2017
+ }
2018
+ }
2019
+ }
2020
+ filterCoveredPort(port, topCoverNode) {
2021
+ if (port && topCoverNode && topCoverNode !== port.node) {
2022
+ return void 0;
2023
+ }
2024
+ return port;
2025
+ }
2026
+ getTopNodeFromSortedNodes(pos, sortedNodes, selection) {
2027
+ return this.getNodeHitInfoFromSortedNodes(pos, sortedNodes, selection).topNode;
2028
+ }
2029
+ getNodeHitInfoFromSortedNodes(pos, sortedNodes, selection) {
1929
2030
  const zoom = this.entityManager.getEntity(PlaygroundConfigEntity2)?.config?.zoom || 1;
1930
- const containNodes = allNodes.map((node) => {
2031
+ const padding = 4 / zoom;
2032
+ const selectedIDs = selection?.length ? new Set(selection.map((node) => node.id)) : void 0;
2033
+ let topCoverNode;
2034
+ for (let i = sortedNodes.length - 1; i >= 0; i--) {
2035
+ const node = sortedNodes[i];
1931
2036
  const { bounds } = node.getData(FlowNodeTransformData3);
1932
- if (bounds.clone().pad(4 / zoom).contains(pos.x, pos.y)) {
1933
- return node;
2037
+ if (!this.isPointInBounds(pos, bounds, padding)) {
2038
+ continue;
1934
2039
  }
1935
- }).filter(Boolean);
1936
- return containNodes;
2040
+ if (!topCoverNode) {
2041
+ topCoverNode = node;
2042
+ }
2043
+ if (!selectedIDs) {
2044
+ return {
2045
+ topCoverNode,
2046
+ topNode: node
2047
+ };
2048
+ }
2049
+ if (selectedIDs.has(node.id)) {
2050
+ return {
2051
+ topCoverNode,
2052
+ topNode: node
2053
+ };
2054
+ }
2055
+ }
2056
+ return {
2057
+ topCoverNode,
2058
+ topNode: topCoverNode
2059
+ };
2060
+ }
2061
+ getHoverNodes() {
2062
+ const nodeVersion = this.entityManager.getEntityVersion(WorkflowNodeEntity);
2063
+ const activatedID = this.selectService.activatedNode?.id;
2064
+ if (this.hoverNodesCache && this.hoverNodesCacheVersion === nodeVersion && this.hoverNodesCacheActivatedID === activatedID) {
2065
+ return this.hoverNodesCache;
2066
+ }
2067
+ const nodes = this.document.getAllNodes().filter(
2068
+ (node) => node.id !== "root" && node.flowNodeType !== FlowNodeBaseType2.ROOT && node.flowNodeType !== FlowNodeBaseType2.GROUP
2069
+ ).reverse();
2070
+ if (activatedID) {
2071
+ const activatedIndex = nodes.findIndex((node) => node.id === activatedID);
2072
+ if (activatedIndex > 0) {
2073
+ const [activatedNode] = nodes.splice(activatedIndex, 1);
2074
+ nodes.unshift(activatedNode);
2075
+ }
2076
+ }
2077
+ this.hoverNodesCache = nodes;
2078
+ this.hoverNodesCacheVersion = nodeVersion;
2079
+ this.hoverNodesCacheActivatedID = activatedID;
2080
+ return this.hoverNodesCache;
2081
+ }
2082
+ isPointInBounds(pos, bounds, padding = 0) {
2083
+ if (bounds.width + padding * 2 <= 0 || bounds.height + padding * 2 <= 0) {
2084
+ return false;
2085
+ }
2086
+ return pos.x >= bounds.x - padding && pos.x <= bounds.right + padding && pos.y >= bounds.y - padding && pos.y <= bounds.bottom + padding;
1937
2087
  }
1938
2088
  getNodeIndex(node) {
1939
2089
  const nodeRenderData = node.getData(FlowNodeRenderData2);
@@ -1963,7 +2113,7 @@ import { Emitter as Emitter5 } from "@flowgram.ai/utils";
1963
2113
  import { NodeEngineContext } from "@flowgram.ai/form-core";
1964
2114
  import {
1965
2115
  FlowDocument,
1966
- FlowNodeBaseType as FlowNodeBaseType2,
2116
+ FlowNodeBaseType as FlowNodeBaseType3,
1967
2117
  FlowNodeTransformData as FlowNodeTransformData5
1968
2118
  } from "@flowgram.ai/document";
1969
2119
  import {
@@ -2277,7 +2427,7 @@ var WorkflowDocument = class extends FlowDocument {
2277
2427
  toJSON: () => this.toNodeJSON(node)
2278
2428
  });
2279
2429
  node.onDispose(() => {
2280
- if (!node.parent || node.parent.flowNodeType === FlowNodeBaseType2.ROOT) {
2430
+ if (!node.parent || node.parent.flowNodeType === FlowNodeBaseType3.ROOT) {
2281
2431
  return;
2282
2432
  }
2283
2433
  const parentTransform = node.parent.getData(FlowNodeTransformData5);
@@ -2448,13 +2598,13 @@ var WorkflowDocument = class extends FlowDocument {
2448
2598
  );
2449
2599
  }
2450
2600
  getAllNodes() {
2451
- return this.entityManager.getEntities(WorkflowNodeEntity).filter((n) => n.id !== FlowNodeBaseType2.ROOT);
2601
+ return this.entityManager.getEntities(WorkflowNodeEntity).filter((n) => n.id !== FlowNodeBaseType3.ROOT);
2452
2602
  }
2453
2603
  getAllEdges() {
2454
2604
  return this.entityManager.getEntities(WorkflowLineEntity);
2455
2605
  }
2456
2606
  getAllPorts() {
2457
- return this.entityManager.getEntities(WorkflowPortEntity).filter((p) => p.node.id !== FlowNodeBaseType2.ROOT);
2607
+ return this.entityManager.getEntities(WorkflowPortEntity).filter((p) => p.node.id !== FlowNodeBaseType3.ROOT);
2458
2608
  }
2459
2609
  /**
2460
2610
  * 获取画布中的非游离节点
@@ -2666,7 +2816,7 @@ var WorkflowDocument = class extends FlowDocument {
2666
2816
  return subCanvas;
2667
2817
  }
2668
2818
  getNodeChildren(node) {
2669
- if (!node || node.flowNodeType === FlowNodeBaseType2.GROUP) return [];
2819
+ if (!node || node.flowNodeType === FlowNodeBaseType3.GROUP) return [];
2670
2820
  const subCanvas = this.getNodeSubCanvas(node);
2671
2821
  const realChildren = subCanvas ? subCanvas.canvasNode.blocks : node.blocks;
2672
2822
  const childrenWithoutSubCanvas = realChildren.filter((child) => {
@@ -2674,7 +2824,7 @@ var WorkflowDocument = class extends FlowDocument {
2674
2824
  return !childMeta.subCanvas?.(node)?.isCanvas;
2675
2825
  }).filter(Boolean);
2676
2826
  const children = childrenWithoutSubCanvas.map((child) => {
2677
- if (child.flowNodeType === FlowNodeBaseType2.GROUP) {
2827
+ if (child.flowNodeType === FlowNodeBaseType3.GROUP) {
2678
2828
  return [child, ...child.blocks];
2679
2829
  }
2680
2830
  return child;
@@ -2993,7 +3143,7 @@ var WorkflowDragService = class {
2993
3143
  if (!mousePos) {
2994
3144
  return { x: 0, y: 0 };
2995
3145
  }
2996
- if (!subNodeType || !containerNode || containerNode.flowNodeType === FlowNodeBaseType3.ROOT) {
3146
+ if (!subNodeType || !containerNode || containerNode.flowNodeType === FlowNodeBaseType4.ROOT) {
2997
3147
  return mousePos;
2998
3148
  }
2999
3149
  const isParentEmpty = !containerNode.children || containerNode.children.length === 0;
@@ -3206,7 +3356,7 @@ var WorkflowDragService = class {
3206
3356
  return;
3207
3357
  }
3208
3358
  const sourceContainer = nodes[0]?.parent;
3209
- if (!sourceContainer || sourceContainer.flowNodeType === FlowNodeBaseType3.ROOT) {
3359
+ if (!sourceContainer || sourceContainer.flowNodeType === FlowNodeBaseType4.ROOT) {
3210
3360
  return;
3211
3361
  }
3212
3362
  const valid = nodes.every((node) => node?.parent === sourceContainer);
@@ -3351,15 +3501,15 @@ var WorkflowDragService = class {
3351
3501
  }
3352
3502
  updateDrawingLine(isDrawingTo, line, dragPos, originLine) {
3353
3503
  let hasError = false;
3354
- const mouseNode = this.linesManager.getNodeFromMousePos(dragPos);
3355
3504
  let toNode;
3356
3505
  let toPort;
3357
3506
  let fromPort;
3358
3507
  let fromNode;
3359
3508
  if (isDrawingTo) {
3509
+ const mouseHitInfo = this.linesManager.getNodeAndPortFromMousePos(dragPos, "input");
3360
3510
  fromPort = line.fromPort;
3361
- toNode = mouseNode;
3362
- toPort = this.linesManager.getPortFromMousePos(dragPos, "input");
3511
+ toNode = mouseHitInfo.node;
3512
+ toPort = mouseHitInfo.port;
3363
3513
  if (toNode && this.canBuildContainerLine(toNode, dragPos)) {
3364
3514
  toPort = this.getNearestPort(toNode, dragPos, "input");
3365
3515
  hasError = this.checkDraggingPort(isDrawingTo, line, toNode, toPort, originLine).hasError;
@@ -3386,9 +3536,10 @@ var WorkflowDragService = class {
3386
3536
  };
3387
3537
  }
3388
3538
  } else {
3539
+ const mouseHitInfo = this.linesManager.getNodeAndPortFromMousePos(dragPos, "output");
3389
3540
  toPort = line.toPort;
3390
- fromNode = mouseNode;
3391
- fromPort = this.linesManager.getPortFromMousePos(dragPos, "output");
3541
+ fromNode = mouseHitInfo.node;
3542
+ fromPort = mouseHitInfo.port;
3392
3543
  if (fromNode && this.canBuildContainerLine(fromNode, dragPos)) {
3393
3544
  fromPort = this.getNearestPort(fromNode, dragPos, "output");
3394
3545
  hasError = this.checkDraggingPort(