@flowgram-vue/renderer 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 (50) hide show
  1. package/LICENSE +22 -0
  2. package/dist/index.cjs +2933 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.ts +721 -0
  5. package/dist/index.js +2918 -0
  6. package/dist/index.js.map +1 -0
  7. package/index.module.less +167 -0
  8. package/package.json +64 -0
  9. package/src/components/Adder.ts +107 -0
  10. package/src/components/BranchDraggableRenderer.ts +77 -0
  11. package/src/components/Collapse.ts +87 -0
  12. package/src/components/CollapseAdder.ts +94 -0
  13. package/src/components/CustomLine.ts +35 -0
  14. package/src/components/LabelsRenderer.ts +162 -0
  15. package/src/components/LinesRenderer.ts +99 -0
  16. package/src/components/MarkerActivatedArrow.ts +44 -0
  17. package/src/components/MarkerArrow.ts +44 -0
  18. package/src/components/RoundedTurningLine.ts +165 -0
  19. package/src/components/StraightLine.ts +31 -0
  20. package/src/components/utils.ts +295 -0
  21. package/src/entities/README.md +3 -0
  22. package/src/entities/flow-drag-entity.ts +267 -0
  23. package/src/entities/flow-select-config-entity.ts +114 -0
  24. package/src/entities/index.ts +8 -0
  25. package/src/entities/selector-box-config-entity.ts +88 -0
  26. package/src/env.d.ts +10 -0
  27. package/src/flow-renderer-container-module.ts +14 -0
  28. package/src/flow-renderer-contribution.ts +12 -0
  29. package/src/flow-renderer-registry.ts +155 -0
  30. package/src/flow-renderer-resize-observer.ts +56 -0
  31. package/src/hooks/use-base-color.ts +26 -0
  32. package/src/index.ts +16 -0
  33. package/src/layer-vue-provide.ts +44 -0
  34. package/src/layers/flow-context-menu-layer.ts +153 -0
  35. package/src/layers/flow-debug-layer.ts +227 -0
  36. package/src/layers/flow-drag-layer.ts +413 -0
  37. package/src/layers/flow-labels-layer.ts +100 -0
  38. package/src/layers/flow-lines-layer.ts +111 -0
  39. package/src/layers/flow-nodes-content-layer.ts +155 -0
  40. package/src/layers/flow-nodes-transform-layer.ts +151 -0
  41. package/src/layers/flow-scroll-bar-layer.ts +401 -0
  42. package/src/layers/flow-scroll-limit-layer.ts +36 -0
  43. package/src/layers/flow-selector-bounds-layer.ts +191 -0
  44. package/src/layers/flow-selector-box-layer.ts +244 -0
  45. package/src/layers/index.ts +16 -0
  46. package/src/utils/element.ts +36 -0
  47. package/src/utils/find-selected-nodes.ts +80 -0
  48. package/src/utils/index.ts +7 -0
  49. package/src/utils/scroll-bar-events.ts +13 -0
  50. package/src/utils/scroll-limit.ts +58 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { Fragment, h, type VNode } from 'vue';
