@flowgram-vue/document 0.2.0

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 (42) hide show
  1. package/LICENSE +22 -0
  2. package/dist/index.cjs +3233 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.ts +1728 -0
  5. package/dist/index.js +3199 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +61 -0
  8. package/src/datas/flow-node-render-data.ts +227 -0
  9. package/src/datas/flow-node-transform-data.ts +337 -0
  10. package/src/datas/flow-node-transition-data.ts +182 -0
  11. package/src/datas/index.ts +8 -0
  12. package/src/entities/flow-document-transformer-entity.ts +125 -0
  13. package/src/entities/flow-node-entity.ts +415 -0
  14. package/src/entities/flow-renderer-state-entity.ts +127 -0
  15. package/src/entities/index.ts +8 -0
  16. package/src/flow-document-config.ts +42 -0
  17. package/src/flow-document-container-module.ts +33 -0
  18. package/src/flow-document-contribution.ts +22 -0
  19. package/src/flow-document-options.ts +79 -0
  20. package/src/flow-document.ts +766 -0
  21. package/src/flow-render-tree.ts +236 -0
  22. package/src/flow-virtual-tree.ts +244 -0
  23. package/src/index.ts +16 -0
  24. package/src/layout/horizontal-fixed-layout.ts +198 -0
  25. package/src/layout/index.ts +7 -0
  26. package/src/layout/vertical-fixed-layout.ts +198 -0
  27. package/src/services/flow-drag-service.ts +211 -0
  28. package/src/services/flow-group-service/flow-group-controller.ts +120 -0
  29. package/src/services/flow-group-service/flow-group-service.ts +107 -0
  30. package/src/services/flow-group-service/flow-group-utils.ts +104 -0
  31. package/src/services/flow-group-service/index.ts +7 -0
  32. package/src/services/flow-operation-base-service.ts +333 -0
  33. package/src/services/index.ts +8 -0
  34. package/src/typings/flow-group.ts +8 -0
  35. package/src/typings/flow-layout.ts +72 -0
  36. package/src/typings/flow-node-register.ts +356 -0
  37. package/src/typings/flow-operation.ts +312 -0
  38. package/src/typings/flow-transition.ts +104 -0
  39. package/src/typings/flow.ts +70 -0
  40. package/src/typings/index.ts +11 -0
  41. package/src/utils/get-default-spacing.ts +21 -0
  42. package/src/utils/index.ts +6 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { FlowNodeBaseType } from '../../typings';
