@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/index.js CHANGED
@@ -365,7 +365,14 @@ var WorkflowPortEntity = class extends import_core4.Entity {
365
365
  );
366
366
  }
367
367
  isHovered(x, y) {
368
- return this.bounds.contains(x, y);
368
+ const { point } = this;
369
+ const size = this._size || { width: PORT_SIZE, height: PORT_SIZE };
370
+ const halfWidth = size.width / 2;
371
+ const halfHeight = size.height / 2;
372
+ if (size.width <= 0 || size.height <= 0) {
373
+ return false;
374
+ }
375
+ return x >= point.x - halfWidth && x <= point.x + halfWidth && y >= point.y - halfHeight && y <= point.y + halfHeight;
369
376
  }
370
377
  /**
371
378
  * 相对节点左上角的位置
@@ -466,6 +473,7 @@ var WorkflowNodePortsData = class extends import_core5.EntityData {
466
473
  this._staticPorts = [];
467
474
  /** 存储 port 实体的 id,用于判断 port 是否存在 */
468
475
  this._portIDSet = /* @__PURE__ */ new Set();
476
+ this._portDisposeIDSet = /* @__PURE__ */ new Set();
469
477
  this.entity = entity;
470
478
  const meta = entity.getNodeMeta();
471
479
  const defaultPorts = meta.useDynamicPort ? [] : [{ type: "input" }, { type: "output" }];
@@ -552,6 +560,7 @@ var WorkflowNodePortsData = class extends import_core5.EntityData {
552
560
  });
553
561
  ports.forEach((port) => this.updatePortEntity(port));
554
562
  this._prePorts = ports;
563
+ this.clearPortsCache();
555
564
  this.fireChange();
556
565
  }
557
566
  this.allPorts.forEach((port) => {
@@ -564,19 +573,28 @@ var WorkflowNodePortsData = class extends import_core5.EntityData {
564
573
  * 获取所有 port entities
565
574
  */
566
575
  get allPorts() {
567
- return Array.from(this._portIDSet).map((portId) => this.getPortEntity(portId)).filter(Boolean);
576
+ if (!this._allPortsCache) {
577
+ this._allPortsCache = Array.from(this._portIDSet).map((portId) => this.getPortEntity(portId)).filter(Boolean);
578
+ }
579
+ return this._allPortsCache;
568
580
  }
569
581
  /**
570
582
  * 获取输入点位
571
583
  */
572
584
  get inputPorts() {
573
- return this.allPorts.filter((port) => port.portType === "input");
585
+ if (!this._inputPortsCache) {
586
+ this._inputPortsCache = this.allPorts.filter((port) => port.portType === "input");
587
+ }
588
+ return this._inputPortsCache;
574
589
  }
575
590
  /**
576
591
  * 获取输出点位
577
592
  */
578
593
  get outputPorts() {
579
- return this.allPorts.filter((port) => port.portType === "output");
594
+ if (!this._outputPortsCache) {
595
+ this._outputPortsCache = this.allPorts.filter((port) => port.portType === "output");
596
+ }
597
+ return this._outputPortsCache;
580
598
  }
581
599
  /**
582
600
  * 获取输入点位置
@@ -588,7 +606,7 @@ var WorkflowNodePortsData = class extends import_core5.EntityData {
588
606
  * 获取输出点位置
589
607
  */
590
608
  get outputPoints() {
591
- return this.inputPorts.map((port) => port.point);
609
+ return this.outputPorts.map((port) => port.point);
592
610
  }
593
611
  /**
594
612
  * 根据 key 获取 输入点位置
@@ -617,6 +635,11 @@ var WorkflowNodePortsData = class extends import_core5.EntityData {
617
635
  getPortId(portType, portKey = "") {
618
636
  return getPortEntityId(this.entity, portType, portKey);
619
637
  }
638
+ clearPortsCache() {
639
+ this._allPortsCache = void 0;
640
+ this._inputPortsCache = void 0;
641
+ this._outputPortsCache = void 0;
642
+ }
620
643
  /**
621
644
  * 创建 port 实体
622
645
  */
@@ -630,10 +653,18 @@ var WorkflowNodePortsData = class extends import_core5.EntityData {
630
653
  ...portInfo
631
654
  });
632
655
  }
633
- portEntity.onDispose(() => {
634
- this._portIDSet.delete(id);
635
- });
636
- this._portIDSet.add(id);
656
+ if (!this._portDisposeIDSet.has(id)) {
657
+ this._portDisposeIDSet.add(id);
658
+ portEntity.onDispose(() => {
659
+ this._portIDSet.delete(id);
660
+ this._portDisposeIDSet.delete(id);
661
+ this.clearPortsCache();
662
+ });
663
+ }
664
+ if (!this._portIDSet.has(id)) {
665
+ this._portIDSet.add(id);
666
+ this.clearPortsCache();
667
+ }
637
668
  return portEntity;
638
669
  }
