@yh-ui/flow 1.0.7 → 1.0.9
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/LICENSE +1 -1
- package/README.md +37 -203
- package/dist/Flow.vue +5 -1
- package/dist/components/NodeEditPanel.d.vue.ts +1 -0
- package/dist/components/NodeEditPanel.vue +14 -2
- package/dist/components/NodeEditPanel.vue.d.ts +1 -0
- package/dist/components/nodes/NodeResizer.d.vue.ts +1 -0
- package/dist/components/nodes/NodeResizer.vue +14 -1
- package/dist/components/nodes/NodeResizer.vue.d.ts +1 -0
- package/dist/components/nodes/NodeToolbar.d.vue.ts +2 -0
- package/dist/components/nodes/NodeToolbar.vue +14 -1
- package/dist/components/nodes/NodeToolbar.vue.d.ts +2 -0
- package/dist/renderer/EdgeRenderer.d.vue.ts +1 -0
- package/dist/renderer/EdgeRenderer.vue +4 -6
- package/dist/renderer/EdgeRenderer.vue.d.ts +1 -0
- package/dist/renderer/NodeRenderer.d.vue.ts +1 -0
- package/dist/renderer/NodeRenderer.vue +4 -1
- package/dist/renderer/NodeRenderer.vue.d.ts +1 -0
- package/package.json +5 -4
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,234 +1,68 @@
|
|
|
1
1
|
# @yh-ui/flow
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
<img src="https://raw.githubusercontent.com/1079161148/yh-ui/main/docs/public/logo.svg" width="100" height="100" alt="YH-UI Logo">
|
|
5
|
-
</p>
|
|
3
|
+
YH-UI 的流程图和节点编辑包,适合搭建工作流编排、审批流、AI Agent 流程、BPMN 草图、数据处理链路和可视化配置平台。
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
[Documentation](https://1079161148.github.io/yh-ui/flow/) | [GitHub](https://github.com/1079161148/yh-ui)
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
可视化流程图组件 · 拖拽连线 · 自定义节点 · 内置布局算法 · 完整 SSR 支持
|
|
11
|
-
</p>
|
|
7
|
+
## Highlights
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
</a>
|
|
20
|
-
<a href="https://github.com/1079161148/yh-ui/blob/main/LICENSE">
|
|
21
|
-
<img src="https://img.shields.io/npm/l/@yh-ui/flow.svg?style=flat-square" alt="license">
|
|
22
|
-
</a>
|
|
23
|
-
</p>
|
|
9
|
+
- 可视化流程画布:节点、连线、缩放、拖拽、选择、视口适配和画布交互由核心组件统一管理。
|
|
10
|
+
- 内置节点类型:基础节点、输入节点、输出节点、分组节点、菱形节点、数据库节点,以及 BPMN 和 AI Workflow 节点库。
|
|
11
|
+
- 多种连线:基础线、平滑线、阶梯线、贝塞尔线和数据流连线,适合表达不同业务语义。
|
|
12
|
+
- 编辑器辅助组件:节点编辑面板、连线编辑面板、AI 节点编辑面板、MiniMap、Controls、Background 可按需组合。
|
|
13
|
+
- 可扩展架构:导出 types、core、utils、plugins,方便封装自己的节点库、连线策略和编辑侧栏。
|
|
14
|
+
- Vue 和 TypeScript 原生体验:节点可使用 Vue 组件表达,流程数据有类型约束。
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
## ✨ 特性
|
|
28
|
-
|
|
29
|
-
- 🚀 **高性能渲染** — 基于 SVG + Canvas 混合渲染,轻松支持 1000+ 节点
|
|
30
|
-
- 🎯 **拖拽连线** — 直觉式交互,支持多种连线类型(直线、折线、贝塞尔曲线)
|
|
31
|
-
- 🧩 **自定义节点** — 完全自定义节点外观,支持复杂 Vue 组件作为节点
|
|
32
|
-
- 📐 **内置布局算法** — 支持 DAG 有向无环图自动布局(dagre)
|
|
33
|
-
- 🔌 **插件化扩展** — 迷你地图、工具栏、右键菜单等均为可选插件
|
|
34
|
-
- 🌐 **SSR 安全** — 服务端渲染兼容,无 `window`/`document` 依赖问题
|
|
35
|
-
- 🔒 **完整 TypeScript** — 所有 API 均有精确类型定义
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## 📦 安装
|
|
16
|
+
## Install
|
|
40
17
|
|
|
41
18
|
```bash
|
|
42
|
-
# pnpm(推荐)
|
|
43
19
|
pnpm add @yh-ui/flow
|
|
44
|
-
|
|
45
|
-
# npm
|
|
46
|
-
npm install @yh-ui/flow
|
|
47
20
|
```
|
|
48
21
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
## 🔨 快速开始
|
|
52
|
-
|
|
53
|
-
### 基础流程图
|
|
22
|
+
## Basic Usage
|
|
54
23
|
|
|
55
24
|
```vue
|
|
56
25
|
<script setup lang="ts">
|
|
57
|
-
import {
|
|
58
|
-
import '@yh-ui/flow
|
|
59
|
-
import type { FlowNode, FlowEdge } from '@yh-ui/flow'
|
|
26
|
+
import { Flow, Controls, FlowBackground, Minimap } from '@yh-ui/flow'
|
|
27
|
+
import type { FlowEdge, FlowNode } from '@yh-ui/flow'
|
|
60
28
|
|
|
61
29
|
const nodes: FlowNode[] = [
|
|
62
|
-
{ id: '
|
|
63
|
-
{ id: '
|
|
64
|
-
{ id: '
|
|
65
|
-
{ id: '4', type: 'output', label: '完成', position: { x: 250, y: 200 } }
|
|
30
|
+
{ id: 'start', type: 'input', label: 'Start', position: { x: 80, y: 80 } },
|
|
31
|
+
{ id: 'task', label: 'Process order', position: { x: 280, y: 80 } },
|
|
32
|
+
{ id: 'done', type: 'output', label: 'Done', position: { x: 500, y: 80 } }
|
|
66
33
|
]
|
|
67
34
|
|
|
68
35
|
const edges: FlowEdge[] = [
|
|
69
|
-
{ id: '
|
|
70
|
-
{ id: '
|
|
71
|
-
{ id: 'e2-4', source: '2', target: '4' },
|
|
72
|
-
{ id: 'e3-4', source: '3', target: '4' }
|
|
36
|
+
{ id: 'e-start-task', source: 'start', target: 'task' },
|
|
37
|
+
{ id: 'e-task-done', source: 'task', target: 'done' }
|
|
73
38
|
]
|
|
74
39
|
</script>
|
|
75
40
|
|
|
76
41
|
<template>
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<!-- 背景网格 -->
|
|
83
|
-
<YhFlowBackground variant="dots" />
|
|
84
|
-
</YhFlow>
|
|
85
|
-
</template>
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### 自定义节点
|
|
89
|
-
|
|
90
|
-
```vue
|
|
91
|
-
<script setup lang="ts">
|
|
92
|
-
// CustomNode.vue — 自定义节点组件
|
|
93
|
-
import { YhFlowHandle } from '@yh-ui/flow'
|
|
94
|
-
defineProps<{ data: { label: string; status: 'success' | 'error' | 'pending' } }>()
|
|
95
|
-
</script>
|
|
96
|
-
|
|
97
|
-
<template>
|
|
98
|
-
<div class="custom-node" :class="`status-${data.status}`">
|
|
99
|
-
<!-- 输入连接点 -->
|
|
100
|
-
<YhFlowHandle type="target" position="top" />
|
|
101
|
-
<div class="node-content">
|
|
102
|
-
<span class="status-icon">{{ data.status === 'success' ? '✅' : '⏳' }}</span>
|
|
103
|
-
<span>{{ data.label }}</span>
|
|
104
|
-
</div>
|
|
105
|
-
<!-- 输出连接点 -->
|
|
106
|
-
<YhFlowHandle type="source" position="bottom" />
|
|
107
|
-
</div>
|
|
42
|
+
<Flow :nodes="nodes" :edges="edges" fit-view style="height: 560px">
|
|
43
|
+
<Minimap />
|
|
44
|
+
<Controls />
|
|
45
|
+
<FlowBackground />
|
|
46
|
+
</Flow>
|
|
108
47
|
</template>
|
|
109
48
|
```
|
|
110
49
|
|
|
111
|
-
|
|
112
|
-
<script setup lang="ts">
|
|
113
|
-
import { YhFlow } from '@yh-ui/flow'
|
|
114
|
-
import CustomNode from './CustomNode.vue'
|
|
115
|
-
|
|
116
|
-
// 注册自定义节点类型
|
|
117
|
-
const nodeTypes = { custom: CustomNode }
|
|
118
|
-
|
|
119
|
-
const nodes = [
|
|
120
|
-
{
|
|
121
|
-
id: '1',
|
|
122
|
-
type: 'custom',
|
|
123
|
-
data: { label: '数据采集', status: 'success' },
|
|
124
|
-
position: { x: 0, y: 0 }
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
id: '2',
|
|
128
|
-
type: 'custom',
|
|
129
|
-
data: { label: '数据处理', status: 'pending' },
|
|
130
|
-
position: { x: 200, y: 100 }
|
|
131
|
-
}
|
|
132
|
-
]
|
|
133
|
-
</script>
|
|
134
|
-
|
|
135
|
-
<template>
|
|
136
|
-
<YhFlow :nodes="nodes" :node-types="nodeTypes" style="height: 500px" />
|
|
137
|
-
</template>
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### 自动布局(dagre)
|
|
141
|
-
|
|
142
|
-
```ts
|
|
143
|
-
import { useFlowLayout } from '@yh-ui/flow'
|
|
144
|
-
import type { FlowNode, FlowEdge } from '@yh-ui/flow'
|
|
145
|
-
|
|
146
|
-
const { autoLayout } = useFlowLayout()
|
|
147
|
-
|
|
148
|
-
// 对已有节点和连线自动计算坐标
|
|
149
|
-
const { nodes: layoutedNodes, edges: layoutedEdges } = autoLayout(nodes, edges, {
|
|
150
|
-
direction: 'TB', // 'TB'(上→下)| 'LR'(左→右)
|
|
151
|
-
nodeSpacing: 50,
|
|
152
|
-
rankSpacing: 100
|
|
153
|
-
})
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
## 📚 组件 API
|
|
159
|
-
|
|
160
|
-
### `YhFlow` 主组件
|
|
161
|
-
|
|
162
|
-
| 属性 | 类型 | 默认值 | 说明 |
|
|
163
|
-
| ----------------------- | --------------------------- | ------------- | ------------------ |
|
|
164
|
-
| `nodes` | `FlowNode[]` | `[]` | 节点数据 |
|
|
165
|
-
| `edges` | `FlowEdge[]` | `[]` | 连线数据 |
|
|
166
|
-
| `nodeTypes` | `Record<string, Component>` | `{}` | 自定义节点类型 |
|
|
167
|
-
| `edgeTypes` | `Record<string, Component>` | `{}` | 自定义连线类型 |
|
|
168
|
-
| `fitView` | `boolean` | `false` | 初始化时自适应视图 |
|
|
169
|
-
| `minZoom` | `number` | `0.5` | 最小缩放比例 |
|
|
170
|
-
| `maxZoom` | `number` | `2` | 最大缩放比例 |
|
|
171
|
-
| `snapToGrid` | `boolean` | `false` | 是否开启网格对齐 |
|
|
172
|
-
| `snapGrid` | `[number, number]` | `[15, 15]` | 网格对齐尺寸 |
|
|
173
|
-
| `selectable` | `boolean` | `true` | 节点是否可选中 |
|
|
174
|
-
| `multiSelectionKeyCode` | `string` | `'Meta'` | 多选按键 |
|
|
175
|
-
| `deleteKeyCode` | `string` | `'Backspace'` | 删除按键 |
|
|
176
|
-
|
|
177
|
-
### 事件
|
|
178
|
-
|
|
179
|
-
| 事件 | 说明 |
|
|
180
|
-
| --------------- | -------------------------- |
|
|
181
|
-
| `@node-click` | 点击节点 |
|
|
182
|
-
| `@edge-click` | 点击连线 |
|
|
183
|
-
| `@nodes-change` | 节点变更(移动/选中/删除) |
|
|
184
|
-
| `@edges-change` | 连线变更 |
|
|
185
|
-
| `@connect` | 建立新连接 |
|
|
186
|
-
| `@pane-click` | 点击画布空白区 |
|
|
187
|
-
|
|
188
|
-
### 插槽
|
|
189
|
-
|
|
190
|
-
| 插槽 | 说明 |
|
|
191
|
-
| -------------- | ------------------------------------ |
|
|
192
|
-
| `default` | 插入插件组件(MiniMap、Controls 等) |
|
|
193
|
-
| `node-toolbar` | 节点工具栏 |
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## 🔌 可用插件
|
|
198
|
-
|
|
199
|
-
```vue
|
|
200
|
-
<YhFlow>
|
|
201
|
-
<!-- 迷你地图 -->
|
|
202
|
-
<YhFlowMiniMap position="bottom-right" />
|
|
203
|
-
|
|
204
|
-
<!-- 缩放控制栏 -->
|
|
205
|
-
<YhFlowControls position="bottom-left" :show-fit-view="true" />
|
|
206
|
-
|
|
207
|
-
<!-- 背景 -->
|
|
208
|
-
<YhFlowBackground variant="dots" gap="16" color="#aaa" />
|
|
209
|
-
</YhFlow>
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
## ⚠️ 注意事项
|
|
215
|
-
|
|
216
|
-
- **容器需要固定高度**:`YhFlow` 需要父容器有明确的 `height`,否则无法正确渲染
|
|
217
|
-
- **SSR**:在 Nuxt 3 中需使用 `<ClientOnly>` 包裹或设置 `ssr: false`
|
|
218
|
-
```vue
|
|
219
|
-
<ClientOnly>
|
|
220
|
-
<YhFlow :nodes="nodes" :edges="edges" style="height: 600px" />
|
|
221
|
-
</ClientOnly>
|
|
222
|
-
```
|
|
223
|
-
- **样式**:需手动引入 `@yh-ui/flow/dist/style.css`,或通过 `@yh-ui/nuxt` 自动注入
|
|
50
|
+
## Built-In Exports
|
|
224
51
|
|
|
225
|
-
|
|
52
|
+
| Area | Exports |
|
|
53
|
+
| ------- | ----------------------------------------------------------------------------------------------- |
|
|
54
|
+
| Canvas | `Flow`, `Minimap`, `Controls`, `FlowBackground` |
|
|
55
|
+
| Nodes | `BaseNode`, `InputNode`, `OutputNode`, `GroupNode`, `CustomNode`, `DiamondNode`, `DatabaseNode` |
|
|
56
|
+
| Edges | `BaseEdge`, `SmoothEdge`, `StepEdge`, `BezierEdge`, `DataFlowEdge` |
|
|
57
|
+
| Editing | `NodeEditPanel`, `EdgeEditPanel`, `AiNodeEditPanel`, `NodeToolbar`, `NodeResizer` |
|
|
58
|
+
| Presets | BPMN nodes, AI workflow nodes, registration helpers |
|
|
226
59
|
|
|
227
|
-
##
|
|
60
|
+
## Tips
|
|
228
61
|
|
|
229
|
-
-
|
|
230
|
-
-
|
|
62
|
+
- Give the canvas an explicit height; otherwise the editor cannot calculate the viewport.
|
|
63
|
+
- In Nuxt or SSR pages, render browser-only editing surfaces inside `<ClientOnly>` when they depend on pointer or canvas APIs.
|
|
64
|
+
- Keep node data serializable if you plan to persist workflows or send them to a backend.
|
|
231
65
|
|
|
232
|
-
##
|
|
66
|
+
## License
|
|
233
67
|
|
|
234
|
-
MIT
|
|
68
|
+
MIT
|
package/dist/Flow.vue
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
<div class="yh-flow__content" :style="contentStyle">
|
|
20
20
|
<!-- Edges -->
|
|
21
21
|
<EdgeRenderer
|
|
22
|
+
:flow-id="flowDomId"
|
|
22
23
|
:edges="edgesRef || []"
|
|
23
24
|
:nodes="nodesRef || []"
|
|
24
25
|
:edge-types="edgeTypes"
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
|
|
45
46
|
<!-- Nodes -->
|
|
46
47
|
<NodeRenderer
|
|
48
|
+
:flow-id="flowDomId"
|
|
47
49
|
:nodes="visibleNodes || []"
|
|
48
50
|
:node-types="nodeTypes"
|
|
49
51
|
:transform="viewportRef"
|
|
@@ -98,6 +100,7 @@
|
|
|
98
100
|
<!-- 普通节点使用基础编辑面板 -->
|
|
99
101
|
<NodeEditPanel
|
|
100
102
|
v-else
|
|
103
|
+
:flow-id="flowDomId"
|
|
101
104
|
:node="editingNode"
|
|
102
105
|
:visible="showNodeEditPanel"
|
|
103
106
|
@update="handleNodeEditUpdate"
|
|
@@ -116,7 +119,7 @@
|
|
|
116
119
|
</template>
|
|
117
120
|
|
|
118
121
|
<script setup>
|
|
119
|
-
import { ref, computed, watch, onMounted, onBeforeUnmount, shallowRef } from "vue";
|
|
122
|
+
import { ref, computed, watch, onMounted, onBeforeUnmount, shallowRef, useId } from "vue";
|
|
120
123
|
if (typeof window !== "undefined") window.__YH_FLOW_VERSION__ = "1.0.1";
|
|
121
124
|
import EdgeRenderer from "./renderer/EdgeRenderer.vue";
|
|
122
125
|
import EdgeHandlesRenderer from "./renderer/EdgeHandlesRenderer.vue";
|
|
@@ -154,6 +157,7 @@ import {
|
|
|
154
157
|
import { provideFlowContext } from "./core/FlowContext";
|
|
155
158
|
import { createEventBus } from "./utils/event-bus";
|
|
156
159
|
import { PluginManager } from "./plugins/plugin";
|
|
160
|
+
const flowDomId = `yh-flow-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
157
161
|
const validateConnection = (sourceNode, targetNode, connection) => {
|
|
158
162
|
const basicResult = isValidConnection(sourceNode, targetNode, connection);
|
|
159
163
|
if (!basicResult.isValid) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, watch } from "vue";
|
|
3
3
|
const props = defineProps({
|
|
4
|
+
flowId: { type: String, required: false },
|
|
4
5
|
node: { type: [Object, null], required: true },
|
|
5
6
|
visible: { type: Boolean, required: true }
|
|
6
7
|
});
|
|
@@ -9,6 +10,17 @@ const localNode = ref({
|
|
|
9
10
|
label: "",
|
|
10
11
|
description: ""
|
|
11
12
|
});
|
|
13
|
+
const escapeSelectorValue = (value) => {
|
|
14
|
+
return CSS.escape ? CSS.escape(value) : value.replace(/["\\]/g, "\\$&");
|
|
15
|
+
};
|
|
16
|
+
const getNodeElement = (nodeId) => {
|
|
17
|
+
if (typeof document === "undefined") return null;
|
|
18
|
+
if (props.flowId) {
|
|
19
|
+
const scoped = document.getElementById(`${props.flowId}-node-${nodeId}`);
|
|
20
|
+
if (scoped) return scoped;
|
|
21
|
+
}
|
|
22
|
+
return document.querySelector(`[data-node-id="${escapeSelectorValue(nodeId)}"]`);
|
|
23
|
+
};
|
|
12
24
|
watch(
|
|
13
25
|
() => props.node,
|
|
14
26
|
(node) => {
|
|
@@ -20,8 +32,8 @@ watch(
|
|
|
20
32
|
description: data.description || data.desc || "",
|
|
21
33
|
labelColor: node.labelColor || "#303133",
|
|
22
34
|
descriptionColor: node.descriptionColor || "#909399",
|
|
23
|
-
width: node.width ||
|
|
24
|
-
height: node.height ||
|
|
35
|
+
width: node.width || getNodeElement(node.id)?.offsetWidth || 150,
|
|
36
|
+
height: node.height || getNodeElement(node.id)?.offsetHeight || 40,
|
|
25
37
|
type: node.type
|
|
26
38
|
};
|
|
27
39
|
}
|
|
@@ -16,6 +16,7 @@ import { computed } from "vue";
|
|
|
16
16
|
import { useFlowContext } from "../../core/FlowContext";
|
|
17
17
|
const props = defineProps({
|
|
18
18
|
nodeId: { type: String, required: true },
|
|
19
|
+
flowId: { type: String, required: false },
|
|
19
20
|
selected: { type: Boolean, required: false },
|
|
20
21
|
minWidth: { type: Number, required: false },
|
|
21
22
|
minHeight: { type: Number, required: false },
|
|
@@ -25,6 +26,18 @@ const flow = useFlowContext();
|
|
|
25
26
|
const emit = defineEmits(["resize", "resizeStart", "resizeEnd"]);
|
|
26
27
|
const isVisible = computed(() => props.selected);
|
|
27
28
|
const handles = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
|
|
29
|
+
const escapeSelectorValue = (value) => {
|
|
30
|
+
return CSS.escape ? CSS.escape(value) : value.replace(/["\\]/g, "\\$&");
|
|
31
|
+
};
|
|
32
|
+
const getNodeElement = () => {
|
|
33
|
+
if (props.flowId) {
|
|
34
|
+
const scoped = document.getElementById(`${props.flowId}-node-${props.nodeId}`);
|
|
35
|
+
if (scoped) return scoped;
|
|
36
|
+
}
|
|
37
|
+
return document.querySelector(
|
|
38
|
+
`[data-node-id="${escapeSelectorValue(props.nodeId)}"]`
|
|
39
|
+
);
|
|
40
|
+
};
|
|
28
41
|
let startX = 0;
|
|
29
42
|
let startY = 0;
|
|
30
43
|
let lastX = 0;
|
|
@@ -39,7 +52,7 @@ const onResizeStart = (event, handle) => {
|
|
|
39
52
|
lastX = event.clientX;
|
|
40
53
|
lastY = event.clientY;
|
|
41
54
|
currentHandle = handle;
|
|
42
|
-
const nodeEl =
|
|
55
|
+
const nodeEl = getNodeElement();
|
|
43
56
|
if (!nodeEl) return;
|
|
44
57
|
startWidth = nodeEl.offsetWidth;
|
|
45
58
|
startHeight = nodeEl.offsetHeight;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Position } from '../../types';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
nodeId: string;
|
|
4
|
+
flowId?: string;
|
|
4
5
|
selected?: boolean;
|
|
5
6
|
position?: Position;
|
|
6
7
|
offset?: number;
|
|
@@ -20,6 +21,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {
|
|
|
20
21
|
onCopy?: (() => any) | undefined;
|
|
21
22
|
}>, {
|
|
22
23
|
position: Position;
|
|
24
|
+
flowId: string;
|
|
23
25
|
offset: number;
|
|
24
26
|
teleportTo: string;
|
|
25
27
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import { computed, ref, onMounted, onBeforeUnmount, watch } from "vue";
|
|
24
24
|
const props = defineProps({
|
|
25
25
|
nodeId: { type: String, required: true },
|
|
26
|
+
flowId: { type: String, required: false, default: void 0 },
|
|
26
27
|
selected: { type: Boolean, required: false },
|
|
27
28
|
position: { type: String, required: false, default: "top" },
|
|
28
29
|
offset: { type: Number, required: false, default: 10 },
|
|
@@ -40,9 +41,21 @@ const toolbarStyle = ref({
|
|
|
40
41
|
});
|
|
41
42
|
const teleportTarget = computed(() => props.teleportTo);
|
|
42
43
|
let rafId = 0;
|
|
44
|
+
const escapeSelectorValue = (value) => {
|
|
45
|
+
return CSS.escape ? CSS.escape(value) : value.replace(/["\\]/g, "\\$&");
|
|
46
|
+
};
|
|
47
|
+
const getNodeElement = () => {
|
|
48
|
+
if (props.flowId) {
|
|
49
|
+
const scoped = document.getElementById(`${props.flowId}-node-${props.nodeId}`);
|
|
50
|
+
if (scoped) return scoped;
|
|
51
|
+
}
|
|
52
|
+
return document.querySelector(
|
|
53
|
+
`[data-node-id="${escapeSelectorValue(props.nodeId)}"]`
|
|
54
|
+
);
|
|
55
|
+
};
|
|
43
56
|
const updatePosition = () => {
|
|
44
57
|
if (!isVisible.value) return;
|
|
45
|
-
const nodeEl =
|
|
58
|
+
const nodeEl = getNodeElement();
|
|
46
59
|
if (!nodeEl) {
|
|
47
60
|
rafId = requestAnimationFrame(updatePosition);
|
|
48
61
|
return;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Position } from '../../types';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
nodeId: string;
|
|
4
|
+
flowId?: string;
|
|
4
5
|
selected?: boolean;
|
|
5
6
|
position?: Position;
|
|
6
7
|
offset?: number;
|
|
@@ -20,6 +21,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {
|
|
|
20
21
|
onCopy?: (() => any) | undefined;
|
|
21
22
|
}>, {
|
|
22
23
|
position: Position;
|
|
24
|
+
flowId: string;
|
|
23
25
|
offset: number;
|
|
24
26
|
teleportTo: string;
|
|
25
27
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -14,11 +14,7 @@
|
|
|
14
14
|
>
|
|
15
15
|
<defs>
|
|
16
16
|
<!-- Dynamic masks to create a true gap in the line behind the label -->
|
|
17
|
-
<mask
|
|
18
|
-
v-for="ed in edgeData"
|
|
19
|
-
:key="`mask-${ed.edge.id}`"
|
|
20
|
-
:id="`yh-mask-${ed.edge.id.replace(/\s/g, '')}`"
|
|
21
|
-
>
|
|
17
|
+
<mask v-for="ed in edgeData" :key="`mask-${ed.edge.id}`" :id="getMaskId(ed.edge.id)">
|
|
22
18
|
<rect x="-5000" y="-5000" width="10000" height="10000" fill="white" />
|
|
23
19
|
<rect
|
|
24
20
|
:x="ed.labelX - ed.labelWidth / 2 - 4"
|
|
@@ -75,7 +71,7 @@
|
|
|
75
71
|
'yh-flow-edge-path': true,
|
|
76
72
|
'is-animated': ed.edge.animated
|
|
77
73
|
}"
|
|
78
|
-
:mask="ed.edge.label ? `url(
|
|
74
|
+
:mask="ed.edge.label ? `url(#${getMaskId(ed.edge.id)})` : void 0"
|
|
79
75
|
:style="{
|
|
80
76
|
pointerEvents: 'none',
|
|
81
77
|
transition: 'stroke 0.2s, stroke-width 0.2s',
|
|
@@ -117,6 +113,7 @@ import { computed } from "vue";
|
|
|
117
113
|
import { getEdgePath, getEdgeCenter, getHandlePosition } from "../utils/edge";
|
|
118
114
|
import { getCustomEdge } from "../utils/custom-types";
|
|
119
115
|
const props = defineProps({
|
|
116
|
+
flowId: { type: String, required: true },
|
|
120
117
|
edges: { type: Array, required: true },
|
|
121
118
|
nodes: { type: Array, required: true },
|
|
122
119
|
edgeTypes: { type: Object, required: false },
|
|
@@ -130,6 +127,7 @@ const getComponent = (type) => {
|
|
|
130
127
|
}
|
|
131
128
|
return getCustomEdge(type || "default")?.component;
|
|
132
129
|
};
|
|
130
|
+
const getMaskId = (edgeId) => `${props.flowId}-yh-mask-${edgeId.replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
133
131
|
const emit = defineEmits(["edge-click", "edge-dblclick", "edge-contextmenu", "edge-update-start"]);
|
|
134
132
|
const nodeMap = computed(() => {
|
|
135
133
|
const m = /* @__PURE__ */ new Map();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<div
|
|
4
4
|
v-for="node in visibleNodes"
|
|
5
5
|
:key="node.id"
|
|
6
|
-
:id="
|
|
6
|
+
:id="getNodeDomId(node.id)"
|
|
7
7
|
class="yh-flow-node"
|
|
8
8
|
:class="{
|
|
9
9
|
'is-selected': node.selected,
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
[`type-${node.type}`]: true
|
|
13
13
|
}"
|
|
14
14
|
:style="getNodeStyle(node)"
|
|
15
|
+
:data-node-id="node.id"
|
|
15
16
|
@mousedown="handleNodeMouseDown($event, node)"
|
|
16
17
|
@click="handleNodeClick($event, node)"
|
|
17
18
|
@dblclick="handleNodeDblClick($event, node)"
|
|
@@ -68,6 +69,7 @@ import DiamondNode from "../components/nodes/DiamondNode.vue";
|
|
|
68
69
|
import DatabaseNode from "../components/nodes/DatabaseNode.vue";
|
|
69
70
|
const props = defineProps({
|
|
70
71
|
nodes: { type: Array, required: true },
|
|
72
|
+
flowId: { type: String, required: true },
|
|
71
73
|
nodeTypes: { type: Object, required: false, default: () => ({}) },
|
|
72
74
|
transform: { type: Object, required: true },
|
|
73
75
|
draggable: { type: Boolean, required: false, default: true },
|
|
@@ -86,6 +88,7 @@ const getComponent = (type) => {
|
|
|
86
88
|
const visibleNodes = computed(() => {
|
|
87
89
|
return props.nodes.filter((n) => !n.hidden);
|
|
88
90
|
});
|
|
91
|
+
const getNodeDomId = (nodeId) => `${props.flowId}-node-${nodeId}`;
|
|
89
92
|
const getNodeStyle = (node) => {
|
|
90
93
|
const width = node.width || 150;
|
|
91
94
|
const height = node.height || 40;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yh-ui/flow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "YH-UI High-performance Flow Chart Component",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -17,14 +17,15 @@
|
|
|
17
17
|
"types": "./dist/*.d.ts",
|
|
18
18
|
"import": "./dist/*.mjs",
|
|
19
19
|
"require": "./dist/*.cjs"
|
|
20
|
-
}
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
21
22
|
},
|
|
22
23
|
"files": [
|
|
23
24
|
"dist"
|
|
24
25
|
],
|
|
25
26
|
"dependencies": {
|
|
26
|
-
"@yh-ui/utils": "^1.0.
|
|
27
|
-
"@yh-ui/hooks": "^1.0.
|
|
27
|
+
"@yh-ui/utils": "^1.0.9",
|
|
28
|
+
"@yh-ui/hooks": "^1.0.9"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"vue": "^3.5.27",
|