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

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
  * 获取输入点位置
@@ -541,7 +559,7 @@ var WorkflowNodePortsData = class extends EntityData {
541
559
  * 获取输出点位置
542
560
  */
543
561
  get outputPoints() {
544
- return this.inputPorts.map((port) => port.point);
562
+ return this.outputPorts.map((port) => port.point);
545
563
  }
546
564
  /**
547
565
  * 根据 key 获取 输入点位置
@@ -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
@@ -1540,6 +1570,12 @@ var WorkflowDocumentOptionsDefault = {
1540
1570
  };
1541
1571
 
1542
1572
  // src/workflow-lines-manager.ts
1573
+ var NODE_HIT_CELL_SIZE = 512;
1574
+ var LINE_HIT_CELL_SIZE = 512;
1575
+ var PORT_NODE_BOUNDS_PADDING = 96;
1576
+ var NODE_INDEX_MIN_SIZE = 128;
1577
+ var LINE_INDEX_MIN_SIZE = 128;
1578
+ var MAX_SPATIAL_CELLS_PER_ITEM = 64;
1543
1579
  var WorkflowLinesManager = class {
1544
1580
  constructor() {
1545
1581
  this.toDispose = new DisposableCollection();
@@ -1563,11 +1599,35 @@ var WorkflowLinesManager = class {
1563
1599
  this.isDrawing = false;
1564
1600
  }
1565
1601
  init(doc) {
1602
+ if (this.document === doc) {
1603
+ return;
1604
+ }
1566
1605
  this.document = doc;
1606
+ this.toDispose.pushAll([
1607
+ this.entityManager.onEntityChange((entityType) => {
1608
+ if (entityType === WorkflowNodeEntity.type) {
1609
+ this.invalidateNodeHitCaches();
1610
+ }
1611
+ if (entityType === WorkflowLineEntity.type || entityType === WorkflowPortEntity.type) {
1612
+ this.invalidateLineHitCache();
1613
+ this.outsidePortNodesCache = void 0;
1614
+ }
1615
+ }),
1616
+ this.entityManager.onEntityDataChange(({ entityDataType }) => {
1617
+ if (entityDataType === FlowNodeTransformData3.type) {
1618
+ this.invalidateSpatialHitCaches();
1619
+ }
1620
+ })
1621
+ ]);
1567
1622
  }
1568
1623
  forceUpdate() {
1569
1624
  this.onForceUpdateEmitter.fire();
1570
1625
  }
1626
+ invalidateSortedNodesCache() {
1627
+ this.sortedNodesCache = void 0;
1628
+ this.sortedNodesCacheVersion = void 0;
1629
+ this.invalidateNodeHitCaches();
1630
+ }
1571
1631
  get lineType() {
1572
1632
  return this._lineType;
1573
1633
  }
@@ -1596,6 +1656,7 @@ var WorkflowLinesManager = class {
1596
1656
  }
1597
1657
  if (newType !== this._lineType) {
1598
1658
  this._lineType = newType;
1659
+ this.lineSpatialCache = void 0;
1599
1660
  this.getAllLines().forEach((line) => {
1600
1661
  line.getData(WorkflowLineRenderData).update();
1601
1662
  });
@@ -1754,17 +1815,26 @@ var WorkflowLinesManager = class {
1754
1815
  */
1755
1816
  getCloseInLineFromMousePos(mousePos, minDistance = LINE_HOVER_DISTANCE) {
1756
1817
  let targetLine, targetLineDist;
1757
- this.getAllLines().forEach((line) => {
1818
+ const lines = this.getLineHitCandidates(mousePos, minDistance);
1819
+ for (let i = 0; i < lines.length; i++) {
1820
+ const line = lines[i];
1821
+ if (!this.isPointInBounds(mousePos, line.bounds, minDistance)) {
1822
+ continue;
1823
+ }
1758
1824
  const dist = line.getHoverDist(mousePos);
1759
- if (dist <= minDistance && (!targetLineDist || targetLineDist >= dist)) {
1825
+ if (dist <= minDistance && (targetLineDist === void 0 || targetLineDist >= dist)) {
1760
1826
  targetLineDist = dist;
1761
1827
  targetLine = line;
1762
1828
  }
1763
- });
1829
+ }
1764
1830
  return targetLine;
1765
1831
  }
1766
1832
  dispose() {
1767
1833
  this.portLineMap.clear();
1834
+ this.sortedNodeSpatialCache = void 0;
1835
+ this.hoverNodeSpatialCache = void 0;
1836
+ this.lineSpatialCache = void 0;
1837
+ this.outsidePortNodesCache = void 0;
1768
1838
  this.toDispose.dispose();
1769
1839
  }
1770
1840
  get disposed() {
@@ -1879,39 +1949,45 @@ var WorkflowLinesManager = class {
1879
1949
  * @param pos
1880
1950
  */
1881
1951
  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;
1952
+ return this.getHoveredPortFromSortedNodes(pos, this.getSortedNodes(), portType);
1953
+ }
1954
+ getPortsFromMousePos(pos) {
1955
+ return this.getHoveredPortsFromSortedNodes(pos, this.getSortedNodes(), void 0, {
1956
+ collectBoth: true
1957
+ });
1958
+ }
1959
+ getNodeAndPortFromMousePos(pos, portType) {
1960
+ const sortedNodes = this.getSortedNodes();
1961
+ const nodeHitInfo = this.getNodeHitInfoFromSortedNodes(
1962
+ pos,
1963
+ sortedNodes,
1964
+ this.selectService.selection
1965
+ );
1966
+ const ports = this.getHoveredPortsFromSortedNodes(pos, sortedNodes, portType, {
1967
+ topCoverNode: nodeHitInfo.topCoverNode
1968
+ });
1969
+ return {
1970
+ node: nodeHitInfo.topNode,
1971
+ port: ports.port
1972
+ };
1973
+ }
1974
+ getHoverNodeFromMousePos(pos) {
1975
+ const nodes = this.getHoverNodes();
1976
+ const candidates = this.getHoverNodeHitCandidates(pos, nodes);
1977
+ for (let i = 0; i < candidates.length; i++) {
1978
+ const node = candidates[i];
1979
+ const { bounds } = node.getData(FlowNodeTransformData3);
1980
+ if (this.isPointInBounds(pos, bounds)) {
1981
+ return node;
1895
1982
  }
1896
1983
  }
1897
- return targetPort;
1898
1984
  }
1899
1985
  /**
1900
1986
  * 根据鼠标位置找到 node
1901
1987
  * @param pos - 鼠标位置
1902
1988
  */
1903
1989
  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);
1990
+ return this.getTopNodeFromSortedNodes(pos, this.getSortedNodes(), this.selectService.selection);
1915
1991
  }
1916
1992
  registerContribution(factory) {
1917
1993
  this.contributionFactories.push(factory);
@@ -1921,19 +1997,352 @@ var WorkflowLinesManager = class {
1921
1997
  line.addData(WorkflowLineRenderData);
1922
1998
  }
1923
1999
  getSortedNodes() {
1924
- return this.document.getAllNodes().sort((a, b) => this.getNodeIndex(a) - this.getNodeIndex(b));
2000
+ const nodeVersion = this.entityManager.getEntityVersion(WorkflowNodeEntity);
2001
+ if (this.sortedNodesCache && this.sortedNodesCacheVersion === nodeVersion) {
2002
+ return this.sortedNodesCache;
2003
+ }
2004
+ this.sortedNodesCache = [...this.document.getAllNodes()].sort(
2005
+ (a, b) => this.getNodeIndex(a) - this.getNodeIndex(b)
2006
+ );
2007
+ this.sortedNodesCacheVersion = nodeVersion;
2008
+ this.sortedNodeSpatialCache = void 0;
2009
+ this.outsidePortNodesCache = void 0;
2010
+ return this.sortedNodesCache;
2011
+ }
2012
+ invalidateNodeHitCaches() {
2013
+ this.sortedNodeSpatialCache = void 0;
2014
+ this.hoverNodeSpatialCache = void 0;
2015
+ this.outsidePortNodesCache = void 0;
2016
+ }
2017
+ invalidateLineHitCache() {
2018
+ this.lineSpatialCache = void 0;
2019
+ }
2020
+ invalidateSpatialHitCaches() {
2021
+ this.invalidateNodeHitCaches();
2022
+ this.invalidateLineHitCache();
2023
+ }
2024
+ getHoveredPortFromSortedNodes(pos, sortedNodes, portType) {
2025
+ return this.getHoveredPortsFromSortedNodes(pos, sortedNodes, portType).port;
2026
+ }
2027
+ getHoveredPortsFromSortedNodes(pos, sortedNodes, portType, options = {}) {
2028
+ let inputPort;
2029
+ let outputPort;
2030
+ let anyPort;
2031
+ const { collectBoth = false } = options;
2032
+ const needInput = !portType || portType === "input";
2033
+ const needOutput = !portType || portType === "output";
2034
+ const needAny = !portType && !collectBoth;
2035
+ const candidates = this.getSortedNodeHitCandidates(pos, sortedNodes, PORT_NODE_BOUNDS_PADDING);
2036
+ const checkedNodes = candidates.length === sortedNodes.length ? void 0 : new Set(candidates);
2037
+ for (let i = candidates.length - 1; i >= 0; i--) {
2038
+ const node = candidates[i];
2039
+ if (!this.isPointNearNodePorts(pos, node)) {
2040
+ continue;
2041
+ }
2042
+ const result = this.collectHoveredPorts(node.ports.allPorts, pos, {
2043
+ needInput,
2044
+ needOutput,
2045
+ needAny,
2046
+ inputPort,
2047
+ outputPort,
2048
+ anyPort
2049
+ });
2050
+ inputPort = result.inputPort;
2051
+ outputPort = result.outputPort;
2052
+ anyPort = result.anyPort;
2053
+ if (portType === "input" && inputPort || portType === "output" && outputPort || !portType && (collectBoth ? inputPort && outputPort : anyPort)) {
2054
+ break;
2055
+ }
2056
+ }
2057
+ if (checkedNodes && !(portType === "input" && inputPort || portType === "output" && outputPort || !portType && (collectBoth ? inputPort && outputPort : anyPort))) {
2058
+ const outsidePortNodes = this.getOutsidePortNodes(sortedNodes);
2059
+ for (let i = outsidePortNodes.length - 1; i >= 0; i--) {
2060
+ const node = outsidePortNodes[i];
2061
+ if (checkedNodes.has(node)) {
2062
+ continue;
2063
+ }
2064
+ const result = this.collectHoveredPorts(node.ports.allPorts, pos, {
2065
+ needInput,
2066
+ needOutput,
2067
+ needAny,
2068
+ inputPort,
2069
+ outputPort,
2070
+ anyPort
2071
+ });
2072
+ inputPort = result.inputPort;
2073
+ outputPort = result.outputPort;
2074
+ anyPort = result.anyPort;
2075
+ if (portType === "input" && inputPort || portType === "output" && outputPort || !portType && (collectBoth ? inputPort && outputPort : anyPort)) {
2076
+ break;
2077
+ }
2078
+ }
2079
+ }
2080
+ const needCoverCheck = inputPort || outputPort || anyPort;
2081
+ const topCoverNode = needCoverCheck ? options.topCoverNode || this.getNodeHitInfoFromSortedNodes(pos, sortedNodes).topCoverNode : void 0;
2082
+ inputPort = this.filterCoveredPort(inputPort, topCoverNode);
2083
+ outputPort = this.filterCoveredPort(outputPort, topCoverNode);
2084
+ anyPort = this.filterCoveredPort(anyPort, topCoverNode);
2085
+ return {
2086
+ input: inputPort,
2087
+ output: outputPort,
2088
+ port: portType === "input" ? inputPort : portType === "output" ? outputPort : anyPort
2089
+ };
1925
2090
  }
1926
- /** 获取鼠标坐标位置的所有节点(stackIndex 从小到大排序) */
1927
- getContainNodesFromMousePos(pos) {
1928
- const allNodes = this.getSortedNodes();
2091
+ collectHoveredPorts(ports, pos, options) {
2092
+ let { inputPort, outputPort, anyPort } = options;
2093
+ for (let i = 0; i < ports.length; i++) {
2094
+ const port = ports[i];
2095
+ if ((!options.needAny || anyPort) && (!options.needInput || inputPort) && (!options.needOutput || outputPort)) {
2096
+ break;
2097
+ }
2098
+ if (port.isHovered(pos.x, pos.y)) {
2099
+ if (!anyPort && options.needAny) {
2100
+ anyPort = port;
2101
+ }
2102
+ if (!inputPort && options.needInput && port.portType === "input") {
2103
+ inputPort = port;
2104
+ }
2105
+ if (!outputPort && options.needOutput && port.portType === "output") {
2106
+ outputPort = port;
2107
+ }
2108
+ }
2109
+ }
2110
+ return {
2111
+ inputPort,
2112
+ outputPort,
2113
+ anyPort
2114
+ };
2115
+ }
2116
+ filterCoveredPort(port, topCoverNode) {
2117
+ if (port && topCoverNode && topCoverNode !== port.node) {
2118
+ return void 0;
2119
+ }
2120
+ return port;
2121
+ }
2122
+ getTopNodeFromSortedNodes(pos, sortedNodes, selection) {
2123
+ return this.getNodeHitInfoFromSortedNodes(pos, sortedNodes, selection).topNode;
2124
+ }
2125
+ getNodeHitInfoFromSortedNodes(pos, sortedNodes, selection) {
1929
2126
  const zoom = this.entityManager.getEntity(PlaygroundConfigEntity2)?.config?.zoom || 1;
1930
- const containNodes = allNodes.map((node) => {
2127
+ const padding = 4 / zoom;
2128
+ const selectedIDs = selection?.length ? new Set(selection.map((node) => node.id)) : void 0;
2129
+ const candidates = this.getSortedNodeHitCandidates(pos, sortedNodes, padding);
2130
+ let topCoverNode;
2131
+ for (let i = candidates.length - 1; i >= 0; i--) {
2132
+ const node = candidates[i];
1931
2133
  const { bounds } = node.getData(FlowNodeTransformData3);
1932
- if (bounds.clone().pad(4 / zoom).contains(pos.x, pos.y)) {
1933
- return node;
2134
+ if (!this.isPointInBounds(pos, bounds, padding)) {
2135
+ continue;
1934
2136
  }
1935
- }).filter(Boolean);
1936
- return containNodes;
2137
+ if (!topCoverNode) {
2138
+ topCoverNode = node;
2139
+ }
2140
+ if (!selectedIDs) {
2141
+ return {
2142
+ topCoverNode,
2143
+ topNode: node
2144
+ };
2145
+ }
2146
+ if (selectedIDs.has(node.id)) {
2147
+ return {
2148
+ topCoverNode,
2149
+ topNode: node
2150
+ };
2151
+ }
2152
+ }
2153
+ return {
2154
+ topCoverNode,
2155
+ topNode: topCoverNode
2156
+ };
2157
+ }
2158
+ getHoverNodeHitCandidates(pos, nodes) {
2159
+ if (nodes.length < NODE_INDEX_MIN_SIZE) {
2160
+ return nodes;
2161
+ }
2162
+ const nodeVersion = this.entityManager.getEntityVersion(WorkflowNodeEntity);
2163
+ const transformVersion = this.entityManager.getEntityDataVersion(FlowNodeTransformData3);
2164
+ const activatedID = this.selectService.activatedNode?.id;
2165
+ if (!this.hoverNodeSpatialCache || this.hoverNodeSpatialCache.nodeVersion !== nodeVersion || this.hoverNodeSpatialCache.transformVersion !== transformVersion || this.hoverNodeSpatialCache.activatedID !== activatedID || this.hoverNodeSpatialCache.nodes !== nodes) {
2166
+ this.hoverNodeSpatialCache = {
2167
+ nodeVersion,
2168
+ transformVersion,
2169
+ activatedID,
2170
+ nodes,
2171
+ index: this.createSpatialIndex(
2172
+ nodes,
2173
+ (node) => node.getData(FlowNodeTransformData3).bounds,
2174
+ NODE_HIT_CELL_SIZE
2175
+ )
2176
+ };
2177
+ }
2178
+ return this.querySpatialIndex(this.hoverNodeSpatialCache.index, pos);
2179
+ }
2180
+ getSortedNodeHitCandidates(pos, sortedNodes, padding = 0) {
2181
+ if (sortedNodes.length < NODE_INDEX_MIN_SIZE) {
2182
+ return sortedNodes;
2183
+ }
2184
+ const nodeVersion = this.entityManager.getEntityVersion(WorkflowNodeEntity);
2185
+ const transformVersion = this.entityManager.getEntityDataVersion(FlowNodeTransformData3);
2186
+ if (!this.sortedNodeSpatialCache || this.sortedNodeSpatialCache.nodeVersion !== nodeVersion || this.sortedNodeSpatialCache.transformVersion !== transformVersion || this.sortedNodeSpatialCache.nodes !== sortedNodes) {
2187
+ this.sortedNodeSpatialCache = {
2188
+ nodeVersion,
2189
+ transformVersion,
2190
+ nodes: sortedNodes,
2191
+ index: this.createSpatialIndex(
2192
+ sortedNodes,
2193
+ (node) => node.getData(FlowNodeTransformData3).bounds,
2194
+ NODE_HIT_CELL_SIZE
2195
+ )
2196
+ };
2197
+ }
2198
+ return this.querySpatialIndex(this.sortedNodeSpatialCache.index, pos, padding);
2199
+ }
2200
+ getLineHitCandidates(pos, padding) {
2201
+ const lines = this.getAllLines();
2202
+ if (lines.length < LINE_INDEX_MIN_SIZE) {
2203
+ return lines;
2204
+ }
2205
+ const lineVersion = this.entityManager.getEntityVersion(WorkflowLineEntity);
2206
+ const portVersion = this.entityManager.getEntityVersion(WorkflowPortEntity);
2207
+ const transformVersion = this.entityManager.getEntityDataVersion(FlowNodeTransformData3);
2208
+ if (!this.lineSpatialCache || this.lineSpatialCache.lineVersion !== lineVersion || this.lineSpatialCache.portVersion !== portVersion || this.lineSpatialCache.transformVersion !== transformVersion) {
2209
+ this.lineSpatialCache = {
2210
+ lineVersion,
2211
+ portVersion,
2212
+ transformVersion,
2213
+ index: this.createSpatialIndex(lines, (line) => line.bounds, LINE_HIT_CELL_SIZE)
2214
+ };
2215
+ }
2216
+ return this.querySpatialIndex(this.lineSpatialCache.index, pos, padding);
2217
+ }
2218
+ createSpatialIndex(items, getBounds, cellSize) {
2219
+ const index = {
2220
+ cellSize,
2221
+ cells: /* @__PURE__ */ new Map(),
2222
+ overflowItems: [],
2223
+ order: /* @__PURE__ */ new Map()
2224
+ };
2225
+ items.forEach((item, itemIndex) => {
2226
+ index.order.set(item, itemIndex);
2227
+ const bounds = getBounds(item);
2228
+ if (!this.canIndexBounds(bounds)) {
2229
+ index.overflowItems.push(item);
2230
+ return;
2231
+ }
2232
+ const minCellX = this.getSpatialCell(bounds.x, cellSize);
2233
+ const maxCellX = this.getSpatialCell(bounds.right, cellSize);
2234
+ const minCellY = this.getSpatialCell(bounds.y, cellSize);
2235
+ const maxCellY = this.getSpatialCell(bounds.bottom, cellSize);
2236
+ const cellCount = (maxCellX - minCellX + 1) * (maxCellY - minCellY + 1);
2237
+ if (cellCount > MAX_SPATIAL_CELLS_PER_ITEM) {
2238
+ index.overflowItems.push(item);
2239
+ return;
2240
+ }
2241
+ for (let x = minCellX; x <= maxCellX; x++) {
2242
+ for (let y = minCellY; y <= maxCellY; y++) {
2243
+ const key = this.getSpatialCellKey(x, y);
2244
+ let cellItems = index.cells.get(key);
2245
+ if (!cellItems) {
2246
+ cellItems = [];
2247
+ index.cells.set(key, cellItems);
2248
+ }
2249
+ cellItems.push(item);
2250
+ }
2251
+ }
2252
+ });
2253
+ return index;
2254
+ }
2255
+ querySpatialIndex(index, pos, padding = 0) {
2256
+ const minCellX = this.getSpatialCell(pos.x - padding, index.cellSize);
2257
+ const maxCellX = this.getSpatialCell(pos.x + padding, index.cellSize);
2258
+ const minCellY = this.getSpatialCell(pos.y - padding, index.cellSize);
2259
+ const maxCellY = this.getSpatialCell(pos.y + padding, index.cellSize);
2260
+ const candidates = [];
2261
+ const candidateSet = /* @__PURE__ */ new Set();
2262
+ const pushCandidate = (item) => {
2263
+ if (candidateSet.has(item)) {
2264
+ return;
2265
+ }
2266
+ candidateSet.add(item);
2267
+ candidates.push(item);
2268
+ };
2269
+ for (let x = minCellX; x <= maxCellX; x++) {
2270
+ for (let y = minCellY; y <= maxCellY; y++) {
2271
+ const cellItems = index.cells.get(this.getSpatialCellKey(x, y));
2272
+ if (cellItems) {
2273
+ cellItems.forEach(pushCandidate);
2274
+ }
2275
+ }
2276
+ }
2277
+ index.overflowItems.forEach(pushCandidate);
2278
+ return candidates.sort((a, b) => (index.order.get(a) ?? 0) - (index.order.get(b) ?? 0));
2279
+ }
2280
+ getSpatialCell(value, cellSize) {
2281
+ return Math.floor(value / cellSize);
2282
+ }
2283
+ getSpatialCellKey(x, y) {
2284
+ return `${x}:${y}`;
2285
+ }
2286
+ canIndexBounds(bounds) {
2287
+ return Number.isFinite(bounds.x) && Number.isFinite(bounds.y) && Number.isFinite(bounds.width) && Number.isFinite(bounds.height) && bounds.width > 0 && bounds.height > 0;
2288
+ }
2289
+ isPointNearNodePorts(pos, node) {
2290
+ const { bounds } = node.getData(FlowNodeTransformData3);
2291
+ return this.isPointInBounds(pos, bounds, PORT_NODE_BOUNDS_PADDING);
2292
+ }
2293
+ hasPortOutsideNodeBounds(node) {
2294
+ const { bounds } = node.getData(FlowNodeTransformData3);
2295
+ return node.ports.allPorts.some((port) => {
2296
+ if (port.targetElement) {
2297
+ return true;
2298
+ }
2299
+ return !this.isPointInBounds(port.point, bounds, PORT_NODE_BOUNDS_PADDING);
2300
+ });
2301
+ }
2302
+ getOutsidePortNodes(sortedNodes) {
2303
+ const nodeVersion = this.entityManager.getEntityVersion(WorkflowNodeEntity);
2304
+ const portVersion = this.entityManager.getEntityVersion(WorkflowPortEntity);
2305
+ const transformVersion = this.entityManager.getEntityDataVersion(FlowNodeTransformData3);
2306
+ if (this.outsidePortNodesCache && this.outsidePortNodesCache.nodeVersion === nodeVersion && this.outsidePortNodesCache.portVersion === portVersion && this.outsidePortNodesCache.transformVersion === transformVersion && this.outsidePortNodesCache.sortedNodes === sortedNodes) {
2307
+ return this.outsidePortNodesCache.outsideNodes;
2308
+ }
2309
+ const outsideNodes = sortedNodes.filter((node) => this.hasPortOutsideNodeBounds(node));
2310
+ this.outsidePortNodesCache = {
2311
+ nodeVersion,
2312
+ portVersion,
2313
+ transformVersion,
2314
+ sortedNodes,
2315
+ outsideNodes
2316
+ };
2317
+ return outsideNodes;
2318
+ }
2319
+ getHoverNodes() {
2320
+ const nodeVersion = this.entityManager.getEntityVersion(WorkflowNodeEntity);
2321
+ const activatedID = this.selectService.activatedNode?.id;
2322
+ if (this.hoverNodesCache && this.hoverNodesCacheVersion === nodeVersion && this.hoverNodesCacheActivatedID === activatedID) {
2323
+ return this.hoverNodesCache;
2324
+ }
2325
+ const nodes = this.document.getAllNodes().filter(
2326
+ (node) => node.id !== "root" && node.flowNodeType !== FlowNodeBaseType2.ROOT && node.flowNodeType !== FlowNodeBaseType2.GROUP
2327
+ ).reverse();
2328
+ if (activatedID) {
2329
+ const activatedIndex = nodes.findIndex((node) => node.id === activatedID);
2330
+ if (activatedIndex > 0) {
2331
+ const [activatedNode] = nodes.splice(activatedIndex, 1);
2332
+ nodes.unshift(activatedNode);
2333
+ }
2334
+ }
2335
+ this.hoverNodesCache = nodes;
2336
+ this.hoverNodesCacheVersion = nodeVersion;
2337
+ this.hoverNodesCacheActivatedID = activatedID;
2338
+ this.hoverNodeSpatialCache = void 0;
2339
+ return this.hoverNodesCache;
2340
+ }
2341
+ isPointInBounds(pos, bounds, padding = 0) {
2342
+ if (bounds.width + padding * 2 <= 0 || bounds.height + padding * 2 <= 0) {
2343
+ return false;
2344
+ }
2345
+ return pos.x >= bounds.x - padding && pos.x <= bounds.right + padding && pos.y >= bounds.y - padding && pos.y <= bounds.bottom + padding;
1937
2346
  }
1938
2347
  getNodeIndex(node) {
1939
2348
  const nodeRenderData = node.getData(FlowNodeRenderData2);
@@ -1963,7 +2372,7 @@ import { Emitter as Emitter5 } from "@flowgram.ai/utils";
1963
2372
  import { NodeEngineContext } from "@flowgram.ai/form-core";
1964
2373
  import {
1965
2374
  FlowDocument,
1966
- FlowNodeBaseType as FlowNodeBaseType2,
2375
+ FlowNodeBaseType as FlowNodeBaseType3,
1967
2376
  FlowNodeTransformData as FlowNodeTransformData5
1968
2377
  } from "@flowgram.ai/document";
1969
2378
  import {
@@ -2277,7 +2686,7 @@ var WorkflowDocument = class extends FlowDocument {
2277
2686
  toJSON: () => this.toNodeJSON(node)
2278
2687
  });
2279
2688
  node.onDispose(() => {
2280
- if (!node.parent || node.parent.flowNodeType === FlowNodeBaseType2.ROOT) {
2689
+ if (!node.parent || node.parent.flowNodeType === FlowNodeBaseType3.ROOT) {
2281
2690
  return;
2282
2691
  }
2283
2692
  const parentTransform = node.parent.getData(FlowNodeTransformData5);
@@ -2448,13 +2857,13 @@ var WorkflowDocument = class extends FlowDocument {
2448
2857
  );
2449
2858
  }
2450
2859
  getAllNodes() {
2451
- return this.entityManager.getEntities(WorkflowNodeEntity).filter((n) => n.id !== FlowNodeBaseType2.ROOT);
2860
+ return this.entityManager.getEntities(WorkflowNodeEntity).filter((n) => n.id !== FlowNodeBaseType3.ROOT);
2452
2861
  }
2453
2862
  getAllEdges() {
2454
2863
  return this.entityManager.getEntities(WorkflowLineEntity);
2455
2864
  }
2456
2865
  getAllPorts() {
2457
- return this.entityManager.getEntities(WorkflowPortEntity).filter((p) => p.node.id !== FlowNodeBaseType2.ROOT);
2866
+ return this.entityManager.getEntities(WorkflowPortEntity).filter((p) => p.node.id !== FlowNodeBaseType3.ROOT);
2458
2867
  }
2459
2868
  /**
2460
2869
  * 获取画布中的非游离节点
@@ -2666,7 +3075,7 @@ var WorkflowDocument = class extends FlowDocument {
2666
3075
  return subCanvas;
2667
3076
  }
2668
3077
  getNodeChildren(node) {
2669
- if (!node || node.flowNodeType === FlowNodeBaseType2.GROUP) return [];
3078
+ if (!node || node.flowNodeType === FlowNodeBaseType3.GROUP) return [];
2670
3079
  const subCanvas = this.getNodeSubCanvas(node);
2671
3080
  const realChildren = subCanvas ? subCanvas.canvasNode.blocks : node.blocks;
2672
3081
  const childrenWithoutSubCanvas = realChildren.filter((child) => {
@@ -2674,7 +3083,7 @@ var WorkflowDocument = class extends FlowDocument {
2674
3083
  return !childMeta.subCanvas?.(node)?.isCanvas;
2675
3084
  }).filter(Boolean);
2676
3085
  const children = childrenWithoutSubCanvas.map((child) => {
2677
- if (child.flowNodeType === FlowNodeBaseType2.GROUP) {
3086
+ if (child.flowNodeType === FlowNodeBaseType3.GROUP) {
2678
3087
  return [child, ...child.blocks];
2679
3088
  }
2680
3089
  return child;
@@ -2993,7 +3402,7 @@ var WorkflowDragService = class {
2993
3402
  if (!mousePos) {
2994
3403
  return { x: 0, y: 0 };
2995
3404
  }
2996
- if (!subNodeType || !containerNode || containerNode.flowNodeType === FlowNodeBaseType3.ROOT) {
3405
+ if (!subNodeType || !containerNode || containerNode.flowNodeType === FlowNodeBaseType4.ROOT) {
2997
3406
  return mousePos;
2998
3407
  }
2999
3408
  const isParentEmpty = !containerNode.children || containerNode.children.length === 0;
@@ -3206,7 +3615,7 @@ var WorkflowDragService = class {
3206
3615
  return;
3207
3616
  }
3208
3617
  const sourceContainer = nodes[0]?.parent;
3209
- if (!sourceContainer || sourceContainer.flowNodeType === FlowNodeBaseType3.ROOT) {
3618
+ if (!sourceContainer || sourceContainer.flowNodeType === FlowNodeBaseType4.ROOT) {
3210
3619
  return;
3211
3620
  }
3212
3621
  const valid = nodes.every((node) => node?.parent === sourceContainer);
@@ -3351,15 +3760,15 @@ var WorkflowDragService = class {
3351
3760
  }
3352
3761
  updateDrawingLine(isDrawingTo, line, dragPos, originLine) {
3353
3762
  let hasError = false;
3354
- const mouseNode = this.linesManager.getNodeFromMousePos(dragPos);
3355
3763
  let toNode;
3356
3764
  let toPort;
3357
3765
  let fromPort;
3358
3766
  let fromNode;
3359
3767
  if (isDrawingTo) {
3768
+ const mouseHitInfo = this.linesManager.getNodeAndPortFromMousePos(dragPos, "input");
3360
3769
  fromPort = line.fromPort;
3361
- toNode = mouseNode;
3362
- toPort = this.linesManager.getPortFromMousePos(dragPos, "input");
3770
+ toNode = mouseHitInfo.node;
3771
+ toPort = mouseHitInfo.port;
3363
3772
  if (toNode && this.canBuildContainerLine(toNode, dragPos)) {
3364
3773
  toPort = this.getNearestPort(toNode, dragPos, "input");
3365
3774
  hasError = this.checkDraggingPort(isDrawingTo, line, toNode, toPort, originLine).hasError;
@@ -3386,9 +3795,10 @@ var WorkflowDragService = class {
3386
3795
  };
3387
3796
  }
3388
3797
  } else {
3798
+ const mouseHitInfo = this.linesManager.getNodeAndPortFromMousePos(dragPos, "output");
3389
3799
  toPort = line.toPort;
3390
- fromNode = mouseNode;
3391
- fromPort = this.linesManager.getPortFromMousePos(dragPos, "output");
3800
+ fromNode = mouseHitInfo.node;
3801
+ fromPort = mouseHitInfo.port;
3392
3802
  if (fromNode && this.canBuildContainerLine(fromNode, dragPos)) {
3393
3803
  fromPort = this.getNearestPort(fromNode, dragPos, "output");
3394
3804
  hasError = this.checkDraggingPort(