639
670
  /**
@@ -1491,7 +1522,6 @@ var import_document10 = require("@flowgram.ai/document");
1491
1522
  var import_core15 = require("@flowgram.ai/core");
1492
1523
 
1493
1524
  // src/workflow-lines-manager.ts
1494
- var import_lodash_es3 = require("lodash-es");
1495
1525
  var import_inversify3 = require("inversify");
1496
1526
  var import_utils12 = require("@flowgram.ai/utils");
1497
1527
  var import_document6 = require("@flowgram.ai/document");
@@ -1612,6 +1642,12 @@ var WorkflowDocumentOptionsDefault = {
1612
1642
  };
1613
1643
 
1614
1644
  // src/workflow-lines-manager.ts
1645
+ var NODE_HIT_CELL_SIZE = 512;
1646
+ var LINE_HIT_CELL_SIZE = 512;
1647
+ var PORT_NODE_BOUNDS_PADDING = 96;
1648
+ var NODE_INDEX_MIN_SIZE = 128;
1649
+ var LINE_INDEX_MIN_SIZE = 128;
1650
+ var MAX_SPATIAL_CELLS_PER_ITEM = 64;
1615
1651
  var WorkflowLinesManager = class {
1616
1652
  constructor() {
1617
1653
  this.toDispose = new import_utils12.DisposableCollection();
@@ -1635,11 +1671,35 @@ var WorkflowLinesManager = class {
1635
1671
  this.isDrawing = false;
1636
1672
  }
1637
1673
  init(doc) {
1674
+ if (this.document === doc) {
1675
+ return;
1676
+ }
1638
1677
  this.document = doc;
1678
+ this.toDispose.pushAll([
1679
+ this.entityManager.onEntityChange((entityType) => {
1680
+ if (entityType === WorkflowNodeEntity.type) {
1681
+ this.invalidateNodeHitCaches();
1682
+ }
1683
+ if (entityType === WorkflowLineEntity.type || entityType === WorkflowPortEntity.type) {
1684
+ this.invalidateLineHitCache();
1685
+ this.outsidePortNodesCache = void 0;
1686
+ }
1687
+ }),
1688
+ this.entityManager.onEntityDataChange(({ entityDataType }) => {
1689
+ if (entityDataType === import_document6.FlowNodeTransformData.type) {
1690
+ this.invalidateSpatialHitCaches();
1691
+ }
1692
+ })
1693
+ ]);
1639
1694
  }
1640
1695
  forceUpdate() {
1641
1696
  this.onForceUpdateEmitter.fire();
1642
1697
  }
1698
+ invalidateSortedNodesCache() {
1699
+ this.sortedNodesCache = void 0;
1700
+ this.sortedNodesCacheVersion = void 0;
1701
+ this.invalidateNodeHitCaches();
1702
+ }
1643
1703
  get lineType() {
1644
1704
  return this._lineType;
1645
1705
  }
@@ -1668,6 +1728,7 @@ var WorkflowLinesManager = class {
1668
1728
  }
1669
1729
  if (newType !== this._lineType) {
1670
1730
  this._lineType = newType;
1731
+ this.lineSpatialCache = void 0;
1671
1732
  this.getAllLines().forEach((line) => {
1672
1733
  line.getData(WorkflowLineRenderData).update();
1673
1734
  });
@@ -1826,17 +1887,26 @@ var WorkflowLinesManager = class {
1826
1887
  */
