@flowgram.ai/free-layout-core 0.1.0-alpha.11 → 0.1.0-alpha.13

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.
Files changed (35) hide show
  1. package/dist/esm/{chunk-CTGO4RKX.js → chunk-E7ZUQ7LV.js} +1 -1
  2. package/dist/esm/chunk-E7ZUQ7LV.js.map +1 -0
  3. package/dist/esm/{chunk-242F2JCI.js → chunk-U2XMPOSL.js} +2 -1
  4. package/dist/esm/{chunk-242F2JCI.js.map → chunk-U2XMPOSL.js.map} +1 -1
  5. package/dist/esm/index.js +294 -99
  6. package/dist/esm/index.js.map +1 -1
  7. package/dist/esm/typings/index.js +2 -2
  8. package/dist/esm/typings/workflow-json.js +1 -1
  9. package/dist/esm/typings/workflow-line.js +1 -1
  10. package/dist/index.d.mts +22 -9
  11. package/dist/index.d.ts +22 -9
  12. package/dist/index.js +313 -116
  13. package/dist/index.js.map +1 -1
  14. package/dist/typings/index.d.mts +1 -1
  15. package/dist/typings/index.d.ts +1 -1
  16. package/dist/typings/index.js +1 -0
  17. package/dist/typings/index.js.map +1 -1
  18. package/dist/typings/workflow-drag.d.mts +1 -1
  19. package/dist/typings/workflow-drag.d.ts +1 -1
  20. package/dist/typings/workflow-json.d.mts +1 -1
  21. package/dist/typings/workflow-json.d.ts +1 -1
  22. package/dist/typings/workflow-json.js +1 -0
  23. package/dist/typings/workflow-json.js.map +1 -1
  24. package/dist/typings/workflow-line.d.mts +1 -1
  25. package/dist/typings/workflow-line.d.ts +1 -1
  26. package/dist/typings/workflow-line.js.map +1 -1
  27. package/dist/typings/workflow-node.d.mts +1 -1
  28. package/dist/typings/workflow-node.d.ts +1 -1
  29. package/dist/typings/workflow-registry.d.mts +2 -1
  30. package/dist/typings/workflow-registry.d.ts +2 -1
  31. package/dist/typings/workflow-registry.js.map +1 -1
  32. package/dist/{workflow-line-entity-SJhFU8K_.d.mts → workflow-line-entity-DSC3qPV1.d.mts} +128 -39
  33. package/dist/{workflow-line-entity-Z4omkx2m.d.ts → workflow-line-entity-I_VrhJ_t.d.ts} +128 -39
  34. package/package.json +9 -9
  35. package/dist/esm/chunk-CTGO4RKX.js.map +0 -1
package/dist/esm/index.js CHANGED
@@ -7,11 +7,11 @@ import "./chunk-TQLT57GW.js";
7
7
  import "./chunk-CGOMTQ3G.js";
8
8
  import {
9
9
  WorkflowContentChangeType
10
- } from "./chunk-242F2JCI.js";
10
+ } from "./chunk-U2XMPOSL.js";
11
11
  import {
12
12
  LineColors,
13
13
  LineType
14
- } from "./chunk-CTGO4RKX.js";
14
+ } from "./chunk-E7ZUQ7LV.js";
15
15
  import "./chunk-DDJTYHXN.js";