7
+ import { FlowNodeEntity } from '../../entities';
8
+ import { FlowGroupController } from './flow-group-controller';
9
+
10
+ export namespace FlowGroupUtils {
11
+ /** 找到节点所有上级 */
12
+ const findNodeParents = (node: FlowNodeEntity): FlowNodeEntity[] => {
13
+ const parents = [];
14
+ let parent = node.parent;
15
+ while (parent) {
16
+ parents.push(parent);
17
+ parent = parent.parent;
18
+ }
19
+ return parents;
20
+ };
21
+
22
+ /** 节点是否处于分组中 */
23
+ const isNodeInGroup = (node: FlowNodeEntity): boolean => {
24
+ // 处于分组中
25
+ if (node?.parent?.flowNodeType === FlowNodeBaseType.GROUP) {
26
+ return true;
27
+ }
28
+ return false;
29
+ };
30
+
31
+ /** 判断节点能否组成分组 */
32
+ export const validate = (nodes: FlowNodeEntity[]): boolean => {
33
+ if (!nodes || !Array.isArray(nodes) || nodes.length === 0) {
34
+ // 参数不合法
35
+ return false;
36
+ }
37
+
38
+ // 判断是否有分组节点
39
+ const isGroupRelatedNode = nodes.some((node) => isGroupNode(node));
40
+ if (isGroupRelatedNode) return false;
41
+
42
+ // 判断是否有节点已经处于分组中
43
+ const hasGroup = nodes.some((node) => node && isNodeInGroup(node));
44
+ if (hasGroup) return false;
45
+
46
+ // 判断是否来自同一个父亲
47
+ const parent = nodes[0].parent;
48
+ const isSameParent = nodes.every((node) => node.parent === parent);
49
+ if (!isSameParent) return false;
50
+
51
+ // 判断节点索引是否连续
52
+ const indexes = nodes.map((node) => node.index).sort((a, b) => a - b);
53
+ const isIndexContinuous = indexes.every((index, i, arr) => {
54
+ if (i === 0) {
55
+ return true;
56
+ }
57
+ return index === arr[i - 1] + 1;
58
+ });
59
+ if (!isIndexContinuous) return false;
60
+
61
+ // 判断节点父亲是否已经在分组中
62
+ const parents = findNodeParents(nodes[0]);
63
+ const parentsInGroup = parents.some((parent) => isNodeInGroup(parent));
64
+ if (parentsInGroup) return false;
65
+
66
+ // 参数正确
67
+ return true;
68
+ };
69
+
70
+ /** 获取节点分组控制 */
71
+ export const getNodeGroupController = (
72
+ node?: FlowNodeEntity
73
+ ): FlowGroupController | undefined => {
74
+ if (!node) {
75
+ return;
76
+ }
77
+ if (!isNodeInGroup(node)) {
78
+ return;
79
+ }
80
+ const groupNode = node?.parent;
81
+ return FlowGroupController.create(groupNode);
82
+ };
83
+
84
+ /** 向上递归查找分组递归控制 */
85
+ export const getNodeRecursionGroupController = (
86
+ node?: FlowNodeEntity
87
+ ): FlowGroupController | undefined => {
88
+ if (!node) {
89
+ return;
90
+ }
91
+ const group = getNodeGroupController(node);
92
+ if (group) {
93
+ return group;
94
+ }
95
+ if (node.parent) {
96
+ return getNodeRecursionGroupController(node.parent);
97
+ }
98
+ return;
99
+ };
100
+
101
+ /** 是否分组节点 */
102
+ export const isGroupNode = (group: FlowNodeEntity): boolean =>
103
+ group.flowNodeType === FlowNodeBaseType.GROUP;
104
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export { FlowGroupController } from './flow-group-controller';
7
+ export { FlowGroupService } from './flow-group-service';
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { inject, injectable, postConstruct } from 'inversify';
7
+ import { DisposableCollection, Emitter } from '@flowgram-vue/utils';
8
+ import { EntityManager } from '@flowgram-vue/core';
9
+
10
+ import {
11
+ FlowOperation,
12
+ FlowOperationBaseService,
13
+ MoveChildNodesOperationValue,
14
+ OperationType,
15
+ } from '../typings/flow-operation';
16
+ import {
17
+ AddBlockConfig,
18
+ AddNodeConfig,
19
+ AddNodeData,
20
+ FlowNodeBaseType,
21
+ FlowNodeEntityOrId,
22
+ FlowNodeJSON,
23
+ MoveNodeConfig,
24
+ OnNodeAddEvent,
25
+ OnNodeMoveEvent,
26
+ } from '../typings';
27
+ import { FlowDocument } from '../flow-document';
28
+ import { FlowNodeEntity } from '../entities';
29
+
30
+ /**
31
+ * 操作服务
32
+ */
33
+ @injectable()
34
+ export class FlowOperationBaseServiceImpl implements FlowOperationBaseService {
35
+ @inject(EntityManager)
36
+ protected entityManager: EntityManager;
37
+
38
+ @inject(FlowDocument)
39
+ protected document: FlowDocument;
40
+
41
+ protected onNodeAddEmitter = new Emitter<OnNodeAddEvent>();
42
+
43
+ readonly onNodeAdd = this.onNodeAddEmitter.event;
44
+
45
+ protected toDispose = new DisposableCollection();
46
+
47
+ private onNodeMoveEmitter = new Emitter<OnNodeMoveEvent>();
48
+
49
+ readonly onNodeMove = this.onNodeMoveEmitter.event;
50
+
51
+ @postConstruct()
52
+ protected init() {
53
+ this.toDispose.pushAll([this.onNodeAddEmitter, this.onNodeMoveEmitter]);
54
+ }
55
+
56
+ addNode(nodeJSON: FlowNodeJSON, config: AddNodeConfig = {}): FlowNodeEntity {
57
+ const { parent, index, hidden } = config;
58
+ let parentEntity;
59
+
60
+ if (parent) {
61
+ parentEntity = this.toNodeEntity(parent);
62
+ }
63
+
64
+ let register;
65
+ if (parentEntity) {
66
+ register = parentEntity.getNodeRegistry();
67
+ }
68
+
69
+ const addJSON = {
70
+ ...nodeJSON,
71
+ type: nodeJSON.type || FlowNodeBaseType.BLOCK,
72
+ };
73
+
74
+ const addNodeData: AddNodeData = {
75
+ ...addJSON,
76
+ parent: parentEntity,
77
+ index,
78
+ hidden,
79
+ };
80
+
81
+ let added;
82
+ if (parentEntity && register?.addChild) {
83
+ added = register.addChild(parentEntity, addJSON, {
84
+ index,
85
+ hidden,
86
+ });
87
+ } else {
88
+ added = this.document.addNode(addNodeData);
89
+ }
90
+
91
+ this.onNodeAddEmitter.fire({
92
+ node: added,
93
+ data: addNodeData,
94
+ });
95
+
96
+ return added;
97
+ }
98
+
99
+ addFromNode(fromNode: FlowNodeEntityOrId, nodeJSON: FlowNodeJSON): FlowNodeEntity {
100
+ return this.document.addFromNode(fromNode, nodeJSON);
101
+ }
102
+
103
+ deleteNode(node: FlowNodeEntityOrId): void {
104
+ this.document.removeNode(node);
105
+ }
106
+
107
+ deleteNodes(nodes: FlowNodeEntityOrId[]): void {
108
+ (nodes || []).forEach((node) => {
109
+ this.deleteNode(node);
110
+ });
111
+ }
112
+
113
+ addBlock(
114
+ target: FlowNodeEntityOrId,
115
+ blockJSON: FlowNodeJSON,
116
+ config: AddBlockConfig = {}
117
+ ): FlowNodeEntity {
118
+ const { parent, index } = config;
119
+ return this.document.addBlock(target, blockJSON, undefined, parent, index);
120
+ }
121
+
122
+ moveNode(node: FlowNodeEntityOrId, config: MoveNodeConfig = {}) {
123
+ const { parent: newParent, index } = config;
124
+ const entity = this.toNodeEntity(node);
125
+ const parent = entity?.parent;
126
+
127
+ if (!parent) {
128
+ return;
129
+ }
130
+
131
+ const newParentEntity: FlowNodeEntity | undefined = newParent
132
+ ? this.toNodeEntity(newParent)
133
+ : parent;
134
+
135
+ if (!newParentEntity) {
136
+ console.warn('no new parent found', newParent);
137
+ return;
138
+ }
139
+
140
+ let toIndex = typeof index === 'undefined' ? newParentEntity.collapsedChildren.length : index;
141
+
142
+ return this.doMoveNode(entity, newParentEntity, toIndex);
143
+ }
144
+
145
+ /**
146
+ * 拖拽节点
147
+ * @param param0
148
+ * @returns
149
+ */
150
+ dragNodes({ dropNode, nodes }: { dropNode: FlowNodeEntity; nodes: FlowNodeEntity[] }) {
151
+ if (nodes.length === 0) {
152
+ return;
153
+ }
154
+
155
+ const startNode = nodes[0];
156
+ const fromParent = startNode.parent;
157
+ const toParent = dropNode.parent;
158
+
159
+ if (!fromParent || !toParent) {
160
+ return;
161
+ }
162
+
163
+ const fromIndex = fromParent.children.findIndex((child) => child === startNode);
164
+ const dropIndex = toParent.children.findIndex((child) => child === dropNode);
165
+
166
+ let toIndex = dropIndex + 1;
167
+ // 同父级节点移动,处理脏路径
168
+ if (fromParent === toParent && fromIndex < dropIndex) {
169
+ toIndex = toIndex - nodes.length;
170
+ }
171
+
172
+ const value: MoveChildNodesOperationValue = {
173
+ nodeIds: nodes.map((node) => node.id),
174
+ fromParentId: fromParent.id,
175
+ toParentId: toParent.id,
176
+ fromIndex,
177
+ toIndex,
178
+ };
179
+
180
+ return this.apply({
181
+ type: OperationType.moveChildNodes,
182
+ value,
183
+ });
184
+ }
185
+
186
+ /**
187
+ * 执行操作
188
+ * @param operation 可序列化的操作
189
+ * @returns 操作返回
190
+ */
191
+ apply(operation: FlowOperation): any {
192
+ const document = this.document;
193
+ switch (operation.type) {
194
+ case OperationType.addFromNode:
195
+ return document.addFromNode(operation.value.fromId, operation.value.data);
196
+ case OperationType.deleteFromNode:
197
+ return document.getNode(operation.value?.data?.id)?.dispose();
198
+ case OperationType.addBlock: {
199
+ let parent;
200
+
201
+ if (operation.value.parentId) {
202
+ parent = document.getNode(operation.value.parentId);
203
+ }
204
+ return document.addBlock(
205
+ operation.value.targetId,
206
+ operation.value.blockData,
207
+ undefined,
208
+ parent,
209
+ operation.value.index
210
+ );
211
+ }
212
+ case OperationType.deleteBlock: {
213
+ const entity = document.getNode(operation.value?.blockData.id);
214
+ return entity?.dispose();
215
+ }
216
+ case OperationType.createGroup: {
217
+ const groupNode = document.addFromNode(operation.value.targetId, {
218
+ id: operation.value.groupId,
219
+ type: FlowNodeBaseType.GROUP,
220
+ });
221
+ document.moveNodes({
222
+ dropNodeId: operation.value.groupId,
223
+ sortNodeIds: operation.value.nodeIds,
224
+ inside: true,
225
+ });
226
+ return groupNode;
227
+ }
228
+ case OperationType.ungroup: {
229
+ document.moveNodes({
230
+ dropNodeId: operation.value.groupId,
231
+ sortNodeIds: operation.value.nodeIds,
232
+ });
233
+ return document.getNode(operation.value.groupId)?.dispose();
234
+ }
235
+ case OperationType.moveNodes: {
236
+ return document.moveNodes({
237
+ dropNodeId: operation.value.toId,
238
+ sortNodeIds: operation.value.nodeIds,
239
+ });
240
+ }
241
+ case OperationType.moveBlock: {
242
+ return document.moveChildNodes({
243
+ ...operation.value,
244
+ nodeIds: [operation.value.nodeId],
245
+ });
246
+ }
247
+ case OperationType.addNodes: {
248
+ let fromId = operation.value.fromId;
249
+ (operation.value.nodes || []).forEach((node) => {
250
+ const added = document.addFromNode(fromId, node);
251
+ fromId = added.id;
252
+ });
253
+ break;
254
+ }
255
+ case OperationType.deleteNodes: {
256
+ (operation.value.nodes || []).forEach((node) => {
257
+ const entity = document.getNode(node.id);
258
+ entity?.dispose();
259
+ });
260
+ break;
261
+ }
262
+ case OperationType.addChildNode: {
263
+ return document.addNode({
264
+ ...operation.value.data,
265
+ parent: operation.value.parentId ? document.getNode(operation.value.parentId) : undefined,
266
+ originParent: operation.value.originParentId
267
+ ? document.getNode(operation.value.originParentId)
268
+ : undefined,
269
+ index: operation.value.index,
270
+ hidden: operation.value.hidden,
271
+ });
272
+ }
273
+ case OperationType.deleteChildNode:
274
+ return document.getNode(operation.value.data.id)?.dispose();
275
+ case OperationType.moveChildNodes:
276
+ return document.moveChildNodes(operation.value);
277
+ default:
278
+ throw new Error(`unknown operation type`);
279
+ }
280
+ }
281
+
282
+ /**
283
+ * 事务执行
284
+ * @param transaction
285
+ */
286
+ transact(transaction: () => void) {
287
+ transaction();
288
+ }
289
+
290
+ dispose() {
291
+ this.toDispose.dispose();
292
+ }
293
+
294
+ protected toId(node: FlowNodeEntityOrId): string {
295
+ return typeof node === 'string' ? node : node.id;
296
+ }
297
+
298
+ protected toNodeEntity(node: FlowNodeEntityOrId): FlowNodeEntity | undefined {
299
+ return typeof node === 'string' ? this.document.getNode(node) : node;
300
+ }
301
+
302
+ protected getNodeIndex(node: FlowNodeEntityOrId): number {
303
+ const entity = this.toNodeEntity(node);
304
+ const parent = entity?.parent;
305
+
306
+ if (!parent) {
307
+ return -1;
308
+ }
309
+
310
+ return parent.children.findIndex((child) => child === entity);
311
+ }
312
+
313
+ protected doMoveNode(node: FlowNodeEntity, newParent: FlowNodeEntity, index: number) {
314
+ if (!node.parent) {
315
+ throw new Error('root node cannot move');
316
+ }
317
+
318
+ const event: OnNodeMoveEvent = {
319
+ node,
320
+ fromParent: node.parent,
321
+ toParent: newParent,
322
+ fromIndex: this.getNodeIndex(node),
323
+ toIndex: index,
324
+ };
325
+
326
+ this.document.moveChildNodes({
327
+ nodeIds: [this.toId(node)],
328
+ toParentId: this.toId(newParent),
329
+ toIndex: index,
330
+ });
331
+ this.onNodeMoveEmitter.fire(event);
332
+ }
333
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export { FlowDragService } from './flow-drag-service';
7
+ export { FlowOperationBaseServiceImpl } from './flow-operation-base-service';
8
+ export { FlowGroupService, FlowGroupController } from './flow-group-service';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export interface FlowGroupJSON {
7
+ nodeIDs: string[];
8
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { IPoint, PaddingSchema, ScrollSchema, SizeSchema } from '@flowgram-vue/utils';
7
+
8
+ import { type FlowNodeEntity } from '../entities';
9
+ import { type FlowNodeTransformData } from '../datas';
10
+
11
+ export const FlowLayout = Symbol('FlowLayout');
12
+ export const FlowLayoutContribution = Symbol('FlowLayoutContribution');
13
+
14
+ export enum FlowLayoutDefault {
15
+ VERTICAL_FIXED_LAYOUT = 'vertical-fixed-layout', // 垂直固定布局
16
+ HORIZONTAL_FIXED_LAYOUT = 'horizontal-fixed-layout', // 水平固定布局
17
+ }
18
+
19
+ export namespace FlowLayoutDefault {
20
+ export function isVertical(layout: FlowLayout): boolean {
21
+ return layout.name === FlowLayoutDefault.VERTICAL_FIXED_LAYOUT;
22
+ }
23
+ }
24
+
25
+ export interface FlowLayoutContribution {
26
+ onAfterUpdateLocalTransform?: (transform: FlowNodeTransformData, layout: FlowLayout) => void;
27
+ }
28
+
29
+ /**
30
+ * 流程布局算法
31
+ */
32
+ export interface FlowLayout {
33
+ /**
34
+ * 布局名字
35
+ */
36
+ name: string;
37
+ /**
38
+ * 布局切换时候触发
39
+ */
40
+ reload?(): void;
41
+ /**
42
+ * 更新布局
43
+ */
44
+ update(): void;
45
+
46
+ /**
47
+ * 获取节点的 padding 数据
48
+ * @param node
49
+ */
50
+ getPadding(node: FlowNodeEntity): PaddingSchema;
51
+
52
+ /**
53
+ * 获取默认滚动 目前用在 scroll-limit-layer
54
+ * @param contentSize
55
+ */
56
+ getInitScroll(contentSize: SizeSchema): ScrollSchema;
57
+
58
+ /**
59
+ * 获取默认输入点
60
+ */
61
+ getDefaultInputPoint(node: FlowNodeEntity): IPoint;
62
+
63
+ /**
64
+ * 获取默认输出点
65
+ */
66
+ getDefaultOutputPoint(node: FlowNodeEntity): IPoint;
67
+
68
+ /**
69
+ * 获取默认远点
70
+ */
71
+ getDefaultNodeOrigin(): IPoint;
72
+ }