1827
1888
  getCloseInLineFromMousePos(mousePos, minDistance = LINE_HOVER_DISTANCE) {
1828
1889
  let targetLine, targetLineDist;
1829
- this.getAllLines().forEach((line) => {
1890
+ const lines = this.getLineHitCandidates(mousePos, minDistance);
1891
+ for (let i = 0; i < lines.length; i++) {
1892
+ const line = lines[i];
1893
+ if (!this.isPointInBounds(mousePos, line.bounds, minDistance)) {
1894
+ continue;
1895
+ }
1830
1896
  const dist = line.getHoverDist(mousePos);
1831
- if (dist <= minDistance && (!targetLineDist || targetLineDist >= dist)) {
1897
+ if (dist <= minDistance && (targetLineDist === void 0 || targetLineDist >= dist)) {
1832
1898
  targetLineDist = dist;
1833
1899
  targetLine = line;
1834
1900
  }
1835
- });
1901
+ }
1836
1902
  return targetLine;
1837
1903
  }
1838
1904
  dispose() {
1839
1905
  this.portLineMap.clear();
1906
+ this.sortedNodeSpatialCache = void 0;
1907
+ this.hoverNodeSpatialCache = void 0;
1908
+ this.lineSpatialCache = void 0;
1909
+ this.outsidePortNodesCache = void 0;
1840
1910
  this.toDispose.dispose();
1841
1911
  }
1842
1912
  get disposed() {
@@ -1951,39 +2021,45 @@ var WorkflowLinesManager = class {
1951
2021
  * @param pos
1952
2022
  */
1953
2023
  getPortFromMousePos(pos, portType) {
1954
- const allNodes = this.getSortedNodes().reverse();
1955
- const allPorts = allNodes.map((node) => {
1956
- if (!portType) {
1957
- return node.ports.allPorts;
1958
- }
1959
- return portType === "input" ? node.ports.inputPorts : node.ports.outputPorts;
1960
- }).flat();
1961
- const targetPort = allPorts.find((port) => port.isHovered(pos.x, pos.y));
1962
- if (targetPort) {
1963
- const containNodes = this.getContainNodesFromMousePos(pos);
1964
- const targetNode = (0, import_lodash_es3.last)(containNodes);
1965
- if (targetNode && targetNode !== targetPort.node) {
1966
- return;
2024
+ return this.getHoveredPortFromSortedNodes(pos, this.getSortedNodes(), portType);
2025
+ }
2026
+ getPortsFromMousePos(pos) {
2027
+ return this.getHoveredPortsFromSortedNodes(pos, this.getSortedNodes(), void 0, {
2028
+ collectBoth: true
2029
+ });
2030
+ }
2031
+ getNodeAndPortFromMousePos(pos, portType) {
2032
+ const sortedNodes = this.getSortedNodes();
2033
+ const nodeHitInfo = this.getNodeHitInfoFromSortedNodes(
2034
+ pos,
2035
+ sortedNodes,
2036
+ this.selectService.selection
2037
+ );
2038
+ const ports = this.getHoveredPortsFromSortedNodes(pos, sortedNodes, portType, {
2039
+ topCoverNode: nodeHitInfo.topCoverNode
2040
+ });
2041
+ return {
2042
+ node: nodeHitInfo.topNode,
2043
+ port: ports.port
2044
+ };
2045
+ }
2046
+ getHoverNodeFromMousePos(pos) {
2047
+ const nodes = this.getHoverNodes();
2048
+ const candidates = this.getHoverNodeHitCandidates(pos, nodes);
2049
+ for (let i = 0; i < candidates.length; i++) {
2050
+ const node = candidates[i];
2051
+ const { bounds } = node.getData(import_document6.FlowNodeTransformData);
2052
+ if (this.isPointInBounds(pos, bounds)) {
2053
+ return node;
1967
2054
  }
1968
2055
  }
1969
- return targetPort;
1970
2056
  }
1971
2057
  /**
1972
2058
  * 根据鼠标位置找到 node
1973
2059
  * @param pos - 鼠标位置
1974
2060
  */
1975
2061
  getNodeFromMousePos(pos) {
1976
- const { selection } = this.selectService;
1977
- const containNodes = this.getContainNodesFromMousePos(pos);
1978
- if (selection?.length) {
1979
- const filteredNodes = containNodes.filter(
1980
- (node) => selection.some((_node) => node.id === _node.id)
1981
- );
1982
- if (filteredNodes?.length) {
1983
- return (0, import_lodash_es3.last)(filteredNodes);
1984
- }
1985
- }
1986
- return (0, import_lodash_es3.last)(containNodes);
2062
+ return this.getTopNodeFromSortedNodes(pos, this.getSortedNodes(), this.selectService.selection);
1987
2063
  }
1988
2064
  registerContribution(factory) {
1989
2065
  this.contributionFactories.push(factory);
@@ -1993,19 +2069,352 @@ var WorkflowLinesManager = class {
1993
2069
  line.addData(WorkflowLineRenderData);
1994
2070
  }
1995
2071
  getSortedNodes() {
1996
- return this.document.getAllNodes().sort((a, b) => this.getNodeIndex(a) - this.getNodeIndex(b));
2072
+ const nodeVersion = this.entityManager.getEntityVersion(WorkflowNodeEntity);
2073
+ if (this.sortedNodesCache && this.sortedNodesCacheVersion === nodeVersion) {
2074
+ return this.sortedNodesCache;
2075
+ }
2076
+ this.sortedNodesCache = [...this.document.getAllNodes()].sort(
2077
+ (a, b) => this.getNodeIndex(a) - this.getNodeIndex(b)
2078
+ );
2079
+ this.sortedNodesCacheVersion = nodeVersion;
2080
+ this.sortedNodeSpatialCache = void 0;
2081
+ this.outsidePortNodesCache = void 0;
2082
+ return this.sortedNodesCache;
2083
+ }
2084
+ invalidateNodeHitCaches() {
2085
+ this.sortedNodeSpatialCache = void 0;
2086
+ this.hoverNodeSpatialCache = void 0;
2087
+ this.outsidePortNodesCache = void 0;
2088
+ }
2089
+ invalidateLineHitCache() {
2090
+ this.lineSpatialCache = void 0;
2091
+ }
2092
+ invalidateSpatialHitCaches() {
2093
+ this.invalidateNodeHitCaches();
2094
+ this.invalidateLineHitCache();
2095
+ }
2096
+ getHoveredPortFromSortedNodes(pos, sortedNodes, portType) {
2097
+ return this.getHoveredPortsFromSortedNodes(pos, sortedNodes, portType).port;
2098
+ }
2099
+ getHoveredPortsFromSortedNodes(pos, sortedNodes, portType, options = {}) {
2100
+ let inputPort;
2101
+ let outputPort;
2102
+ let anyPort;
2103
+ const { collectBoth = false } = options;
2104
+ const needInput = !portType || portType === "input";
2105
+ const needOutput = !portType || portType === "output";
2106
+ const needAny = !portType && !collectBoth;
2107
+ const candidates = this.getSortedNodeHitCandidates(pos, sortedNodes, PORT_NODE_BOUNDS_PADDING);
2108
+ const checkedNodes = candidates.length === sortedNodes.length ? void 0 : new Set(candidates);
2109
+ for (let i = candidates.length - 1; i >= 0; i--) {
2110
+ const node = candidates[i];
2111
+ if (!this.isPointNearNodePorts(pos, node)) {
2112
+ continue;
2113
+ }
2114
+ const result = this.collectHoveredPorts(node.ports.allPorts, pos, {
2115
+ needInput,
2116
+ needOutput,
2117
+ needAny,
2118
+ inputPort,
2119
+ outputPort,
2120
+ anyPort
2121
+ });
2122
+ inputPort = result.inputPort;
2123
+ outputPort = result.outputPort;
2124
+ anyPort = result.anyPort;
2125
+ if (portType === "input" && inputPort || portType === "output" && outputPort || !portType && (collectBoth ? inputPort && outputPort : anyPort)) {
2126
+ break;
2127
+ }
2128
+ }
2129
+ if (checkedNodes && !(portType === "input" && inputPort || portType === "output" && outputPort || !portType && (collectBoth ? inputPort && outputPort : anyPort))) {
2130
+ const outsidePortNodes = this.getOutsidePortNodes(sortedNodes);
2131
+ for (let i = outsidePortNodes.length - 1; i >= 0; i--) {
2132
+ const node = outsidePortNodes[i];
2133
+ if (checkedNodes.has(node)) {
2134
+ continue;
2135
+ }
2136
+ const result = this.collectHoveredPorts(node.ports.allPorts, pos, {
2137
+ needInput,
2138
+ needOutput,
2139
+ needAny,
2140
+ inputPort,
2141
+ outputPort,
2142
+ anyPort
2143
+ });
2144
+ inputPort = result.inputPort;
2145
+ outputPort = result.outputPort;
2146
+ anyPort = result.anyPort;
2147
+ if (portType === "input" && inputPort || portType === "output" && outputPort || !portType && (collectBoth ? inputPort && outputPort : anyPort)) {
2148
+ break;
2149
+ }
2150
+ }
2151
+ }
2152
+ const needCoverCheck = inputPort || outputPort || anyPort;
2153
+ const topCoverNode = needCoverCheck ? options.topCoverNode || this.getNodeHitInfoFromSortedNodes(pos, sortedNodes).topCoverNode : void 0;
2154
+ inputPort = this.filterCoveredPort(inputPort, topCoverNode);
2155
+ outputPort = this.filterCoveredPort(outputPort, topCoverNode);
2156
+ anyPort = this.filterCoveredPort(anyPort, topCoverNode);
2157
+ return {
2158
+ input: inputPort,
2159
+ output: outputPort,
2160
+ port: portType === "input" ? inputPort : portType === "output" ? outputPort : anyPort
2161
+ };
1997
2162
  }
1998
- /** 获取鼠标坐标位置的所有节点(stackIndex 从小到大排序) */
1999
- getContainNodesFromMousePos(pos) {
2000
- const allNodes = this.getSortedNodes();
2163
+ collectHoveredPorts(ports, pos, options) {
2164
+ let { inputPort, outputPort, anyPort } = options;
2165
+ for (let i = 0; i < ports.length; i++) {
2166
+ const port = ports[i];
2167
+ if ((!options.needAny || anyPort) && (!options.needInput || inputPort) && (!options.needOutput || outputPort)) {
2168
+ break;
2169
+ }
2170
+ if (port.isHovered(pos.x, pos.y)) {
2171
+ if (!anyPort && options.needAny) {
2172
+ anyPort = port;
2173
+ }
2174
+ if (!inputPort && options.needInput && port.portType === "input") {
2175
+ inputPort = port;
2176
+ }
2177
+ if (!outputPort && options.needOutput && port.portType === "output") {
2178
+ outputPort = port;
2179
+ }
2180
+ }
2181
+ }
2182
+ return {
2183
+ inputPort,
2184
+ outputPort,
2185
+ anyPort
2186
+ };
2187
+ }
2188
+ filterCoveredPort(port, topCoverNode) {
2189
+ if (port && topCoverNode && topCoverNode !== port.node) {
2190
+ return void 0;
2191
+ }
2192
+ return port;
2193
+ }
2194
+ getTopNodeFromSortedNodes(pos, sortedNodes, selection) {
2195
+ return this.getNodeHitInfoFromSortedNodes(pos, sortedNodes, selection).topNode;
2196
+ }
2197
+ getNodeHitInfoFromSortedNodes(pos, sortedNodes, selection) {
2001
2198
  const zoom = this.entityManager.getEntity(import_core12.PlaygroundConfigEntity)?.config?.zoom || 1;
2002
- const containNodes = allNodes.map((node) => {
2199
+ const padding = 4 / zoom;
2200
+ const selectedIDs = selection?.length ? new Set(selection.map((node) => node.id)) : void 0;
2201
+ const candidates = this.getSortedNodeHitCandidates(pos, sortedNodes, padding);
2202
+ let topCoverNode;
2203
+ for (let i = candidates.length - 1; i >= 0; i--) {
2204
+ const node = candidates[i];
2003
2205
  const { bounds } = node.getData(import_document6.FlowNodeTransformData);
2004
- if (bounds.clone().pad(4 / zoom).contains(pos.x, pos.y)) {
2005
- return node;
2206
+ if (!this.isPointInBounds(pos, bounds, padding)) {
2207
+ continue;
2006
2208
  }
2007
- }).filter(Boolean);
2008
- return containNodes;
2209
+ if (!topCoverNode) {
2210
+ topCoverNode = node;
2211
+ }
2212
+ if (!selectedIDs) {
2213
+ return {
2214
+ topCoverNode,
2215
+ topNode: node
2216
+ };
2217
+ }
2218
+ if (selectedIDs.has(node.id)) {
2219
+ return {
2220
+ topCoverNode,
2221
+ topNode: node
2222
+ };
2223
+ }
2224
+ }
2225
+ return {
2226
+ topCoverNode,
2227
+ topNode: topCoverNode
2228
+ };
2229
+ }
2230
+ getHoverNodeHitCandidates(pos, nodes) {
2231
+ if (nodes.length < NODE_INDEX_MIN_SIZE) {
2232
+ return nodes;
2233
+ }
2234
+ const nodeVersion = this.entityManager.getEntityVersion(WorkflowNodeEntity);
2235
+ const transformVersion = this.entityManager.getEntityDataVersion(import_document6.FlowNodeTransformData);
2236
+ const activatedID = this.selectService.activatedNode?.id;
2237
+ if (!this.hoverNodeSpatialCache || this.hoverNodeSpatialCache.nodeVersion !== nodeVersion || this.hoverNodeSpatialCache.transformVersion !== transformVersion || this.hoverNodeSpatialCache.activatedID !== activatedID || this.hoverNodeSpatialCache.nodes !== nodes) {
2238
+ this.hoverNodeSpatialCache = {
2239
+ nodeVersion,
2240
+ transformVersion,
2241
+ activatedID,
2242
+ nodes,
2243
+ index: this.createSpatialIndex(
2244
+ nodes,
2245
+ (node) => node.getData(import_document6.FlowNodeTransformData).bounds,
2246
+ NODE_HIT_CELL_SIZE
2247
+ )
2248
+ };
2249
+ }
2250
+ return this.querySpatialIndex(this.hoverNodeSpatialCache.index, pos);
2251
+ }
2252
+ getSortedNodeHitCandidates(pos, sortedNodes, padding = 0) {
2253
+ if (sortedNodes.length < NODE_INDEX_MIN_SIZE) {
2254
+ return sortedNodes;
2255
+ }
2256
+ const nodeVersion = this.entityManager.getEntityVersion(WorkflowNodeEntity);
2257
+ const transformVersion = this.entityManager.getEntityDataVersion(import_document6.FlowNodeTransformData);
2258
+ if (!this.sortedNodeSpatialCache || this.sortedNodeSpatialCache.nodeVersion !== nodeVersion || this.sortedNodeSpatialCache.transformVersion !== transformVersion || this.sortedNodeSpatialCache.nodes !== sortedNodes) {
2259
+ this.sortedNodeSpatialCache = {
2260
+ nodeVersion,
2261
+ transformVersion,
2262
+ nodes: sortedNodes,
2263
+ index: this.createSpatialIndex(
2264
+ sortedNodes,
2265
+ (node) => node.getData(import_document6.FlowNodeTransformData).bounds,
2266
+ NODE_HIT_CELL_SIZE
2267
+ )
2268
+ };
2269
+ }
2270
+ return this.querySpatialIndex(this.sortedNodeSpatialCache.index, pos, padding);
2271
+ }
2272
+ getLineHitCandidates(pos, padding) {
2273
+ const lines = this.getAllLines();
2274
+ if (lines.length < LINE_INDEX_MIN_SIZE) {
2275
+ return lines;
2276
+ }
2277
+ const lineVersion = this.entityManager.getEntityVersion(WorkflowLineEntity);
2278
+ const portVersion = this.entityManager.getEntityVersion(WorkflowPortEntity);
2279
+ const transformVersion = this.entityManager.getEntityDataVersion(import_document6.FlowNodeTransformData);
2280
+ if (!this.lineSpatialCache || this.lineSpatialCache.lineVersion !== lineVersion || this.lineSpatialCache.portVersion !== portVersion || this.lineSpatialCache.transformVersion !== transformVersion) {
2281
+ this.lineSpatialCache = {
2282
+ lineVersion,
2283
+ portVersion,
2284
+ transformVersion,
2285
+ index: this.createSpatialIndex(lines, (line) => line.bounds, LINE_HIT_CELL_SIZE)
2286
+ };
2287
+ }
2288
+ return this.querySpatialIndex(this.lineSpatialCache.index, pos, padding);
2289
+ }
2290
+ createSpatialIndex(items, getBounds, cellSize) {
2291
+ const index = {
2292
+ cellSize,
2293
+ cells: /* @__PURE__ */ new Map(),
2294
+ overflowItems: [],
2295
+ order: /* @__PURE__ */ new Map()
2296
+ };
2297
+ items.forEach((item, itemIndex) => {
2298
+ index.order.set(item, itemIndex);
2299
+ const bounds = getBounds(item);
2300
+ if (!this.canIndexBounds(bounds)) {
2301
+ index.overflowItems.push(item);
2302
+ return;
2303
+ }
2304
+ const minCellX = this.getSpatialCell(bounds.x, cellSize);
2305
+ const maxCellX = this.getSpatialCell(bounds.right, cellSize);
2306
+ const minCellY = this.getSpatialCell(bounds.y, cellSize);
2307
+ const maxCellY = this.getSpatialCell(bounds.bottom, cellSize);
2308
+ const cellCount = (maxCellX - minCellX + 1) * (maxCellY - minCellY + 1);
2309
+ if (cellCount > MAX_SPATIAL_CELLS_PER_ITEM) {
2310
+ index.overflowItems.push(item);
2311
+ return;
2312
+ }
2313
+ for (let x = minCellX; x <= maxCellX; x++) {
2314
+ for (let y = minCellY; y <= maxCellY; y++) {
2315
+ const key = this.getSpatialCellKey(x, y);
2316
+ let cellItems = index.cells.get(key);
2317
+ if (!cellItems) {
2318
+ cellItems = [];
2319
+ index.cells.set(key, cellItems);
2320
+ }
2321
+ cellItems.push(item);
2322
+ }
2323
+ }
2324
+ });
2325
+ return index;
2326
+ }
2327
+ querySpatialIndex(index, pos, padding = 0) {
2328
+ const minCellX = this.getSpatialCell(pos.x - padding, index.cellSize);
2329
+ const maxCellX = this.getSpatialCell(pos.x + padding, index.cellSize);
2330
+ const minCellY = this.getSpatialCell(pos.y - padding, index.cellSize);
2331
+ const maxCellY = this.getSpatialCell(pos.y + padding, index.cellSize);
2332
+ const candidates = [];
2333
+ const candidateSet = /* @__PURE__ */ new Set();
2334
+ const pushCandidate = (item) => {
2335
+ if (candidateSet.has(item)) {
2336
+ return;
2337
+ }
2338
+ candidateSet.add(item);
2339
+ candidates.push(item);
2340
+ };
2341
+ for (let x = minCellX; x <= maxCellX; x++) {
2342
+ for (let y = minCellY; y <= maxCellY; y++) {
2343
+ const cellItems = index.cells.get(this.getSpatialCellKey(x, y));
2344
+ if (cellItems) {
2345
+ cellItems.forEach(pushCandidate);
2346
+ }
2347
+ }
2348
+ }
2349
+ index.overflowItems.forEach(pushCandidate);
2350
+ return candidates.sort((a, b) => (index.order.get(a) ?? 0) - (index.order.get(b) ?? 0));
2351
+ }
2352
+ getSpatialCell(value, cellSize) {
2353
+ return Math.floor(value / cellSize);
2354
+ }
2355
+ getSpatialCellKey(x, y) {
2356
+ return `${x}:${y}`;
2357
+ }
2358
+ canIndexBounds(bounds) {
2359
+ return Number.isFinite(bounds.x) && Number.isFinite(bounds.y) && Number.isFinite(bounds.width) && Number.isFinite(bounds.height) && bounds.width > 0 && bounds.height > 0;
2360
+ }
2361
+ isPointNearNodePorts(pos, node) {
2362
+ const { bounds } = node.getData(import_document6.FlowNodeTransformData);
2363
+ return this.isPointInBounds(pos, bounds, PORT_NODE_BOUNDS_PADDING);
2364
+ }
2365
+ hasPortOutsideNodeBounds(node) {
2366
+ const { bounds } = node.getData(import_document6.FlowNodeTransformData);
2367
+ return node.ports.allPorts.some((port) => {
2368
+ if (port.targetElement) {
2369
+ return true;
2370
+ }
2371
+ return !this.isPointInBounds(port.point, bounds, PORT_NODE_BOUNDS_PADDING);
2372
+ });
2373
+ }
2374
+ getOutsidePortNodes(sortedNodes) {
2375
+ const nodeVersion = this.entityManager.getEntityVersion(WorkflowNodeEntity);
2376
+ const portVersion = this.entityManager.getEntityVersion(WorkflowPortEntity);
2377
+ const transformVersion = this.entityManager.getEntityDataVersion(import_document6.FlowNodeTransformData);
2378
+ if (this.outsidePortNodesCache && this.outsidePortNodesCache.nodeVersion === nodeVersion && this.outsidePortNodesCache.portVersion === portVersion && this.outsidePortNodesCache.transformVersion === transformVersion && this.outsidePortNodesCache.sortedNodes === sortedNodes) {
2379
+ return this.outsidePortNodesCache.outsideNodes;
2380
+ }
2381
+ const outsideNodes = sortedNodes.filter((node) => this.hasPortOutsideNodeBounds(node));
2382
+ this.outsidePortNodesCache = {
2383
+ nodeVersion,
2384
+ portVersion,
2385
+ transformVersion,
2386
+ sortedNodes,
2387
+ outsideNodes
2388
+ };
2389
+ return outsideNodes;
2390
+ }
2391
+ getHoverNodes() {
2392
+ const nodeVersion = this.entityManager.getEntityVersion(WorkflowNodeEntity);
2393
+ const activatedID = this.selectService.activatedNode?.id;
2394
+ if (this.hoverNodesCache && this.hoverNodesCacheVersion === nodeVersion && this.hoverNodesCacheActivatedID === activatedID) {
2395
+ return this.hoverNodesCache;
2396
+ }
2397
+ const nodes = this.document.getAllNodes().filter(
2398
+ (node) => node.id !== "root" && node.flowNodeType !== import_document6.FlowNodeBaseType.ROOT && node.flowNodeType !== import_document6.FlowNodeBaseType.GROUP
2399
+ ).reverse();
2400
+ if (activatedID) {
2401
+ const activatedIndex = nodes.findIndex((node) => node.id === activatedID);
2402
+ if (activatedIndex > 0) {
2403
+ const [activatedNode] = nodes.splice(activatedIndex, 1);
2404
+ nodes.unshift(activatedNode);
2405
+ }
2406
+ }
2407
+ this.hoverNodesCache = nodes;
2408
+ this.hoverNodesCacheVersion = nodeVersion;
2409
+ this.hoverNodesCacheActivatedID = activatedID;
2410
+ this.hoverNodeSpatialCache = void 0;
2411
+ return this.hoverNodesCache;
2412
+ }
2413
+ isPointInBounds(pos, bounds, padding = 0) {
2414
+ if (bounds.width + padding * 2 <= 0 || bounds.height + padding * 2 <= 0) {
2415
+ return false;
2416
+ }
2417
+ return pos.x >= bounds.x - padding && pos.x <= bounds.right + padding && pos.y >= bounds.y - padding && pos.y <= bounds.bottom + padding;
2009
2418
  }
2010
2419
  getNodeIndex(node) {
2011
2420
  const nodeRenderData = node.getData(import_document6.FlowNodeRenderData);
@@ -3407,15 +3816,15 @@ var WorkflowDragService = class {
3407
3816
  }
3408
3817
  updateDrawingLine(isDrawingTo, line, dragPos, originLine) {
3409
3818
  let hasError = false;
3410
- const mouseNode = this.linesManager.getNodeFromMousePos(dragPos);
3411
3819
  let toNode;
3412
3820
  let toPort;
3413
3821
  let fromPort;
3414
3822
  let fromNode;
3415
3823
  if (isDrawingTo) {
3824
+ const mouseHitInfo = this.linesManager.getNodeAndPortFromMousePos(dragPos, "input");
3416
3825
  fromPort = line.fromPort;
3417
- toNode = mouseNode;
3418
- toPort = this.linesManager.getPortFromMousePos(dragPos, "input");
3826
+ toNode = mouseHitInfo.node;
3827
+ toPort = mouseHitInfo.port;
3419
3828
  if (toNode && this.canBuildContainerLine(toNode, dragPos)) {
3420
3829
  toPort = this.getNearestPort(toNode, dragPos, "input");
3421
3830
  hasError = this.checkDraggingPort(isDrawingTo, line, toNode, toPort, originLine).hasError;
@@ -3442,9 +3851,10 @@ var WorkflowDragService = class {
3442
3851
  };
3443
3852
  }
3444
3853
  } else {
3854
+ const mouseHitInfo = this.linesManager.getNodeAndPortFromMousePos(dragPos, "output");
3445
3855
  toPort = line.toPort;
3446
- fromNode = mouseNode;
3447
- fromPort = this.linesManager.getPortFromMousePos(dragPos, "output");
3856
+ fromNode = mouseHitInfo.node;
3857
+ fromPort = mouseHitInfo.port;
3448
3858
  if (fromNode && this.canBuildContainerLine(fromNode, dragPos)) {
3449
3859
  fromPort = this.getNearestPort(fromNode, dragPos, "output");
3450
3860
  hasError = this.checkDraggingPort(