16
16
  import {
17
17
  WorkflowOperationBaseService
@@ -72,6 +72,34 @@ import { Rectangle as Rectangle6, SizeSchema } from "@flowgram.ai/utils";
72
72
  import { bindConfigEntity } from "@flowgram.ai/core";
73
73
  import { delay } from "@flowgram.ai/utils";
74
74
 
75
+ // src/utils/build-group-json.ts
76
+ import { FlowNodeBaseType } from "@flowgram.ai/document";
77
+ var buildGroupJSON = (json) => {
78
+ const { nodes, edges } = json;
79
+ const groupJSONs = nodes.filter(
80
+ (nodeJSON) => nodeJSON.type === FlowNodeBaseType.GROUP
81
+ );
82
+ const nodeJSONMap = new Map(nodes.map((n) => [n.id, n]));
83
+ const groupNodeJSONs = groupJSONs.map((groupJSON) => {
84
+ const groupBlocks = (groupJSON.data.blockIDs ?? []).map((blockID) => nodeJSONMap.get(blockID)).filter(Boolean);
85
+ const groupEdges = edges?.filter(
86
+ (edge) => groupBlocks.some((block) => block.id === edge.sourceNodeID || block.id === edge.targetNodeID)
87
+ );
88
+ const groupNodeJSON = {
89
+ ...groupJSON,
90
+ blocks: groupBlocks,
91
+ edges: groupEdges
92
+ };
93
+ return groupNodeJSON;
94
+ });
95
+ const groupBlockSet = new Set(groupJSONs.map((groupJSON) => groupJSON.data.blockIDs).flat());
96
+ const processedNodes = nodes.filter((nodeJSON) => !groupBlockSet.has(nodeJSON.id)).concat(groupNodeJSONs);
97
+ return {
98
+ nodes: processedNodes,
99
+ edges
100
+ };
101
+ };
102
+
75
103
  // src/utils/nanoid.ts
76
104
  import { nanoid as nanoidOrigin } from "nanoid";
77
105
  function nanoid(n) {
@@ -82,8 +110,8 @@ function nanoid(n) {
82
110
  import { compose, composeAsync } from "@flowgram.ai/utils";
83
111
 
84
112
  // src/utils/fit-view.ts
85
- import { TransformData } from "@flowgram.ai/core";
86
113
  import { Rectangle } from "@flowgram.ai/utils";
114
+ import { TransformData } from "@flowgram.ai/core";
87
115
  var fitView = (doc, playgroundConfig, easing = true) => {
88
116
  const bounds = Rectangle.enlarge(
89
117
  doc.getAllNodes().map((node) => node.getData(TransformData).bounds)
@@ -130,7 +158,7 @@ var WorkflowNodeEntity = FlowNodeEntity;
130
158
 
131
159
  // src/entities/workflow-line-entity.ts
132
160
  import { isEqual as isEqual2 } from "lodash-es";
133
- import { domUtils } from "@flowgram.ai/utils";
161
+ import { domUtils, Emitter as Emitter2 } from "@flowgram.ai/utils";
134
162
  import { Entity as Entity2 } from "@flowgram.ai/core";
135
163
 
136
164
  // src/entity-datas/workflow-node-ports-data.ts
@@ -139,7 +167,7 @@ import { FlowNodeRenderData } from "@flowgram.ai/document";
139
167
  import { EntityData, SizeData } from "@flowgram.ai/core";
140
168
 
141
169
  // src/entities/workflow-port-entity.ts
142
- import { Rectangle as Rectangle3, Emitter } from "@flowgram.ai/utils";
170
+ import { Rectangle as Rectangle3, Emitter, Compare } from "@flowgram.ai/utils";
143
171
  import { FlowNodeTransformData } from "@flowgram.ai/document";
144
172
  import {
145
173
  Entity,
@@ -148,17 +176,18 @@ import {
148
176
  } from "@flowgram.ai/core";
149
177
  var PORT_SIZE = 24;
150
178
  var WorkflowPortEntity = class extends Entity {
151
- // relativePosition
152
179
  constructor(opts) {
153
180
  super(opts);
154
181
  this.portID = "";
155
- this._disabled = false;
156
182
  this._hasError = false;
157
183
  this._onErrorChangedEmitter = new Emitter();
158
184
  this.onErrorChanged = this._onErrorChangedEmitter.event;
159
185
  this.portID = opts.portID || "";
160
186
  this.portType = opts.type;
161
- this._disabled = opts.disabled ?? false;
187
+ this._disabled = opts.disabled;
188
+ this._offset = opts.offset;
189
+ this._location = opts.location;
190
+ this._size = opts.size;
162
191
  this.node = opts.node;
163
192
  this.updateTargetElement(opts.targetElement);
164
193
  this.toDispose.push(this.node.getData(TransformData3).onDataChange(() => this.fireChange()));
@@ -167,21 +196,25 @@ var WorkflowPortEntity = class extends Entity {
167
196
  static getPortEntityId(node, portType, portID = "") {
168
197
  return getPortEntityId(node, portType, portID);
169
198
  }
199
+ get position() {
200
+ return this._location;
201
+ }
170
202
  // 获取连线是否为错误态
171
203
  get hasError() {
172
204
  return this._hasError;
173
205
  }
174
206
  // 设置连线的错误态,外部应使用 validate 进行更新
175
207
  set hasError(hasError) {
176
- this._hasError = hasError;
177
- this._onErrorChangedEmitter.fire();
208
+ if (hasError !== this._hasError) {
209
+ this._hasError = hasError;
210
+ this._onErrorChangedEmitter.fire();
211
+ }
178
212
  }
179
213
  validate() {
180
214
  const anyLineHasError = this.allLines.some((line) => {
181
215
  if (line.disposed || line.isHidden) {
182
216
  return false;
183
217
  }
184
- line.validateSelf();
185
218
  return line.hasError;
186
219
  });
187
220
  const isPortHasError = this.node.document.isErrorPort(this);
@@ -190,28 +223,65 @@ var WorkflowPortEntity = class extends Entity {
190
223
  isErrorPort() {
191
224
  return this.node.document.isErrorPort(this, this.hasError);
192
225
  }
226
+ get location() {
227
+ if (this._location) {
228
+ return this._location;
229
+ }
230
+ if (this.portType === "input") {
231
+ return "left";
232
+ }
233
+ return "right";
234
+ }
193
235
  get point() {
194
236
  const { targetElement } = this;
195
237
  const { bounds } = this.node.getData(FlowNodeTransformData);
238
+ const location2 = this.location;
196
239
  if (targetElement) {
197
240
  const pos = domReactToBounds(targetElement.getBoundingClientRect()).center;
198
- return this.entityManager.getEntity(PlaygroundConfigEntity).getPosFromMouseEvent({
241
+ const point2 = this.entityManager.getEntity(PlaygroundConfigEntity).getPosFromMouseEvent({
199
242
  clientX: pos.x,
200
243
  clientY: pos.y
201
244
  });
245
+ return {
246
+ x: point2.x,
247
+ y: point2.y,
248
+ location: location2
249
+ };
202
250
  }
203
- if (this.portType === "input") {
204
- return bounds.leftCenter;
251
+ let point = { x: 0, y: 0 };
252
+ const offset = this._offset || { x: 0, y: 0 };
253
+ switch (location2) {
254
+ case "left":
255
+ point = bounds.leftCenter;
256
+ break;
257
+ case "top":
258
+ point = bounds.topCenter;
259
+ break;
260
+ case "right":
261
+ point = bounds.rightCenter;
262
+ break;
263
+ case "bottom":
264
+ point = bounds.bottomCenter;
265
+ break;
205
266
  }
206
- return bounds.rightCenter;
267
+ return {
268
+ x: point.x + offset.x,
269
+ y: point.y + offset.y,
270
+ location: location2
271
+ };
207
272
  }
208
273
  /**
209
- * 点的区域
274
+ * 端口热区
210
275
  */
211
276
  get bounds() {
212
277
  const { point } = this;
213
- const halfSize = PORT_SIZE / 2;
214
- return new Rectangle3(point.x - halfSize, point.y - halfSize, PORT_SIZE, PORT_SIZE);
278
+ const size = this._size || { width: PORT_SIZE, height: PORT_SIZE };
279
+ return new Rectangle3(
280
+ point.x - size.width / 2,
281
+ point.y - size.height / 2,
282
+ size.width,
283
+ size.height
284
+ );
215
285
  }
216
286
  isHovered(x, y) {
217
287
  return this.bounds.contains(x, y);
@@ -278,6 +348,32 @@ var WorkflowPortEntity = class extends Entity {
278
348
  });
279
349
  return lines;
280
350
  }
351
+ update(data) {
352
+ let changed = false;
353
+ if (data.targetElement !== this.targetElement) {
354
+ this.targetElement = data.targetElement;
355
+ changed = true;
356
+ }
357
+ if (data.location !== this._location) {
358
+ this._location = data.location;
359
+ changed = true;
360
+ }
361
+ if (Compare.isChanged(data.offset, this._offset)) {
362
+ this._offset = data.offset;
363
+ changed = true;
364
+ }
365
+ if (Compare.isChanged(data.size, this._size)) {
366
+ this._size = data.size;
367
+ changed = true;
368
+ }
369
+ if (data.disabled !== this._disabled) {
370
+ this._disabled = data.disabled;
371
+ changed = true;
372
+ }
373
+ if (changed) {
374
+ this.fireChange();
375
+ }
376
+ }
281
377
  dispose() {
282
378
  this.lines.forEach((l) => l.dispose());
283
379
  super.dispose();
@@ -316,17 +412,26 @@ var WorkflowNodePortsData = class extends EntityData {
316
412
  return {};
317
413
  }
318
414
  /**
319
- * 更新静态的 ports 数据
415
+ * Update all ports data, includes static ports and dynamic ports
416
+ * @param ports
320
417
  */
321
- updateStaticPorts(ports) {
418
+ updateAllPorts(ports) {
322
419
  const meta = this.entity.getNodeMeta();
323
- this._staticPorts = ports;
420
+ if (ports) {
421
+ this._staticPorts = ports;
422
+ }
324
423
  if (meta.useDynamicPort) {
325
424
  this.updateDynamicPorts();
326
425
  } else {
327
426
  this.updatePorts(this._staticPorts);
328
427
  }
329
428
  }
429
+ /**
430
+ * @deprecated use `updateAllPorts` instead
431
+ */
432
+ updateStaticPorts(ports) {
433
+ this.updateAllPorts(ports);
434
+ }
330
435
  /**
331
436
  * 动态计算点位,通过 dom 的 data-port-key
332
437
  */
@@ -375,7 +480,6 @@ var WorkflowNodePortsData = class extends EntityData {
375
480
  port.allLines.forEach((line) => {
376
481
  line.validate();
377
482
  });
378
- port.validate();
379
483
  });
380
484
  }
381
485
  /**
@@ -466,9 +570,7 @@ var WorkflowNodePortsData = class extends EntityData {
466
570
  */
467
571
  updatePortEntity(portInfo) {
468
572
  const portEntity = this.getOrCreatePortEntity(portInfo);
469
- if (portInfo.targetElement) {
470
- portEntity.updateTargetElement(portInfo.targetElement);
471
- }
573
+ portEntity.update(portInfo);
472
574
  return portEntity;
473
575
  }
474
576
  };
@@ -506,6 +608,9 @@ var _WorkflowNodeLinesData = class _WorkflowNodeLinesData extends EntityData2 {
506
608
  get outputLines() {
507
609
  return this.data.outputLines;
508
610
  }
611
+ get allLines() {
612
+ return this.data.inputLines.concat(this.data.outputLines);
613
+ }
509
614
  /**
510
615
  * 输入节点
511
616
  */
@@ -606,8 +711,8 @@ var WorkflowLineRenderData = class extends EntityData3 {
606
711
  version: "",
607
712
  contributions: /* @__PURE__ */ new Map(),
608
713
  position: {
609
- from: { x: 0, y: 0 },
610
- to: { x: 0, y: 0 }
714
+ from: { x: 0, y: 0, location: "right" },
715
+ to: { x: 0, y: 0, location: "left" }
611
716
  }
612
717
  };
613
718
  }
@@ -653,16 +758,23 @@ var WorkflowLineRenderData = class extends EntityData3 {
653
758
  */
654
759
  updatePosition() {
655
760
  this.data.position.from = this.entity.from.getData(WorkflowNodePortsData).getOutputPoint(this.entity.info.fromPort);
656
- this.data.position.to = this.entity.info.drawingTo ?? this.entity.to?.getData(WorkflowNodePortsData)?.getInputPoint(this.entity.info.toPort) ?? {
657
- x: this.data.position.from.x,
658
- y: this.data.position.from.y
659
- };
761
+ if (this.entity.info.drawingTo) {
762
+ this.data.position.to = this.entity.info.drawingTo;
763
+ } else {
764
+ this.data.position.to = this.entity.to?.getData(WorkflowNodePortsData)?.getInputPoint(this.entity.info.toPort) ?? {
765
+ x: this.data.position.from.x,
766
+ y: this.data.position.from.y,
767
+ location: this.data.position.from.location === "right" ? "left" : "top"
768
+ };
769
+ }
660
770
  this.data.version = [
661
771
  this.lineType,
662
772
  this.data.position.from.x,
663
773
  this.data.position.from.y,
774
+ this.data.position.from.location,
664
775
  this.data.position.to.x,
665
- this.data.position.to.y
776
+ this.data.position.to.y,
777
+ this.data.position.to.location
666
778
  ].join("-");
667
779
  }
668
780
  get currentLine() {
@@ -692,13 +804,16 @@ var POINT_RADIUS = 10;
692
804
  var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
693
805
  constructor(opts) {
694
806
  super(opts);
807
+ this._onLineDataChangeEmitter = new Emitter2();
808
+ this.onLineDataChange = this._onLineDataChangeEmitter.event;
695
809
  this._uiState = {
696
810
  hasError: false,
697
811
  flowing: false,
698
812
  disabled: false,
699
- vertical: false,
700
813
  hideArrow: false,
701
814
  reverse: false,
815
+ shrink: 10,
816
+ curvature: 0.25,
702
817
  highlightColor: "",
703
818
  lockedColor: ""
704
819
  };
@@ -716,11 +831,21 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
716
831
  to: opts.to,
717
832
  drawingTo: opts.drawingTo,
718
833
  fromPort: opts.fromPort,
719
- toPort: opts.toPort
834
+ toPort: opts.toPort,
835
+ data: opts.data
720
836
  });
721
837
  if (opts.drawingTo) {
722
838
  this.isDrawing = true;
723
839
  }
840
+ this.onEntityChange(() => {
841
+ this.fromPort?.validate();
842
+ this.toPort?.validate();
843
+ });
844
+ this.onDispose(() => {
845
+ this.fromPort?.validate();
846
+ this.toPort?.validate();
847
+ });
848
+ this.toDispose.push(this._onLineDataChangeEmitter);
724
849
  }
725
850
  /**
726
851
  * 转成线条 id
@@ -763,9 +888,13 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
763
888
  * 更新线条扩展数据
764
889
  * @param data
765
890
  */
766
- set lineData(data) {
767
- this._lineData = data;
768
- this.fireChange();
891
+ set lineData(newValue) {
892
+ const oldValue = this._lineData;
893
+ if (!isEqual2(oldValue, newValue)) {
894
+ this._lineData = newValue;
895
+ this._onLineDataChangeEmitter.fire({ oldValue, newValue });
896
+ this.fireChange();
897
+ }
769
898
  }
770
899
  /**
771
900
  * 获取线条的前置节点
@@ -788,20 +917,17 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
788
917
  }
789
918
  /**
790
919
  * 获取是否 testrun processing
791
- * @deprecated use `uiState.flowing` instead
920
+ * @deprecated use `flowing` instead
792
921
  */
793
922
  get processing() {
794
923
  return this._uiState.flowing;
795
924
  }
796
925
  /**
797
926
  * 设置 testrun processing 状态
798
- * @deprecated use `uiState.flowing` instead
927
+ * @deprecated use `flowing` instead
799
928
  */
800
929
  set processing(status) {
801
- if (this._uiState.flowing !== status) {
802
- this._uiState.flowing = status;
803
- this.fireChange();
804
- }
930
+ this.flowing = status;
805
931
  }
806
932
  // 获取连线是否为错误态
807
933
  get hasError() {
@@ -826,11 +952,11 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
826
952
  if (this.toPort === toPort) {
827
953
  return;
828
954
  }
955
+ const prePort = this.toPort;
829
956
  if (toPort && toPort.portType === "input" && this.linesManager.canAddLine(this.fromPort, toPort, true)) {
830
957
  const { node, portID } = toPort;
831
958
  this._to = node;
832
959
  this.info.drawingTo = void 0;
833
- this.info.isDefaultLine = false;
834
960
  this.info.to = node.id;
835
961
  this.info.toPort = portID;
836
962
  } else {
@@ -838,6 +964,9 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
838
964
  this.info.to = void 0;
839
965
  this.info.toPort = "";
840
966
  }
967
+ if (prePort) {
968
+ prePort.validate();
969
+ }
841
970
  this.fireChange();
842
971
  }
843
972
  /**
@@ -852,7 +981,6 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
852
981
  }
853
982
  if (!oldDrawingTo || pos.x !== oldDrawingTo.x || pos.y !== oldDrawingTo.y) {
854
983
  this.info.to = void 0;
855
- this.info.isDefaultLine = false;
856
984
  this.info.drawingTo = pos;
857
985
  this.fireChange();
858
986
  }
@@ -918,13 +1046,25 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
918
1046
  get flowing() {
919
1047
  return this.linesManager.isFlowingLine(this, this.uiState.flowing);
920
1048
  }
1049
+ set flowing(flowing) {
1050
+ if (this._uiState.flowing !== flowing) {
1051
+ this._uiState.flowing = flowing;
1052
+ this.fireChange();
1053
+ }
1054
+ }
921
1055
  /** 是否禁用 */
922
1056
  get disabled() {
923
1057
  return this.linesManager.isDisabledLine(this, this.uiState.disabled);
924
1058
  }
925
1059
  /** 是否竖向 */
926
1060
  get vertical() {
927
- return this.linesManager.isVerticalLine(this, this.uiState.vertical);
1061
+ const fromLocation = this.fromPort.location;
1062
+ const toLocation = this.toPort?.location;
1063
+ if (toLocation) {
1064
+ return toLocation === "top";
1065
+ } else {
1066
+ return fromLocation === "bottom";
1067
+ }
928
1068
  }
929
1069
  /** 获取线条渲染器类型 */
930
1070
  get renderType() {
@@ -932,7 +1072,7 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
932
1072
  }
933
1073
  /** 获取线条样式 */
934
1074
  get className() {
935
- return this.linesManager.setLineClassName(this) ?? "";
1075
+ return [this.linesManager.setLineClassName(this), this._uiState.className].filter((s) => !!s).join(" ");
936
1076
  }
937
1077
  get color() {
938
1078
  return this.linesManager.getLineColor(this);
@@ -946,16 +1086,18 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
946
1086
  this.info = info;
947
1087
  this._from = this.document.getNode(info.from);
948
1088
  this._to = info.to ? this.document.getNode(info.to) : void 0;
1089
+ this._lineData = info.data;
949
1090
  this.fireChange();
950
1091
  }
951
1092
  }
952
1093
  // 校验连线是否为错误态
953
1094
  validate() {
954
- const { fromPort, toPort } = this;
955
1095
  this.validateSelf();
956
- fromPort?.validate();
957
- toPort?.validate();
958
1096
  }
1097
+ /**
1098
+ * use `validate` instead
1099
+ * @deprecated
1100
+ */
959
1101
  validateSelf() {
960
1102
  const { fromPort, toPort } = this;
961
1103
  if (fromPort) {
@@ -1105,12 +1247,12 @@ WorkflowSelectService = __decorateClass([
1105
1247
 
1106
1248
  // src/service/workflow-hover-service.ts
1107
1249
  import { inject as inject2, injectable as injectable2 } from "inversify";
1108
- import { Emitter as Emitter2 } from "@flowgram.ai/utils";
1250
+ import { Emitter as Emitter3 } from "@flowgram.ai/utils";
1109
1251
  import { EntityManager } from "@flowgram.ai/core";
1110
1252
  var WorkflowHoverService = class {
1111
1253
  constructor() {
1112
- this.onHoveredChangeEmitter = new Emitter2();
1113
- this.onUpdateHoverPositionEmitter = new Emitter2();
1254
+ this.onHoveredChangeEmitter = new Emitter3();
1255
+ this.onUpdateHoverPositionEmitter = new Emitter3();
1114
1256
  this.onHoveredChange = this.onHoveredChangeEmitter.event;
1115
1257
  this.onUpdateHoverPosition = this.onUpdateHoverPositionEmitter.event;
1116
1258
  // 当前鼠标 hover 位置
@@ -1176,7 +1318,7 @@ import { inject as inject6, injectable as injectable6, postConstruct as postCons
1176
1318
  import {
1177
1319
  domUtils as domUtils2,
1178
1320
  PromiseDeferred,
1179
- Emitter as Emitter5,
1321
+ Emitter as Emitter6,
1180
1322
  DisposableCollection as DisposableCollection2,
1181
1323
  Rectangle as Rectangle8,
1182
1324
  delay as delay2
@@ -1185,7 +1327,7 @@ import {
1185
1327
  FlowNodeTransformData as FlowNodeTransformData6,
1186
1328
  FlowOperationBaseService
1187
1329
  } from "@flowgram.ai/document";
1188
- import { FlowNodeBaseType as FlowNodeBaseType2 } from "@flowgram.ai/document";
1330
+ import { FlowNodeBaseType as FlowNodeBaseType3 } from "@flowgram.ai/document";
1189
1331
  import {
1190
1332
  CommandService,
1191
1333
  MouseTouchEvent,
@@ -1197,7 +1339,7 @@ import {
1197
1339
  // src/workflow-lines-manager.ts
1198
1340
  import { last } from "lodash-es";
1199
1341
  import { inject as inject3, injectable as injectable3 } from "inversify";
1200
- import { DisposableCollection, Emitter as Emitter3 } from "@flowgram.ai/utils";
1342
+ import { DisposableCollection, Emitter as Emitter4 } from "@flowgram.ai/utils";
1201
1343
  import { FlowNodeRenderData as FlowNodeRenderData2, FlowNodeTransformData as FlowNodeTransformData3 } from "@flowgram.ai/document";
1202
1344
  import { EntityManager as EntityManager2, PlaygroundConfigEntity as PlaygroundConfigEntity2 } from "@flowgram.ai/core";
1203
1345
 
@@ -1281,8 +1423,8 @@ var WorkflowLinesManager = class {
1281
1423
  this.toDispose = new DisposableCollection();
1282
1424
  // 线条类型
1283
1425
  this._lineType = 0 /* BEZIER */;
1284
- this.onAvailableLinesChangeEmitter = new Emitter3();
1285
- this.onForceUpdateEmitter = new Emitter3();
1426
+ this.onAvailableLinesChangeEmitter = new Emitter4();
1427
+ this.onForceUpdateEmitter = new Emitter4();
1286
1428
  /**
1287
1429
  * 有效的线条被添加或者删除时候触发,未连上的线条不算
1288
1430
  */
@@ -1353,6 +1495,9 @@ var WorkflowLinesManager = class {
1353
1495
  WorkflowLineEntity.portInfoToLineId(portInfo)
1354
1496
  );
1355
1497
  }
1498
+ getLineById(id) {
1499
+ return this.entityManager.getEntityById(id);
1500
+ }
1356
1501
  replaceLine(oldPortInfo, newPortInfo) {
1357
1502
  const oldLine = this.getLine(oldPortInfo);
1358
1503
  if (oldLine) {
@@ -1361,7 +1506,7 @@ var WorkflowLinesManager = class {
1361
1506
  return this.createLine(newPortInfo);
1362
1507
  }
1363
1508
  createLine(options) {
1364
- const { from, to, drawingTo, fromPort, toPort } = options;
1509
+ const { from, to, drawingTo, fromPort, toPort, data } = options;
1365
1510
  const available = Boolean(from && to);
1366
1511
  const key = options.key || WorkflowLineEntity.portInfoToLineId(options);
1367
1512
  let line = this.entityManager.getEntityById(key);
@@ -1384,7 +1529,8 @@ var WorkflowLinesManager = class {
1384
1529
  fromPort,
1385
1530
  toPort,
1386
1531
  to,
1387
- drawingTo
1532
+ drawingTo,
1533
+ data
1388
1534
  });
1389
1535
  this.registerData(line);
1390
1536
  fromNode.addLine(line);
@@ -1395,7 +1541,6 @@ var WorkflowLinesManager = class {
1395
1541
  }
1396
1542
  fromNode.removeLine(line);
1397
1543
  toNode?.removeLine(line);
1398
- line.validate();
1399
1544
  });
1400
1545
  line.onDispose(() => {
1401
1546
  if (available) {
@@ -1406,6 +1551,14 @@ var WorkflowLinesManager = class {
1406
1551
  });
1407
1552
  }
1408
1553
  });
1554
+ line.onLineDataChange(({ oldValue }) => {
1555
+ this.onAvailableLinesChangeEmitter.fire({
1556
+ type: "LINE_DATA_CHANGE" /* LINE_DATA_CHANGE */,
1557
+ toJSON: () => line.toJSON(),
1558
+ oldValue,
1559
+ entity: line
1560
+ });
1561
+ });
1409
1562
  if (available) {
1410
1563
  this.onAvailableLinesChangeEmitter.fire({
1411
1564
  type: "ADD_LINE" /* ADD_LINE */,
@@ -1469,12 +1622,6 @@ var WorkflowLinesManager = class {
1469
1622
  }
1470
1623
  return defaultValue;
1471
1624
  }
1472
- isVerticalLine(line, defaultValue = false) {
1473
- if (this.options.isVerticalLine) {
1474
- return this.options.isVerticalLine(line);
1475
- }
1476
- return defaultValue;
1477
- }
1478
1625
  setLineRenderType(line) {
1479
1626
  if (this.options.setLineRenderType) {
1480
1627
  return this.options.setLineRenderType(line);
@@ -1518,6 +1665,14 @@ var WorkflowLinesManager = class {
1518
1665
  if (fromPort === toPort || fromPort.node === toPort.node || fromPort.portType !== "output" || toPort.portType !== "input" || toPort.disabled) {
1519
1666
  return false;
1520
1667
  }
1668
+ const fromCanAdd = fromPort.node.getNodeRegistry().canAddLine;
1669
+ const toCanAdd = toPort.node.getNodeRegistry().canAddLine;
1670
+ if (fromCanAdd && !fromCanAdd(fromPort, toPort, this, silent)) {
1671
+ return false;
1672
+ }
1673
+ if (toCanAdd && !toCanAdd(fromPort, toPort, this, silent)) {
1674
+ return false;
1675
+ }
1521
1676
  if (this.options.canAddLine) {
1522
1677
  return this.options.canAddLine(fromPort, toPort, this, silent);
1523
1678
  }
@@ -1621,11 +1776,11 @@ WorkflowLinesManager = __decorateClass([
1621
1776
  // src/workflow-document.ts
1622
1777
  import { customAlphabet } from "nanoid";
1623
1778
  import { inject as inject5, injectable as injectable5, optional, postConstruct } from "inversify";
1624
- import { Emitter as Emitter4 } from "@flowgram.ai/utils";
1779
+ import { Emitter as Emitter5 } from "@flowgram.ai/utils";
1625
1780
  import { NodeEngineContext } from "@flowgram.ai/form-core";
1626
1781
  import {
1627
1782
  FlowDocument,
1628
- FlowNodeBaseType,
1783
+ FlowNodeBaseType as FlowNodeBaseType2,
1629
1784
  FlowNodeTransformData as FlowNodeTransformData5
1630
1785
  } from "@flowgram.ai/document";
1631
1786
  import {
@@ -1782,10 +1937,10 @@ var WorkflowDocumentProvider = Symbol("WorkflowDocumentProvider");
1782
1937
  var WorkflowDocument = class extends FlowDocument {
1783
1938
  constructor() {
1784
1939
  super(...arguments);
1785
- this._onContentChangeEmitter = new Emitter4();
1786
- this.onLoadedEmitter = new Emitter4();
1940
+ this._onContentChangeEmitter = new Emitter5();
1941
+ this.onLoadedEmitter = new Emitter5();
1787
1942
  this.onContentChange = this._onContentChangeEmitter.event;
1788
- this._onReloadEmitter = new Emitter4();
1943
+ this._onReloadEmitter = new Emitter5();
1789
1944
  this.onReload = this._onReloadEmitter.event;
1790
1945
  /**
1791
1946
  * 数据加载完成
@@ -1797,6 +1952,11 @@ var WorkflowDocument = class extends FlowDocument {
1797
1952
  get loading() {
1798
1953
  return this._loading;
1799
1954
  }
1955
+ /**
1956
+ * use `ctx.tools.fitView()` instead
1957
+ * @deprecated
1958
+ * @param easing
1959
+ */
1800
1960
  async fitView(easing) {
1801
1961
  return fitView(this, this.playgroundConfig, easing).then(() => {
1802
1962
  this.linesManager.forceUpdate();
@@ -1918,7 +2078,7 @@ var WorkflowDocument = class extends FlowDocument {
1918
2078
  toJSON: () => this.toNodeJSON(node)
1919
2079
  });
1920
2080
  node.onDispose(() => {
1921
- if (!node.parent || node.parent.flowNodeType === FlowNodeBaseType.ROOT) {
2081
+ if (!node.parent || node.parent.flowNodeType === FlowNodeBaseType2.ROOT) {
1922
2082
  return;
1923
2083
  }
1924
2084
  const parentTransform = node.parent.getData(FlowNodeTransformData5);
@@ -2086,10 +2246,10 @@ var WorkflowDocument = class extends FlowDocument {
2086
2246
  );
2087
2247
  }
2088
2248
  getAllNodes() {
2089
- return this.entityManager.getEntities(WorkflowNodeEntity).filter((n) => n.id !== FlowNodeBaseType.ROOT);
2249
+ return this.entityManager.getEntities(WorkflowNodeEntity).filter((n) => n.id !== FlowNodeBaseType2.ROOT);
2090
2250
  }
2091
2251
  getAllPorts() {
2092
- return this.entityManager.getEntities(WorkflowPortEntity).filter((p) => p.node.id !== FlowNodeBaseType.ROOT);
2252
+ return this.entityManager.getEntities(WorkflowPortEntity).filter((p) => p.node.id !== FlowNodeBaseType2.ROOT);
2093
2253
  }
2094
2254
  /**
2095
2255
  * 获取画布中的非游离节点
@@ -2104,10 +2264,13 @@ var WorkflowDocument = class extends FlowDocument {
2104
2264
  from: line.from.id,
2105
2265
  to: line.to.id
2106
2266
  }));
2107
- const startNodeId = allNode.find((node) => node.isStart).id;
2108
- const endNodeId = allNode.find((node) => node.isNodeEnd).id;
2267
+ const startNodeId = allNode.find((node) => node.isStart)?.id;
2268
+ const endNodeId = allNode.find((node) => node.isNodeEnd)?.id;
2109
2269
  const nodeInContainer = allNode.filter((node) => node.parent?.getNodeMeta().isContainer).map((node) => node.id);
2110
- const associatedCache = /* @__PURE__ */ new Set([endNodeId, ...nodeInContainer]);
2270
+ const associatedCache = new Set(nodeInContainer);
2271
+ if (endNodeId) {
2272
+ associatedCache.add(endNodeId);
2273
+ }
2111
2274
  const bfs = (nodeId) => {
2112
2275
  if (associatedCache.has(nodeId)) {
2113
2276
  return;
@@ -2121,7 +2284,9 @@ var WorkflowDocument = class extends FlowDocument {
2121
2284
  }, []);
2122
2285
  nextNodes.forEach(bfs);
2123
2286
  };
2124
- bfs(startNodeId);
2287
+ if (startNodeId) {
2288
+ bfs(startNodeId);
2289
+ }
2125
2290
  const associatedNodes = allNode.filter((node) => associatedCache.has(node.id));
2126
2291
  return associatedNodes;
2127
2292
  }
@@ -2243,11 +2408,17 @@ var WorkflowDocument = class extends FlowDocument {
2243
2408
  * 导出数据
2244
2409
  */
2245
2410
  toJSON() {
2411
+ if (this.disposed) {
2412
+ throw new Error(
2413
+ "The WorkflowDocument has been disposed and it is no longer possible to call toJSON."
2414
+ );
2415
+ }
2246
2416
  const rootJSON = this.toNodeJSON(this.root);
2247
- return {
2417
+ const json = {
2248
2418
  nodes: rootJSON.blocks ?? [],
2249
2419
  edges: rootJSON.edges ?? []
2250
2420
  };
2421
+ return json;
2251
2422
  }
2252
2423
  dispose() {
2253
2424
  super.dispose();
@@ -2259,10 +2430,11 @@ var WorkflowDocument = class extends FlowDocument {
2259
2430
  renderJSON(json, options) {
2260
2431
  const { parent = this.root, isClone = false } = options ?? {};
2261
2432
  const containerID = this.getNodeSubCanvas(parent)?.canvasNode.id ?? parent.id;
2262
- const nodes = json.nodes.map(
2433
+ const processedJSON = buildGroupJSON(json);
2434
+ const nodes = processedJSON.nodes.map(
2263
2435
  (nodeJSON) => this.createWorkflowNode(nodeJSON, isClone, containerID)
2264
2436
  );
2265
- const edges = json.edges.map((edge) => this.createWorkflowLine(edge, containerID)).filter(Boolean);
2437
+ const edges = processedJSON.edges.map((edge) => this.createWorkflowLine(edge, containerID)).filter(Boolean);
2266
2438
  return { nodes, edges };
2267
2439
  }
2268
2440
  getNodeSubCanvas(node) {
@@ -2272,13 +2444,19 @@ var WorkflowDocument = class extends FlowDocument {
2272
2444
  return subCanvas;
2273
2445
  }
2274
2446
  getNodeChildren(node) {
2275
- if (!node) return [];
2447
+ if (!node || node.flowNodeType === FlowNodeBaseType2.GROUP) return [];
2276
2448
  const subCanvas = this.getNodeSubCanvas(node);
2277
- const childrenWithCanvas = subCanvas ? subCanvas.canvasNode.collapsedChildren : node.collapsedChildren;
2278
- const children = childrenWithCanvas.filter((child) => {
2449
+ const realChildren = subCanvas ? subCanvas.canvasNode.blocks : node.blocks;
2450
+ const childrenWithoutSubCanvas = realChildren.filter((child) => {
2279
2451
  const childMeta = child.getNodeMeta();
2280
2452
  return !childMeta.subCanvas?.(node)?.isCanvas;
2281
2453
  }).filter(Boolean);
2454
+ const children = childrenWithoutSubCanvas.map((child) => {
2455
+ if (child.flowNodeType === FlowNodeBaseType2.GROUP) {
2456
+ return [child, ...child.blocks];
2457
+ }
2458
+ return child;
2459
+ }).flat();
2282
2460
  return children;
2283
2461
  }
2284
2462
  toLineJSON(line) {
@@ -2315,7 +2493,8 @@ var WorkflowDocument = class extends FlowDocument {
2315
2493
  from: json.sourceNodeID,
2316
2494
  fromPort: json.sourcePortID,
2317
2495
  to: json.targetNodeID,
2318
- toPort: json.targetPortID
2496
+ toPort: json.targetPortID,
2497
+ data: json.data
2319
2498
  };
2320
2499
  if (!parentId) {
2321
2500
  return this.linesManager.createLine(lineInfo);
@@ -2380,10 +2559,10 @@ function checkDragSuccess(time, e, originLine) {
2380
2559
  }
2381
2560
  var WorkflowDragService = class {
2382
2561
  constructor() {
2383
- this._onDragLineEventEmitter = new Emitter5();
2562
+ this._onDragLineEventEmitter = new Emitter6();
2384
2563
  this.onDragLineEventChange = this._onDragLineEventEmitter.event;
2385
2564
  this.isDragging = false;
2386
- this._nodesDragEmitter = new Emitter5();
2565
+ this._nodesDragEmitter = new Emitter6();
2387
2566
  this.onNodesDrag = this._nodesDragEmitter.event;
2388
2567
  this._toDispose = new DisposableCollection2();
2389
2568
  this._droppableTransforms = [];
@@ -2410,7 +2589,7 @@ var WorkflowDragService = class {
2410
2589
  }
2411
2590
  this.isDragging = true;
2412
2591
  const sameParent = this.childrenOfContainer(selectedNodes);
2413
- if (sameParent && sameParent.flowNodeType !== FlowNodeBaseType2.ROOT) {
2592
+ if (sameParent && sameParent.flowNodeType !== FlowNodeBaseType3.ROOT) {
2414
2593
  selectedNodes = [sameParent];
2415
2594
  }
2416
2595
  let startPosition = this.getNodesPosition(selectedNodes);
@@ -2580,7 +2759,7 @@ var WorkflowDragService = class {
2580
2759
  if (!mousePos) {
2581
2760
  return { x: 0, y: 0 };
2582
2761
  }
2583
- if (!subNodeType || !containerNode || containerNode.flowNodeType === FlowNodeBaseType2.ROOT) {
2762
+ if (!subNodeType || !containerNode || containerNode.flowNodeType === FlowNodeBaseType3.ROOT) {
2584
2763
  return mousePos;
2585
2764
  }
2586
2765
  const isParentEmpty = !containerNode.children || containerNode.children.length === 0;
@@ -2782,10 +2961,16 @@ var WorkflowDragService = class {
2782
2961
  originLine.highlightColor = this.linesManager.lineColor.hidden;
2783
2962
  }
2784
2963
  dragSuccess = true;
2964
+ const pos = config.getPosFromMouseEvent(event);
2785
2965
  line = this.linesManager.createLine({
2786
2966
  from: fromPort.node.id,
2787
2967
  fromPort: fromPort.portID,
2788
- drawingTo: config.getPosFromMouseEvent(event)
2968
+ data: originLine?.lineData,
2969
+ drawingTo: {
2970
+ x: pos.x,
2971
+ y: pos.y,
2972
+ location: fromPort.location === "right" ? "left" : "top"
2973
+ }
2789
2974
  });
2790
2975
  if (!line) {
2791
2976
  return;
@@ -2820,9 +3005,17 @@ var WorkflowDragService = class {
2820
3005
  lineErrorReset = hasError;
2821
3006
  }
2822
3007
  if (line.toPort) {
2823
- line.drawingTo = { x: line.toPort.point.x, y: line.toPort.point.y };
3008
+ line.drawingTo = {
3009
+ x: line.toPort.point.x,
3010
+ y: line.toPort.point.y,
3011
+ location: line.toPort.location
3012
+ };
2824
3013
  } else {
2825
- line.drawingTo = { x: dragPos.x, y: dragPos.y };
3014
+ line.drawingTo = {
3015
+ x: dragPos.x,
3016
+ y: dragPos.y,
3017
+ location: line.fromPort.location === "right" ? "left" : "top"
3018
+ };
2826
3019
  }
2827
3020
  originLine?.validate();
2828
3021
  line.validate();
@@ -2866,7 +3059,8 @@ var WorkflowDragService = class {
2866
3059
  from: fromPort.node.id,
2867
3060
  fromPort: fromPort.portID,
2868
3061
  to: toPort.node.id,
2869
- toPort: toPort.portID
3062
+ toPort: toPort.portID,
3063
+ data: originLine?.lineData
2870
3064
  } : void 0;
2871
3065
  const isReset = originLine && toPort;
2872
3066
  if (isReset && !this.linesManager.canReset(
@@ -2979,7 +3173,7 @@ WorkflowDragService = __decorateClass([
2979
3173
  import { inject as inject7, injectable as injectable7, postConstruct as postConstruct3 } from "inversify";
2980
3174
  import { PlaygroundConfigEntity as PlaygroundConfigEntity6 } from "@flowgram.ai/core";
2981
3175
  import { EntityManager as EntityManager3 } from "@flowgram.ai/core";
2982
- import { DisposableCollection as DisposableCollection3, Emitter as Emitter6 } from "@flowgram.ai/utils";
3176
+ import { DisposableCollection as DisposableCollection3, Emitter as Emitter7 } from "@flowgram.ai/utils";
2983
3177
 
2984
3178
  // src/utils/layout-to-positions.ts
2985
3179
  import { FlowNodeTransformData as FlowNodeTransformData7 } from "@flowgram.ai/document";
@@ -3024,7 +3218,7 @@ var layoutToPositions = async (nodes, nodePositionMap) => {
3024
3218
  // src/service/workflow-reset-layout-service.ts
3025
3219
  var WorkflowResetLayoutService = class {
3026
3220
  constructor() {
3027
- this._resetLayoutEmitter = new Emitter6();
3221
+ this._resetLayoutEmitter = new Emitter7();
3028
3222
  /**
3029
3223
  * reset layout事件
3030
3224
  */
@@ -3083,13 +3277,13 @@ WorkflowResetLayoutService = __decorateClass([
3083
3277
 
3084
3278
  // src/service/workflow-operation-base-service.ts
3085
3279
  import { inject as inject8 } from "inversify";
3086
- import { Emitter as Emitter7 } from "@flowgram.ai/utils";
3280
+ import { Emitter as Emitter8 } from "@flowgram.ai/utils";
3087
3281
  import { FlowOperationBaseServiceImpl } from "@flowgram.ai/document";
3088
3282
  import { TransformData as TransformData10 } from "@flowgram.ai/core";
3089
3283
  var WorkflowOperationBaseServiceImpl = class extends FlowOperationBaseServiceImpl {
3090
3284
  constructor() {
3091
3285
  super(...arguments);
3092
- this.onNodePostionUpdateEmitter = new Emitter7();
3286
+ this.onNodePostionUpdateEmitter = new Emitter8();
3093
3287
  this.onNodePostionUpdate = this.onNodePostionUpdateEmitter.event;
3094
3288
  }
3095
3289
  updateNodePosition(nodeOrId, position) {
@@ -3136,6 +3330,7 @@ function usePlaygroundReadonlyState(listenChange) {
3136
3330
  function checkTargetDraggable(el) {
3137
3331
  return el && el.tagName !== "INPUT" && el.tagName !== "TEXTAREA" && !el.closest(".flow-canvas-not-draggable");
3138
3332
  }
3333
+ var isFirefox = navigator?.userAgent?.includes?.("Firefox");
3139
3334
  function useNodeRender(nodeFromProps) {
3140
3335
  const node = nodeFromProps || useContext(PlaygroundEntityContext);
3141
3336
  const renderData = node.getData(FlowNodeRenderData3);
@@ -3199,7 +3394,6 @@ function useNodeRender(nodeFromProps) {
3199
3394
  );
3200
3395
  const deleteNode = useCallback(() => node.dispose(), [node]);
3201
3396
  useListenEvents(portsData.onDataChange);
3202
- const isFirefox = navigator?.userAgent?.includes?.("Firefox");
3203
3397
  const onFocus = useCallback(() => {
3204
3398
  if (isFirefox) {
3205
3399
  nodeRef.current?.setAttribute("draggable", "false");
@@ -3502,6 +3696,7 @@ export {
3502
3696
  WorkflowSelectService,
3503
3697
  WorkflowSimpleLineContribution,
3504
3698
  bindConfigEntity,
3699
+ buildGroupJSON,
3505
3700
  compose,
3506
3701
  composeAsync,
3507
3702
  delay,