@flowgram.ai/free-layout-core 0.1.0-alpha.36 → 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
@@ -559,7 +559,7 @@ var WorkflowNodePortsData = class extends EntityData {
559
559
  * 获取输出点位置
560
560
  */
561
561
  get outputPoints() {
562
- return this.inputPorts.map((port) => port.point);
562
+ return this.outputPorts.map((port) => port.point);
563
563
  }
564
564
  /**
565
565
  * 根据 key 获取 输入点位置
@@ -1570,6 +1570,12 @@ var WorkflowDocumentOptionsDefault = {
1570
1570
  };
1571
1571
 
1572
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;
1573
1579
  var WorkflowLinesManager = class {
1574
1580
  constructor() {
1575
1581
  this.toDispose = new DisposableCollection();
@@ -1593,7 +1599,26 @@ var WorkflowLinesManager = class {
1593
1599
  this.isDrawing = false;
1594
1600
  }
1595
1601
  init(doc) {
1602
+ if (this.document === doc) {
1603
+ return;
1604
+ }
1596
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
+ ]);
1597
1622
  }
1598
1623
  forceUpdate() {
1599
1624
  this.onForceUpdateEmitter.fire();
@@ -1601,6 +1626,7 @@ var WorkflowLinesManager = class {
1601
1626
  invalidateSortedNodesCache() {
1602
1627
  this.sortedNodesCache = void 0;
1603
1628
  this.sortedNodesCacheVersion = void 0;
1629
+ this.invalidateNodeHitCaches();
1604
1630
  }
1605
1631
  get lineType() {
1606
1632
  return this._lineType;
@@ -1630,6 +1656,7 @@ var WorkflowLinesManager = class {
1630
1656
  }
1631
1657
  if (newType !== this._lineType) {
1632
1658
  this._lineType = newType;
1659
+ this.lineSpatialCache = void 0;
1633
1660
  this.getAllLines().forEach((line) => {
1634
1661
  line.getData(WorkflowLineRenderData).update();
1635
1662
  });
@@ -1788,7 +1815,7 @@ var WorkflowLinesManager = class {
1788
1815
  */
1789
1816
  getCloseInLineFromMousePos(mousePos, minDistance = LINE_HOVER_DISTANCE) {
1790
1817
  let targetLine, targetLineDist;
1791
- const lines = this.getAllLines();
1818
+ const lines = this.getLineHitCandidates(mousePos, minDistance);
1792
1819
  for (let i = 0; i < lines.length; i++) {
1793
1820
  const line = lines[i];
1794
1821
  if (!this.isPointInBounds(mousePos, line.bounds, minDistance)) {
@@ -1804,6 +1831,10 @@ var WorkflowLinesManager = class {
1804
1831
  }
1805
1832
  dispose() {
1806
1833
  this.portLineMap.clear();
1834
+ this.sortedNodeSpatialCache = void 0;
1835
+ this.hoverNodeSpatialCache = void 0;
1836
+ this.lineSpatialCache = void 0;
1837
+ this.outsidePortNodesCache = void 0;
1807
1838
  this.toDispose.dispose();
1808
1839
  }
1809
1840
  get disposed() {
@@ -1942,8 +1973,9 @@ var WorkflowLinesManager = class {
1942
1973
  }
1943
1974
  getHoverNodeFromMousePos(pos) {
1944
1975
  const nodes = this.getHoverNodes();
1945
- for (let i = 0; i < nodes.length; i++) {
1946
- const node = nodes[i];
1976
+ const candidates = this.getHoverNodeHitCandidates(pos, nodes);
1977
+ for (let i = 0; i < candidates.length; i++) {
1978
+ const node = candidates[i];
1947
1979
  const { bounds } = node.getData(FlowNodeTransformData3);
1948
1980
  if (this.isPointInBounds(pos, bounds)) {
1949
1981
  return node;
@@ -1973,8 +2005,22 @@ var WorkflowLinesManager = class {
1973
2005
  (a, b) => this.getNodeIndex(a) - this.getNodeIndex(b)
1974
2006
  );
1975
2007
  this.sortedNodesCacheVersion = nodeVersion;
2008
+ this.sortedNodeSpatialCache = void 0;
2009
+ this.outsidePortNodesCache = void 0;
1976
2010
  return this.sortedNodesCache;
1977
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
+ }
1978
2024
  getHoveredPortFromSortedNodes(pos, sortedNodes, portType) {
1979
2025
  return this.getHoveredPortsFromSortedNodes(pos, sortedNodes, portType).port;
1980
2026
  }
@@ -1983,21 +2029,54 @@ var WorkflowLinesManager = class {
1983
2029
  let outputPort;
1984
2030
  let anyPort;
1985
2031
  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);
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;
1996
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;
1997
2053
  if (portType === "input" && inputPort || portType === "output" && outputPort || !portType && (collectBoth ? inputPort && outputPort : anyPort)) {
1998
2054
  break;
1999
2055
  }
2000
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
+ }
2001
2080
  const needCoverCheck = inputPort || outputPort || anyPort;
2002
2081
  const topCoverNode = needCoverCheck ? options.topCoverNode || this.getNodeHitInfoFromSortedNodes(pos, sortedNodes).topCoverNode : void 0;
2003
2082
  inputPort = this.filterCoveredPort(inputPort, topCoverNode);
@@ -2009,13 +2088,30 @@ var WorkflowLinesManager = class {
2009
2088
  port: portType === "input" ? inputPort : portType === "output" ? outputPort : anyPort
2010
2089
  };
2011
2090
  }
2012
- findHoveredPort(ports, pos) {
2091
+ collectHoveredPorts(ports, pos, options) {
2092
+ let { inputPort, outputPort, anyPort } = options;
2013
2093
  for (let i = 0; i < ports.length; i++) {
2014
2094
  const port = ports[i];
2095
+ if ((!options.needAny || anyPort) && (!options.needInput || inputPort) && (!options.needOutput || outputPort)) {
2096
+ break;
2097
+ }
2015
2098
  if (port.isHovered(pos.x, pos.y)) {
2016
- return port;
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
+ }
2017
2108
  }
2018
2109
  }
2110
+ return {
2111
+ inputPort,
2112
+ outputPort,
2113
+ anyPort
2114
+ };
2019
2115
  }
2020
2116
  filterCoveredPort(port, topCoverNode) {
2021
2117
  if (port && topCoverNode && topCoverNode !== port.node) {
@@ -2030,9 +2126,10 @@ var WorkflowLinesManager = class {
2030
2126
  const zoom = this.entityManager.getEntity(PlaygroundConfigEntity2)?.config?.zoom || 1;
2031
2127
  const padding = 4 / zoom;
2032
2128
  const selectedIDs = selection?.length ? new Set(selection.map((node) => node.id)) : void 0;
2129
+ const candidates = this.getSortedNodeHitCandidates(pos, sortedNodes, padding);
2033
2130
  let topCoverNode;
2034
- for (let i = sortedNodes.length - 1; i >= 0; i--) {
2035
- const node = sortedNodes[i];
2131
+ for (let i = candidates.length - 1; i >= 0; i--) {
2132
+ const node = candidates[i];
2036
2133
  const { bounds } = node.getData(FlowNodeTransformData3);
2037
2134
  if (!this.isPointInBounds(pos, bounds, padding)) {
2038
2135
  continue;
@@ -2058,6 +2155,167 @@ var WorkflowLinesManager = class {
2058
2155
  topNode: topCoverNode
2059
2156
  };
2060
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
+ }
2061
2319
  getHoverNodes() {
2062
2320
  const nodeVersion = this.entityManager.getEntityVersion(WorkflowNodeEntity);
2063
2321
  const activatedID = this.selectService.activatedNode?.id;
@@ -2077,6 +2335,7 @@ var WorkflowLinesManager = class {
2077
2335
  this.hoverNodesCache = nodes;
2078
2336
  this.hoverNodesCacheVersion = nodeVersion;
2079
2337
  this.hoverNodesCacheActivatedID = activatedID;
2338
+ this.hoverNodeSpatialCache = void 0;
2080
2339
  return this.hoverNodesCache;
2081
2340
  }
2082
2341
  isPointInBounds(pos, bounds, padding = 0) {