@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024-present YH-UI Team
3
+ Copyright (c) 2026 YH-UI Team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,234 +1,68 @@
1
1
  # @yh-ui/flow
2
2
 
3
- <p align="center">
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
- <h3 align="center">YH-UI Flow — 高性能流程图 & 节点编辑器</h3>
5
+ [Documentation](https://1079161148.github.io/yh-ui/flow/) | [GitHub](https://github.com/1079161148/yh-ui)
8
6
 
9
- <p align="center">
10
- 可视化流程图组件 · 拖拽连线 · 自定义节点 · 内置布局算法 · 完整 SSR 支持
11
- </p>
7
+ ## Highlights
12
8
 
13
- <p align="center">
14
- <a href="https://www.npmjs.com/package/@yh-ui/flow">
15
- <img src="https://img.shields.io/npm/v/@yh-ui/flow.svg?style=flat-square&colorB=409eff" alt="npm version">
16
- </a>
17
- <a href="https://www.npmjs.com/package/@yh-ui/flow">
18
- <img src="https://img.shields.io/npm/dm/@yh-ui/flow.svg?style=flat-square&colorB=409eff" alt="npm downloads">
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 { YhFlow, YhFlowNode, YhFlowEdge } from '@yh-ui/flow'
58
- import '@yh-ui/flow/dist/style.css'
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: '1', type: 'input', label: '开始', position: { x: 250, y: 0 } },
63
- { id: '2', label: '处理数据', position: { x: 100, y: 100 } },
64
- { id: '3', label: '校验结果', position: { x: 400, y: 100 } },
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: 'e1-2', source: '1', target: '2' },
70
- { id: 'e1-3', source: '1', target: '3' },
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
- <YhFlow :nodes="nodes" :edges="edges" style="width: 100%; height: 600px">
78
- <!-- 迷你地图插件 -->
79
- <YhFlowMiniMap />
80
- <!-- 工具栏插件 -->
81
- <YhFlowControls />
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
- ```vue
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
- - [📖 官方文档 Flow 组件](https://1079161148.github.io/yh-ui/components/flow)
230
- - [📦 GitHub 仓库](https://github.com/1079161148/yh-ui)
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 License © 2024-present YH-UI Team
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,5 +1,6 @@
1
1
  import type { Node, NodeData } from '../types';
2
2
  type __VLS_Props = {
3
+ flowId?: string;
3
4
  node: Node | null;
4
5
  visible: boolean;
5
6
  };
@@ -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 || document.getElementById(`node-${node.id}`)?.offsetWidth || 150,
24
- height: node.height || document.getElementById(`node-${node.id}`)?.offsetHeight || 40,
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
  }
@@ -1,5 +1,6 @@
1
1
  import type { Node, NodeData } from '../types';
2
2
  type __VLS_Props = {
3
+ flowId?: string;
3
4
  node: Node | null;
4
5
  visible: boolean;
5
6
  };
@@ -1,5 +1,6 @@
1
1
  type __VLS_Props = {
2
2
  nodeId: string;
3
+ flowId?: string;
3
4
  selected?: boolean;
4
5
  minWidth?: number;
5
6
  minHeight?: number;
@@ -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 = document.getElementById(`node-${props.nodeId}`);
55
+ const nodeEl = getNodeElement();
43
56
  if (!nodeEl) return;
44
57
  startWidth = nodeEl.offsetWidth;
45
58
  startHeight = nodeEl.offsetHeight;
@@ -1,5 +1,6 @@
1
1
  type __VLS_Props = {
2
2
  nodeId: string;
3
+ flowId?: string;
3
4
  selected?: boolean;
4
5
  minWidth?: number;
5
6
  minHeight?: number;
@@ -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 = document.getElementById(`node-${props.nodeId}`);
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>;
@@ -1,5 +1,6 @@
1
1
  import type { Edge, Node, HandleType, EdgeTypes } from '../types';
2
2
  type __VLS_Props = {
3
+ flowId: string;
3
4
  edges: Edge[];
4
5
  nodes: Node[];
5
6
  edgeTypes?: EdgeTypes;
@@ -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(#yh-mask-${ed.edge.id.replace(/\s/g, '')})` : void 0"
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();
@@ -1,5 +1,6 @@
1
1
  import type { Edge, Node, HandleType, EdgeTypes } from '../types';
2
2
  type __VLS_Props = {
3
+ flowId: string;
3
4
  edges: Edge[];
4
5
  nodes: Node[];
5
6
  edgeTypes?: EdgeTypes;
@@ -1,6 +1,7 @@
1
1
  import type { Node, HandleType, NodeTypes } from '../types';
2
2
  type __VLS_Props = {
3
3
  nodes: Node[];
4
+ flowId: string;
4
5
  nodeTypes?: NodeTypes;
5
6
  transform: {
6
7
  x: number;
@@ -3,7 +3,7 @@
3
3
  <div
4
4
  v-for="node in visibleNodes"
5
5
  :key="node.id"
6
- :id="`node-${node.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;
@@ -1,6 +1,7 @@
1
1
  import type { Node, HandleType, NodeTypes } from '../types';
2
2
  type __VLS_Props = {
3
3
  nodes: Node[];
4
+ flowId: string;
4
5
  nodeTypes?: NodeTypes;
5
6
  transform: {
6
7
  x: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yh-ui/flow",
3
- "version": "1.0.7",
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.7",
27
- "@yh-ui/hooks": "^1.0.7"
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",