7
+ import { throttle } from 'lodash-es';
8
+ import { inject, injectable, optional } from 'inversify';
9
+ import { domUtils } from '@flowgram-vue/utils';
10
+ import {
11
+ FlowDocument,
12
+ FlowDocumentOptions,
13
+ FlowDocumentTransformerEntity,
14
+ FlowNodeEntity,
15
+ FlowNodeTransitionData,
16
+ FlowRendererStateEntity,
17
+ } from '@flowgram-vue/document';
18
+ import { Layer, observeEntity, observeEntityDatas, PlaygroundContainerFactory } from '@flowgram-vue/core';
19
+
20
+ import { wrapLayerRender } from '../layer-vue-provide';
21
+ import { getBaseColor } from '../hooks/use-base-color';
22
+ import { FlowRendererRegistry } from '../flow-renderer-registry';
23
+ import { createLabels } from '../components/LabelsRenderer';
24
+
25
+ @injectable()
26
+ export class FlowLabelsLayer extends Layer {
27
+ @inject(FlowDocument) readonly document: FlowDocument;
28
+
29
+ @inject(FlowRendererRegistry) readonly rendererRegistry: FlowRendererRegistry;
30
+
31
+ @inject(PlaygroundContainerFactory) readonly playgroundContainer: PlaygroundContainerFactory;
32
+
33
+ @optional()
34
+ @inject(FlowDocumentOptions)
35
+ readonly documentOptions?: FlowDocumentOptions;
36
+
37
+ node = domUtils.createDivWithClass('gedit-flow-labels-layer');
38
+
39
+ @observeEntity(FlowDocumentTransformerEntity)
40
+ readonly documentTransformer: FlowDocumentTransformerEntity;
41
+
42
+ @observeEntity(FlowRendererStateEntity)
43
+ readonly flowRenderState: FlowRendererStateEntity;
44
+
45
+ /**
46
+ * 监听 transition 变化
47
+ */
48
+ @observeEntityDatas(FlowNodeEntity, FlowNodeTransitionData)
49
+ _transitions: FlowNodeTransitionData[];
50
+
51
+ get transitions(): FlowNodeTransitionData[] {
52
+ return this.document.getRenderDatas<FlowNodeTransitionData>(FlowNodeTransitionData);
53
+ }
54
+
55
+ /**
56
+ * 监听缩放,目前采用整体缩放
57
+ * @param scale
58
+ */
59
+ onZoom(scale: number) {
60
+ this.node!.style.transform = `scale(${scale})`;
61
+ }
62
+
63
+ /**
64
+ * 可视区域变化
65
+ */
66
+ onViewportChange: ReturnType<typeof throttle> = throttle(() => {
67
+ this.render();
68
+ }, 100);
69
+
70
+ onReady() {
71
+ // 图层顺序调整:节点 > label > 线条
72
+ // 节点 z-index: 10
73
+ this.node.style.zIndex = '9';
74
+ }
75
+
76
+ /**
77
+ * 监听readonly和 disabled 状态 并刷新layer, 并刷新
78
+ */
79
+ onReadonlyOrDisabledChange() {
80
+ this.render();
81
+ }
82
+
83
+ render() {
84
+ const labels: VNode[] = [];
85
+ if (this.documentTransformer?.loading) return null;
86
+ this.documentTransformer?.refresh?.();
87
+ const { baseActivatedColor, baseColor } = getBaseColor(this.documentOptions);
88
+ const isViewportVisible = this.config.isViewportVisible.bind(this.config);
89
+ this.transitions.forEach((transition) => {
90
+ createLabels({
91
+ data: transition,
92
+ rendererRegistry: this.rendererRegistry,
93
+ isViewportVisible,
94
+ labelsSave: labels,
95
+ getLabelColor: (activated) => (activated ? baseActivatedColor : baseColor),
96
+ });
97
+ });
98
+ return wrapLayerRender(this.playgroundContainer, h(Fragment, null, labels));
99
+ }
100
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { h, type VNode } from 'vue';
7
+ import { groupBy, throttle } from 'lodash-es';
8
+ import { inject, injectable } from 'inversify';
9
+ import { domUtils } from '@flowgram-vue/utils';
10
+ import {
11
+ FlowDocument,
12
+ FlowDocumentTransformerEntity,
13
+ FlowNodeEntity,
14
+ FlowNodeTransitionData,
15
+ FlowRendererStateEntity,
16
+ FlowDragService,
17
+ } from '@flowgram-vue/document';
18
+ import { Layer, observeEntity, observeEntityDatas, PlaygroundContainerFactory } from '@flowgram-vue/core';
19
+
20
+ import { wrapLayerRender } from '../layer-vue-provide';
21
+ import { FlowRendererRegistry } from '../flow-renderer-registry';
22
+ import { createLines } from '../components/LinesRenderer';
23
+
24
+ @injectable()
25
+ export class FlowLinesLayer extends Layer {
26
+ @inject(FlowDocument) readonly document: FlowDocument;
27
+
28
+ @inject(FlowDragService)
29
+ protected readonly dragService: FlowDragService;
30
+
31
+ @inject(FlowRendererRegistry) readonly rendererRegistry: FlowRendererRegistry;
32
+
33
+ @inject(PlaygroundContainerFactory) readonly playgroundContainer: PlaygroundContainerFactory;
34
+
35
+ node = domUtils.createDivWithClass('gedit-flow-lines-layer');
36
+
37
+ @observeEntity(FlowDocumentTransformerEntity)
38
+ readonly documentTransformer: FlowDocumentTransformerEntity;
39
+
40
+ @observeEntity(FlowRendererStateEntity)
41
+ readonly flowRenderState: FlowRendererStateEntity;
42
+
43
+ /**
44
+ * 监听 transition 变化
45
+ */
46
+ @observeEntityDatas(FlowNodeEntity, FlowNodeTransitionData)
47
+ _transitions: FlowNodeTransitionData[];
48
+
49
+ get transitions(): FlowNodeTransitionData[] {
50
+ return this.document.getRenderDatas<FlowNodeTransitionData>(FlowNodeTransitionData);
51
+ }
52
+
53
+ /**
54
+ * 可视区域变化
55
+ */
56
+ onViewportChange: ReturnType<typeof throttle> = throttle(() => {
57
+ this.render();
58
+ }, 100);
59
+
60
+ onZoom() {
61
+ const svgContainer = this.node!.querySelector('svg.flow-lines-container')!;
62
+ svgContainer?.setAttribute?.('viewBox', this.viewBox);
63
+ }
64
+
65
+ onReady() {
66
+ this.node.style.zIndex = '1';
67
+ }
68
+
69
+ get viewBox(): string {
70
+ const ratio = 1000 / this.config.finalScale;
71
+ return `0 0 ${ratio} ${ratio}`;
72
+ }
73
+
74
+ render() {
75
+ const allLines: VNode[] = [];
76
+ const isViewportVisible = this.config.isViewportVisible.bind(this.config);
77
+ if (this.documentTransformer.loading) return null;
78
+ this.documentTransformer.refresh();
79
+
80
+ this.transitions.forEach((transition) => {
81
+ createLines({
82
+ data: transition,
83
+ rendererRegistry: this.rendererRegistry,
84
+ isViewportVisible,
85
+ linesSave: allLines,
86
+ dragService: this.dragService,
87
+ });
88
+ });
89
+
90
+ const { activateLines = [], normalLines = [] } = groupBy(allLines, (line) =>
91
+ line.props?.activated ? 'activateLines' : 'normalLines'
92
+ );
93
+ const resultLines = [...normalLines, ...activateLines];
94
+
95
+ return wrapLayerRender(
96
+ this.playgroundContainer,
97
+ h(
98
+ 'svg',
99
+ {
100
+ class: 'flow-lines-container',
101
+ width: '1000',
102
+ height: '1000',
103
+ overflow: 'visible',
104
+ viewBox: this.viewBox,
105
+ xmlns: 'http://www.w3.org/2000/svg',
106
+ },
107
+ resultLines
108
+ )
109
+ );
110
+ }
111
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { defineComponent, Fragment, h, onMounted, provide, Teleport, type Component } from 'vue';
7
+ import { inject, injectable } from 'inversify';
8
+ import { Cache, type CacheOriginItem, domUtils } from '@flowgram-vue/utils';
9
+ import {
10
+ FlowDocument,
11
+ FlowDocumentTransformerEntity,
12
+ FlowNodeEntity,
13
+ FlowNodeRenderData,
14
+ FlowNodeTransformData,
15
+ } from '@flowgram-vue/document';
16
+ import {
17
+ Layer,
18
+ observeEntity,
19
+ observeEntityDatas,
20
+ PlaygroundContainerFactory,
21
+ PlaygroundEntityContextKey,
22
+ } from '@flowgram-vue/core';
23
+
24
+ import { wrapLayerRender } from '../layer-vue-provide';
25
+ import { FlowRendererKey, FlowRendererRegistry } from '../flow-renderer-registry';
26
+
27
+ interface NodePortal extends CacheOriginItem {
28
+ id: string;
29
+ Portal: Component;
30
+ }
31
+
32
+ /**
33
+ * 渲染节点内容
34
+ */
35
+ @injectable()
36
+ export class FlowNodesContentLayer extends Layer {
37
+ @inject(FlowDocument) readonly document: FlowDocument;
38
+
39
+ @inject(FlowRendererRegistry) readonly rendererRegistry: FlowRendererRegistry;
40
+
41
+ @inject(PlaygroundContainerFactory) readonly playgroundContainer: PlaygroundContainerFactory;
42
+
43
+ @observeEntity(FlowDocumentTransformerEntity)
44
+ readonly documentTransformer: FlowDocumentTransformerEntity;
45
+
46
+ @observeEntityDatas(FlowNodeEntity, FlowNodeRenderData)
47
+ _renderStates: FlowNodeRenderData[];
48
+
49
+ get renderStatesVisible(): FlowNodeRenderData[] {
50
+ return this.document.getRenderDatas<FlowNodeRenderData>(FlowNodeRenderData, false);
51
+ }
52
+
53
+ private renderMemoCache = new WeakMap<any, Component>();
54
+
55
+ node = domUtils.createDivWithClass('gedit-flow-nodes-layer');
56
+
57
+ getPortalRenderer(data: FlowNodeRenderData): Component {
58
+ const meta = data.entity.getNodeMeta();
59
+ const renderer = this.rendererRegistry.getRendererComponent(
60
+ (meta.renderKey as FlowRendererKey) || FlowRendererKey.NODE_RENDER
61
+ );
62
+ const vueRenderer = renderer.renderer as Component;
63
+ let memoCache = this.renderMemoCache.get(vueRenderer);
64
+ if (!memoCache) {
65
+ memoCache = vueRenderer;
66
+ this.renderMemoCache.set(vueRenderer, memoCache);
67
+ }
68
+ return memoCache;
69
+ }
70
+
71
+ /**
72
+ * 监听缩放,目前采用整体缩放
73
+ * @param scale
74
+ */
75
+ onZoom(scale: number) {
76
+ this.node!.style.transform = `scale(${scale})`;
77
+ }
78
+
79
+ dispose(): void {
80
+ this.vuePortals.dispose();
81
+ super.dispose();
82
+ }
83
+
84
+ protected vuePortals = Cache.create<NodePortal, FlowNodeRenderData>(
85
+ (data?: FlowNodeRenderData) => {
86
+ const { node, entity } = data!;
87
+ const { config } = this;
88
+ const PortalRenderer = this.getPortalRenderer(data!);
89
+
90
+ const Portal = defineComponent({
91
+ name: 'FlowNodePortal',
92
+ setup() {
93
+ provide(PlaygroundEntityContextKey, entity);
94
+ onMounted(() => {
95
+ if (!entity.getNodeMeta().autoResizeDisable && node.clientWidth && node.clientHeight) {
96
+ const transform = entity.getData<FlowNodeTransformData>(FlowNodeTransformData);
97
+ if (transform)
98
+ transform.size = {
99
+ width: node.clientWidth,
100
+ height: node.clientHeight,
101
+ };
102
+ }
103
+ });
104
+ return () =>
105
+ h(Teleport, { to: node }, [
106
+ h(PortalRenderer, {
107
+ node: entity,
108
+ version: data?.version,
109
+ activated: data?.activated,
110
+ readonly: config.readonly,
111
+ disabled: config.disabled,
112
+ }),
113
+ ]);
114
+ },
115
+ });
116
+
117
+ return {
118
+ id: node.id || entity.id,
119
+ dispose: () => {
120
+ // TODO, 删除逻辑由 node 去控制了
121
+ },
122
+ Portal,
123
+ } as NodePortal;
124
+ }
125
+ );
126
+
127
+ onReady() {
128
+ this.node!.style.zIndex = '10';
129
+ }
130
+
131
+ /**
132
+ * 监听readonly和 disabled 状态 并刷新layer, 并刷新节点
133
+ */
134
+ onReadonlyOrDisabledChange() {
135
+ this.render();
136
+ }
137
+
138
+ getPortals(): NodePortal[] {
139
+ return this.vuePortals.getMoreByItems(this.renderStatesVisible);
140
+ }
141
+
142
+ render() {
143
+ if (this.documentTransformer.loading) return null;
144
+ this.documentTransformer.refresh();
145
+
146
+ return wrapLayerRender(
147
+ this.playgroundContainer,
148
+ h(
149
+ Fragment,
150
+ null,
151
+ this.getPortals().map((portal) => h(portal.Portal, { key: portal.id }))
152
+ )
153
+ );
154
+ }
155
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { inject, injectable } from 'inversify';
7
+ import { Cache, type Disposable, domUtils } from '@flowgram-vue/utils';
8
+ import {
9
+ FlowDocument,
10
+ FlowDocumentTransformerEntity,
11
+ FlowNodeEntity,
12
+ FlowNodeTransformData,
13
+ } from '@flowgram-vue/document';
14
+ import { Layer, observeEntity, observeEntityDatas } from '@flowgram-vue/core';
15
+ // import { throttle } from 'lodash-es'
16
+
17
+ import { FlowRendererResizeObserver } from '../flow-renderer-resize-observer';
18
+
19
+ interface TransformRenderCache {
20
+ updateBounds(): void;
21
+ }
22
+
23
+ export interface FlowNodesTransformLayerOptions {
24
+ renderElement?: HTMLElement | (() => HTMLElement | undefined);
25
+ }
26
+
27
+ /**
28
+ * 渲染节点位置
29
+ */
30
+ @injectable()
31
+ export class FlowNodesTransformLayer extends Layer<FlowNodesTransformLayerOptions> {
32
+ @inject(FlowDocument) readonly document: FlowDocument;
33
+
34
+ @inject(FlowRendererResizeObserver)
35
+ readonly resizeObserver: FlowRendererResizeObserver;
36
+
37
+ @observeEntity(FlowDocumentTransformerEntity)
38
+ readonly documentTransformer: FlowDocumentTransformerEntity;
39
+
40
+ @observeEntityDatas(FlowNodeEntity, FlowNodeTransformData)
41
+ _transforms: FlowNodeTransformData[];
42
+
43
+ node = domUtils.createDivWithClass('gedit-flow-nodes-layer');
44
+
45
+ get transformVisibles(): FlowNodeTransformData[] {
46
+ return this.document.getRenderDatas<FlowNodeTransformData>(FlowNodeTransformData, false);
47
+ }
48
+
49
+ /**
50
+ * 监听缩放,目前采用整体缩放
51
+ * @param scale
52
+ */
53
+ onZoom(scale: number) {
54
+ this.node!.style.transform = `scale(${scale})`;
55
+ }
56
+
57
+ dispose(): void {
58
+ this.renderCache.dispose();
59
+ super.dispose();
60
+ }
61
+
62
+ // onViewportChange() {
63
+ // this.throttleUpdate()
64
+ // }
65
+
66
+ // throttleUpdate = throttle(() => {
67
+ // this.renderCache.getFromCache().forEach((cache) => cache.updateBounds())
68
+ // }, 100)
69
+
70
+ protected renderCache = Cache.create<TransformRenderCache, FlowNodeTransformData>(
71
+ (transform?: FlowNodeTransformData) => {
72
+ const { renderState } = transform!;
73
+ const { node } = renderState;
74
+ const { entity } = transform!;
75
+ node.id = entity.id;
76
+ let resizeDispose: Disposable | undefined;
77
+ const append = () => {
78
+ if (resizeDispose) return;
79
+ // 监听 dom 节点的大小变化
80
+ this.renderElement.appendChild(node);
81
+ if (!entity.getNodeMeta().autoResizeDisable) {
82
+ resizeDispose = this.resizeObserver.observe(node, transform!);
83
+ }
84
+ };
85
+ const dispose = () => {
86
+ if (!resizeDispose) return;
87
+ // 脱离文档流,但是 react 组件会保留
88
+ if (node.parentElement) {
89
+ this.renderElement.removeChild(node);
90
+ }
91
+ resizeDispose.dispose();
92
+ resizeDispose = undefined;
93
+ };
94
+ append();
95
+ return {
96
+ dispose,
97
+ updateBounds: () => {
98
+ const { bounds } = transform!;
99
+ // 保留2位小数
100
+ const rawX: number = parseFloat(node.style.left);
101
+ const rawY: number = parseFloat(node.style.top);
102
+ if (!this.isCoordEqual(rawX, bounds.x) || !this.isCoordEqual(rawY, bounds.y)) {
103
+ node.style.left = `${bounds.x}px`;
104
+ node.style.top = `${bounds.y}px`;
105
+ }
106
+ },
107
+ };
108
+ }
109
+ );
110
+
111
+ private isCoordEqual(a: number, b: number) {
112
+ const browserCoordEpsilon = 0.05; // 浏览器处理坐标的精度误差: 两位小数四舍五入
113
+ return Math.abs(a - b) < browserCoordEpsilon;
114
+ }
115
+
116
+ onReady() {
117
+ this.node!.style.zIndex = '10';
118
+ }
119
+
120
+ get visibeBounds() {
121
+ return this.transformVisibles.map((transform) => transform.bounds);
122
+ }
123
+
124
+ /**
125
+ * 更新节点的 bounds 数据
126
+ */
127
+ updateNodesBounds() {
128
+ this.renderCache
129
+ .getMoreByItems(this.transformVisibles)
130
+ .forEach((render) => render.updateBounds());
131
+ }
132
+
133
+ autorun() {
134
+ // 更新节点偏移数据 O(n) TODO 这个更新会从 render 里移除改成自动触发
135
+ if (this.documentTransformer.loading) return;
136
+ this.documentTransformer.refresh();
137
+ this.updateNodesBounds();
138
+ }
139
+
140
+ private get renderElement(): HTMLElement {
141
+ if (typeof this.options.renderElement === 'function') {
142
+ const element = this.options.renderElement();
143
+ if (element) {
144
+ return element;
145
+ }
146
+ } else if (typeof this.options.renderElement !== 'undefined') {
147
+ return this.options.renderElement as HTMLElement;
148
+ }
149
+ return this.node;
150
+ }
151
+ }