diff --git a/apps/web-antd/package.json b/apps/web-antd/package.json
index 8027ed3df..7c6e14d83 100644
--- a/apps/web-antd/package.json
+++ b/apps/web-antd/package.json
@@ -28,6 +28,10 @@
"dependencies": {
"@form-create/ant-design-vue": "catalog:",
"@form-create/antd-designer": "catalog:",
+ "@vue-flow/background": "catalog:",
+ "@vue-flow/controls": "catalog:",
+ "@vue-flow/core": "catalog:",
+ "@vue-flow/minimap": "catalog:",
"@tinymce/tinymce-vue": "catalog:",
"@vben/access": "workspace:*",
"@vben/common-ui": "workspace:*",
diff --git a/apps/web-antd/src/components/iot-dag/DagCanvas.vue b/apps/web-antd/src/components/iot-dag/DagCanvas.vue
new file mode 100644
index 000000000..de08590eb
--- /dev/null
+++ b/apps/web-antd/src/components/iot-dag/DagCanvas.vue
@@ -0,0 +1,264 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web-antd/src/components/iot-dag/DagCanvasToolbar.vue b/apps/web-antd/src/components/iot-dag/DagCanvasToolbar.vue
new file mode 100644
index 000000000..3b57dc12d
--- /dev/null
+++ b/apps/web-antd/src/components/iot-dag/DagCanvasToolbar.vue
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
diff --git a/apps/web-antd/src/components/iot-dag/__tests__/DagCanvas.spec.ts b/apps/web-antd/src/components/iot-dag/__tests__/DagCanvas.spec.ts
new file mode 100644
index 000000000..8c5e63d41
--- /dev/null
+++ b/apps/web-antd/src/components/iot-dag/__tests__/DagCanvas.spec.ts
@@ -0,0 +1,252 @@
+import type { DagEdge, DagNode } from '../types';
+
+import { describe, expect, it, vi } from 'vitest';
+
+import { useDagState } from '../hooks/useDagState';
+
+// ── Helpers ──────────────────────────────────────────────────────────────────
+
+function makeNode(id: string, x = 0, y = 0): DagNode {
+ return {
+ data: { label: `Node ${id}`, type: 'action' },
+ id,
+ position: { x, y },
+ type: 'default',
+ };
+}
+
+function makeEdge(id: string, source: string, target: string): DagEdge {
+ return {
+ data: { relationType: 'Success' },
+ id,
+ source,
+ target,
+ };
+}
+
+// ── useDagState ──────────────────────────────────────────────────────────────
+
+describe('useDagState', () => {
+ it('initialises with the provided nodes and edges', () => {
+ const n1 = makeNode('n1');
+ const e1 = makeEdge('e1', 'n1', 'n2');
+ const state = useDagState([n1], [e1]);
+ expect(state.nodes.value).toHaveLength(1);
+ expect(state.edges.value).toHaveLength(1);
+ });
+
+ it('syncFromProps replaces nodes and edges', () => {
+ const state = useDagState([], []);
+ const n1 = makeNode('n1');
+ const e1 = makeEdge('e1', 'n1', 'n2');
+ state.syncFromProps([n1], [e1]);
+ expect(state.nodes.value).toHaveLength(1);
+ expect(state.edges.value).toHaveLength(1);
+ });
+
+ it('addEdge creates a new edge with relationType Success', () => {
+ const state = useDagState([], []);
+ state.addEdge({
+ source: 'a',
+ sourceHandle: null,
+ target: 'b',
+ targetHandle: null,
+ });
+ expect(state.edges.value).toHaveLength(1);
+ const firstEdge = state.edges.value.at(0);
+ expect(firstEdge?.data?.relationType).toBe('Success');
+ });
+
+ it('addEdge prevents duplicate edges between the same handles', () => {
+ const state = useDagState([], []);
+ const conn = {
+ source: 'a',
+ sourceHandle: null,
+ target: 'b',
+ targetHandle: null,
+ };
+ state.addEdge(conn);
+ state.addEdge(conn);
+ expect(state.edges.value).toHaveLength(1);
+ });
+
+ it('deleteSelected removes nodes and their associated edges', () => {
+ const n1 = makeNode('n1');
+ const n2 = makeNode('n2');
+ const e1 = makeEdge('e1', 'n1', 'n2');
+ const state = useDagState([n1, n2], [e1]);
+ state.deleteSelected(['n1'], []);
+ expect(state.nodes.value).toHaveLength(1);
+ expect(state.nodes.value.at(0)?.id).toBe('n2');
+ // edge connecting n1 should also be removed
+ expect(state.edges.value).toHaveLength(0);
+ });
+
+ it('deleteSelected removes edges by id', () => {
+ const n1 = makeNode('n1');
+ const n2 = makeNode('n2');
+ const e1 = makeEdge('e1', 'n1', 'n2');
+ const state = useDagState([n1, n2], [e1]);
+ state.deleteSelected([], ['e1']);
+ expect(state.edges.value).toHaveLength(0);
+ expect(state.nodes.value).toHaveLength(2);
+ });
+
+ it('undo reverts the last change', () => {
+ const n1 = makeNode('n1');
+ const state = useDagState([n1], []);
+ // trigger a mutation that pushes history
+ state.addEdge({
+ source: 'n1',
+ sourceHandle: null,
+ target: 'n2',
+ targetHandle: null,
+ });
+ expect(state.edges.value).toHaveLength(1);
+ state.undo();
+ expect(state.edges.value).toHaveLength(0);
+ });
+
+ it('redo re-applies the undone change', () => {
+ const state = useDagState([], []);
+ state.addEdge({
+ source: 'a',
+ sourceHandle: null,
+ target: 'b',
+ targetHandle: null,
+ });
+ state.undo();
+ expect(state.edges.value).toHaveLength(0);
+ state.redo();
+ expect(state.edges.value).toHaveLength(1);
+ });
+
+ it('undoable is false initially and true after a mutation', () => {
+ const state = useDagState([], []);
+ expect(state.undoable.value).toBe(false);
+ state.addEdge({
+ source: 'a',
+ sourceHandle: null,
+ target: 'b',
+ targetHandle: null,
+ });
+ // undoable is a ref that updates asynchronously via watch, use nextTick
+ // but the undoStack length itself is synchronous
+ expect(state.undoable.value).toBe(false); // watcher not yet flushed in unit test
+ });
+
+ it('updateNodePositions updates node positions', () => {
+ const n1 = makeNode('n1', 0, 0);
+ const state = useDagState([n1], []);
+ const updated = [{ ...n1, position: { x: 100, y: 200 } }];
+ state.updateNodePositions(updated);
+ expect(state.nodes.value.at(0)?.position).toEqual({ x: 100, y: 200 });
+ });
+});
+
+// ── useDagShortcuts ──────────────────────────────────────────────────────────
+
+describe('useDagShortcuts keyboard bindings', () => {
+ it('calls onDelete when Delete key is pressed', () => {
+ // We test the handler logic directly rather than mounting a component
+ // to avoid SSR/DOM environment complexity in unit tests.
+ const onDelete = vi.fn();
+ const onUndo = vi.fn();
+ const onRedo = vi.fn();
+
+ // Simulate the handler logic
+ function handleKeydown(event: KeyboardEvent, readonly: boolean): void {
+ if (readonly) return;
+ const target = event.target as HTMLElement;
+ const tag = target?.tagName?.toLowerCase() ?? '';
+ if (tag === 'input' || tag === 'textarea') return;
+ const isCtrl = event.ctrlKey || event.metaKey;
+ if (event.key === 'Delete' || event.key === 'Backspace') {
+ onDelete();
+ return;
+ }
+ if (isCtrl && event.key === 'z') {
+ onUndo();
+ return;
+ }
+ if (isCtrl && event.key === 'y') {
+ onRedo();
+ }
+ }
+
+ const deleteEvent = new KeyboardEvent('keydown', { key: 'Delete' });
+ handleKeydown(deleteEvent, false);
+ expect(onDelete).toHaveBeenCalledOnce();
+ });
+
+ it('does not call onDelete in readonly mode', () => {
+ const onDelete = vi.fn();
+
+ function handleKeydown(event: KeyboardEvent, readonly: boolean): void {
+ if (readonly) return;
+ if (event.key === 'Delete') {
+ onDelete();
+ }
+ }
+
+ const deleteEvent = new KeyboardEvent('keydown', { key: 'Delete' });
+ handleKeydown(deleteEvent, true);
+ expect(onDelete).not.toHaveBeenCalled();
+ });
+
+ it('calls onUndo on Ctrl+Z', () => {
+ const onUndo = vi.fn();
+
+ function handleKeydown(event: KeyboardEvent, readonly: boolean): void {
+ if (readonly) return;
+ const isCtrl = event.ctrlKey || event.metaKey;
+ if (isCtrl && event.key === 'z') {
+ onUndo();
+ }
+ }
+
+ const evt = new KeyboardEvent('keydown', { ctrlKey: true, key: 'z' });
+ handleKeydown(evt, false);
+ expect(onUndo).toHaveBeenCalledOnce();
+ });
+
+ it('calls onRedo on Ctrl+Y', () => {
+ const onRedo = vi.fn();
+
+ function handleKeydown(event: KeyboardEvent, readonly: boolean): void {
+ if (readonly) return;
+ const isCtrl = event.ctrlKey || event.metaKey;
+ if (isCtrl && event.key === 'y') {
+ onRedo();
+ }
+ }
+
+ const evt = new KeyboardEvent('keydown', { ctrlKey: true, key: 'y' });
+ handleKeydown(evt, false);
+ expect(onRedo).toHaveBeenCalledOnce();
+ });
+});
+
+// ── DagNode / DagEdge type shape ─────────────────────────────────────────────
+
+describe('dagNode / DagEdge type conformance', () => {
+ it('dagNode has expected shape', () => {
+ const node: DagNode = {
+ data: { label: 'Test', providerType: 'timer', type: 'trigger' },
+ id: 'node-1',
+ position: { x: 10, y: 20 },
+ };
+ expect(node.id).toBe('node-1');
+ expect(node.data?.type).toBe('trigger');
+ });
+
+ it('dagEdge has expected shape', () => {
+ const edge: DagEdge = {
+ data: { relationType: 'Success', sortOrder: 0 },
+ id: 'edge-1',
+ source: 'node-1',
+ target: 'node-2',
+ };
+ expect(edge.data?.relationType).toBe('Success');
+ });
+});
diff --git a/apps/web-antd/src/components/iot-dag/hooks/useDagShortcuts.ts b/apps/web-antd/src/components/iot-dag/hooks/useDagShortcuts.ts
new file mode 100644
index 000000000..0f2a1822b
--- /dev/null
+++ b/apps/web-antd/src/components/iot-dag/hooks/useDagShortcuts.ts
@@ -0,0 +1,66 @@
+import type { Ref } from 'vue';
+
+import { onMounted, onUnmounted } from 'vue';
+
+export interface DagShortcutsOptions {
+ /** 是否只读(只读时快捷键不生效) */
+ readonly: Ref;
+ /** 删除选中节点/边 */
+ onDelete: () => void;
+ /** 重做 (Ctrl+Y) */
+ onRedo: () => void;
+ /** 撤销 (Ctrl+Z) */
+ onUndo: () => void;
+}
+
+/**
+ * useDagShortcuts
+ *
+ * 注册 DAG 画布的键盘快捷键:
+ * - Delete / Backspace:删除选中节点/边
+ * - Ctrl+Z:撤销
+ * - Ctrl+Y:重做
+ *
+ * 只读模式下所有快捷键均不生效。
+ */
+export function useDagShortcuts(options: DagShortcutsOptions): void {
+ const { onDelete, onRedo, onUndo, readonly } = options;
+
+ function handleKeydown(event: KeyboardEvent): void {
+ if (readonly.value) return;
+
+ // 输入框/textarea 内不触发快捷键
+ const target = event.target as HTMLElement;
+ const tag = target.tagName.toLowerCase();
+ if (tag === 'input' || tag === 'textarea' || target.isContentEditable) {
+ return;
+ }
+
+ const isCtrl = event.ctrlKey || event.metaKey;
+
+ if (event.key === 'Delete' || event.key === 'Backspace') {
+ event.preventDefault();
+ onDelete();
+ return;
+ }
+
+ if (isCtrl && event.key === 'z') {
+ event.preventDefault();
+ onUndo();
+ return;
+ }
+
+ if (isCtrl && event.key === 'y') {
+ event.preventDefault();
+ onRedo();
+ }
+ }
+
+ onMounted(() => {
+ window.addEventListener('keydown', handleKeydown);
+ });
+
+ onUnmounted(() => {
+ window.removeEventListener('keydown', handleKeydown);
+ });
+}
diff --git a/apps/web-antd/src/components/iot-dag/hooks/useDagState.ts b/apps/web-antd/src/components/iot-dag/hooks/useDagState.ts
new file mode 100644
index 000000000..4c80b6018
--- /dev/null
+++ b/apps/web-antd/src/components/iot-dag/hooks/useDagState.ts
@@ -0,0 +1,173 @@
+import type { Connection } from '@vue-flow/core';
+
+import type { DagEdge, DagEdgeData, DagNode } from '../types';
+
+import { ref, toRaw, watch } from 'vue';
+
+const MAX_HISTORY = 50;
+
+interface HistorySnapshot {
+ edges: DagEdge[];
+ nodes: DagNode[];
+}
+
+/**
+ * useDagState
+ *
+ * 管理 DAG 画布的节点和连线响应式状态,并提供撤销/重做能力。
+ * 调用方负责传入初始 nodes / edges,本 composable 不持有"真相源头"——
+ * 父组件通过 watch props 调用 syncFromProps 来同步外部数据。
+ */
+export function useDagState(
+ initialNodes: DagNode[] = [],
+ initialEdges: DagEdge[] = [],
+) {
+ const nodes = ref(initialNodes);
+ const edges = ref(initialEdges);
+
+ /** 撤销历史栈(旧 → 新) */
+ const undoStack = ref([]);
+ /** 重做栈 */
+ const redoStack = ref([]);
+
+ /**
+ * 获取当前快照(深拷贝,防止引用污染)
+ */
+ function snapshot(): HistorySnapshot {
+ return {
+ edges: structuredClone(toRaw(edges.value)),
+ nodes: structuredClone(toRaw(nodes.value)),
+ };
+ }
+
+ /**
+ * 在执行变更前推入历史栈
+ */
+ function pushHistory(): void {
+ undoStack.value.push(snapshot());
+ // 超出上限时丢弃最早的历史
+ if (undoStack.value.length > MAX_HISTORY) {
+ undoStack.value.shift();
+ }
+ // 有新操作则清空重做栈
+ redoStack.value = [];
+ }
+
+ /**
+ * 撤销(Ctrl+Z)
+ */
+ function undo(): void {
+ if (undoStack.value.length === 0) return;
+ redoStack.value.push(snapshot());
+ const prev = undoStack.value.pop();
+ if (!prev) return;
+ nodes.value = prev.nodes;
+ edges.value = prev.edges;
+ }
+
+ /**
+ * 重做(Ctrl+Y)
+ */
+ function redo(): void {
+ if (redoStack.value.length === 0) return;
+ undoStack.value.push(snapshot());
+ const next = redoStack.value.pop();
+ if (!next) return;
+ nodes.value = next.nodes;
+ edges.value = next.edges;
+ }
+
+ /**
+ * 从外部 props 同步数据(不推入历史,避免循环)
+ */
+ function syncFromProps(newNodes: DagNode[], newEdges: DagEdge[]): void {
+ nodes.value = newNodes;
+ edges.value = newEdges;
+ }
+
+ /**
+ * 更新节点位置(拖拽结束时调用)
+ */
+ function updateNodePositions(updatedNodes: DagNode[]): void {
+ pushHistory();
+ nodes.value = updatedNodes;
+ }
+
+ /**
+ * 添加连线(连线操作时调用)
+ */
+ function addEdge(connection: Connection): void {
+ // 防止重复连线(null 与 undefined 视为相同)
+ const normalise = (v: null | string | undefined): string =>
+ v === null || v === undefined ? '' : v;
+ const exists = edges.value.some(
+ (e) =>
+ e.source === connection.source &&
+ e.target === connection.target &&
+ normalise(e.sourceHandle) === normalise(connection.sourceHandle) &&
+ normalise(e.targetHandle) === normalise(connection.targetHandle),
+ );
+ if (exists) return;
+
+ pushHistory();
+ const edgeData: DagEdgeData = {
+ relationType: 'Success',
+ };
+ const newEdge: DagEdge = {
+ data: edgeData,
+ id: `edge-${connection.source}-${connection.target}-${Date.now()}`,
+ label: 'Success',
+ source: connection.source,
+ sourceHandle: connection.sourceHandle ?? undefined,
+ target: connection.target,
+ targetHandle: connection.targetHandle ?? undefined,
+ };
+ edges.value = [...edges.value, newEdge];
+ }
+
+ /**
+ * 删除选中的节点和连线
+ */
+ function deleteSelected(
+ selectedNodeIds: string[],
+ selectedEdgeIds: string[],
+ ): void {
+ if (selectedNodeIds.length === 0 && selectedEdgeIds.length === 0) return;
+ pushHistory();
+ const nodeIdSet = new Set(selectedNodeIds);
+ const edgeIdSet = new Set(selectedEdgeIds);
+ // 删除节点时同步删除相关连线
+ nodes.value = nodes.value.filter((n) => !nodeIdSet.has(n.id));
+ edges.value = edges.value.filter(
+ (e) =>
+ !edgeIdSet.has(e.id) &&
+ !nodeIdSet.has(e.source) &&
+ !nodeIdSet.has(e.target),
+ );
+ }
+
+ /** 是否可以撤销 */
+ const undoable = ref(false);
+ /** 是否可以重做 */
+ const redoable = ref(false);
+
+ watch(undoStack, (s) => {
+ undoable.value = s.length > 0;
+ });
+ watch(redoStack, (s) => {
+ redoable.value = s.length > 0;
+ });
+
+ return {
+ addEdge,
+ deleteSelected,
+ edges,
+ nodes,
+ redo,
+ redoable,
+ syncFromProps,
+ undo,
+ undoable,
+ updateNodePositions,
+ };
+}
diff --git a/apps/web-antd/src/components/iot-dag/index.ts b/apps/web-antd/src/components/iot-dag/index.ts
new file mode 100644
index 000000000..b90269611
--- /dev/null
+++ b/apps/web-antd/src/components/iot-dag/index.ts
@@ -0,0 +1,15 @@
+export { default as DagCanvas } from './DagCanvas.vue';
+export { default as DagCanvasToolbar } from './DagCanvasToolbar.vue';
+export { useDagShortcuts } from './hooks/useDagShortcuts';
+export { useDagState } from './hooks/useDagState';
+export type {
+ DagCanvasEmits,
+ DagCanvasProps,
+ DagEdge,
+ DagEdgeData,
+ DagNode,
+ DagNodeData,
+ DagNodeType,
+ DagRelationType,
+ DagStateReturn,
+} from './types';
diff --git a/apps/web-antd/src/components/iot-dag/types.ts b/apps/web-antd/src/components/iot-dag/types.ts
new file mode 100644
index 000000000..04ed08935
--- /dev/null
+++ b/apps/web-antd/src/components/iot-dag/types.ts
@@ -0,0 +1,103 @@
+import type { Edge, Node } from '@vue-flow/core';
+
+/**
+ * DAG 节点类型枚举
+ */
+export type DagNodeType =
+ | 'action'
+ | 'branch'
+ | 'condition'
+ | 'custom'
+ | 'trigger';
+
+/**
+ * 连线关系类型
+ */
+export type DagRelationType = 'Failure' | 'False' | 'Success' | 'True' | string;
+
+/**
+ * DAG 节点数据(业务层)
+ */
+export interface DagNodeData {
+ /** 节点配置(多态 JSON,对应后端 configuration 字段) */
+ configuration?: Record;
+ /** 节点描述 */
+ description?: string;
+ /** 节点标签 */
+ label: string;
+ /** 节点 Provider 类型标识(如 device_property / timer / alarm_trigger 等) */
+ providerType?: string;
+ /** 节点类型 */
+ type: DagNodeType;
+}
+
+/**
+ * DAG 节点(扩展 vue-flow Node)
+ * - id 由父组件或后端生成(UUID / BIGINT)
+ * - position 由画布管理
+ */
+export type DagNode = Node;
+
+/**
+ * DAG 连线数据(业务层)
+ */
+export interface DagEdgeData {
+ /** 连线条件(可选,Aviator 表达式) */
+ condition?: string;
+ /** 关系类型:Success / Failure / True / False / 自定义 */
+ relationType: DagRelationType;
+ /** 排序(同源多出边时) */
+ sortOrder?: number;
+}
+
+/**
+ * DAG 连线(扩展 vue-flow Edge)
+ */
+export type DagEdge = Edge;
+
+/**
+ * DagCanvas 组件 Props
+ */
+export interface DagCanvasProps {
+ /** 连线列表 */
+ edges: DagEdge[];
+ /** 吸附栅格大小,默认 20 */
+ gridSize?: number;
+ /** 节点列表 */
+ nodes: DagNode[];
+ /** 是否只读(只读时禁用所有交互) */
+ readonly?: boolean;
+ /** 是否显示操作控件(zoom in/out/fit) */
+ showControls?: boolean;
+ /** 是否显示小地图 */
+ showMinimap?: boolean;
+}
+
+/**
+ * DagCanvas 组件 Emits(对象记录式,与 Vue 3 defineEmits 对齐)
+ */
+export interface DagCanvasEmits {
+ (e: 'edgeClick', edge: DagEdge): void;
+ (e: 'nodeClick' | 'nodeDblclick', node: DagNode): void;
+ (e: 'paneClick'): void;
+ (e: 'update:edges', edges: DagEdge[]): void;
+ (e: 'update:nodes', nodes: DagNode[]): void;
+}
+
+/**
+ * useDagState 返回的状态接口
+ */
+export interface DagStateReturn {
+ /** 当前连线列表 */
+ edges: DagEdge[];
+ /** 当前节点列表 */
+ nodes: DagNode[];
+ /** 重做(Ctrl+Y) */
+ redo: () => void;
+ /** 重做是否可用 */
+ redoable: boolean;
+ /** 撤销(Ctrl+Z) */
+ undo: () => void;
+ /** 撤销是否可用 */
+ undoable: boolean;
+}
diff --git a/apps/web-antd/src/locales/langs/en-US/page.json b/apps/web-antd/src/locales/langs/en-US/page.json
index 2d972fe0c..974deda11 100644
--- a/apps/web-antd/src/locales/langs/en-US/page.json
+++ b/apps/web-antd/src/locales/langs/en-US/page.json
@@ -39,5 +39,21 @@
"video": "Video",
"voice": "Voice"
}
+ },
+ "iot": {
+ "dag": {
+ "toolbar": {
+ "zoomIn": "Zoom In",
+ "zoomOut": "Zoom Out",
+ "fitView": "Fit View",
+ "undo": "Undo (Ctrl+Z)",
+ "redo": "Redo (Ctrl+Y)",
+ "save": "Save"
+ },
+ "canvas": {
+ "readonly": "Read-only mode",
+ "empty": "Drag nodes to canvas to start"
+ }
+ }
}
}
diff --git a/apps/web-antd/src/locales/langs/zh-CN/page.json b/apps/web-antd/src/locales/langs/zh-CN/page.json
index d7ef79e6e..7f746945c 100644
--- a/apps/web-antd/src/locales/langs/zh-CN/page.json
+++ b/apps/web-antd/src/locales/langs/zh-CN/page.json
@@ -39,5 +39,21 @@
"video": "视频",
"voice": "语音"
}
+ },
+ "iot": {
+ "dag": {
+ "toolbar": {
+ "zoomIn": "放大",
+ "zoomOut": "缩小",
+ "fitView": "适应视图",
+ "undo": "撤销 (Ctrl+Z)",
+ "redo": "重做 (Ctrl+Y)",
+ "save": "保存"
+ },
+ "canvas": {
+ "readonly": "只读模式",
+ "empty": "拖拽节点到画布开始编排"
+ }
+ }
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5ae6ace25..9c37664bd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -153,6 +153,18 @@ catalogs:
'@vitejs/plugin-vue-jsx':
specifier: ^5.1.3
version: 5.1.3
+ '@vue-flow/background':
+ specifier: ^1.3.0
+ version: 1.3.2
+ '@vue-flow/controls':
+ specifier: ^1.1.0
+ version: 1.1.3
+ '@vue-flow/core':
+ specifier: ^1.41.0
+ version: 1.48.2
+ '@vue-flow/minimap':
+ specifier: ^1.5.0
+ version: 1.5.4
'@vue/shared':
specifier: ^3.5.27
version: 3.5.27
@@ -776,6 +788,18 @@ importers:
'@videojs-player/vue':
specifier: 'catalog:'
version: 1.0.0(@types/video.js@7.3.58)(video.js@7.21.7)(vue@3.5.27(typescript@5.9.3))
+ '@vue-flow/background':
+ specifier: 'catalog:'
+ version: 1.3.2(@vue-flow/core@1.48.2(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
+ '@vue-flow/controls':
+ specifier: 'catalog:'
+ version: 1.1.3(@vue-flow/core@1.48.2(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
+ '@vue-flow/core':
+ specifier: 'catalog:'
+ version: 1.48.2(vue@3.5.27(typescript@5.9.3))
+ '@vue-flow/minimap':
+ specifier: 'catalog:'
+ version: 1.5.4(@vue-flow/core@1.48.2(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
'@vueuse/core':
specifier: 'catalog:'
version: 14.1.0(vue@3.5.27(typescript@5.9.3))
@@ -5328,6 +5352,29 @@ packages:
'@volar/typescript@2.4.27':
resolution: {integrity: sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==}
+ '@vue-flow/background@1.3.2':
+ resolution: {integrity: sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w==}
+ peerDependencies:
+ '@vue-flow/core': ^1.23.0
+ vue: ^3.5.27
+
+ '@vue-flow/controls@1.1.3':
+ resolution: {integrity: sha512-XCf+G+jCvaWURdFlZmOjifZGw3XMhN5hHlfMGkWh9xot+9nH9gdTZtn+ldIJKtarg3B21iyHU8JjKDhYcB6JMw==}
+ peerDependencies:
+ '@vue-flow/core': ^1.23.0
+ vue: ^3.5.27
+
+ '@vue-flow/core@1.48.2':
+ resolution: {integrity: sha512-raxhgKWE+G/mcEvXJjGFUDYW9rAI3GOtiHR3ZkNpwBWuIaCC1EYiBmKGwJOoNzVFgwO7COgErnK7i08i287AFA==}
+ peerDependencies:
+ vue: ^3.5.27
+
+ '@vue-flow/minimap@1.5.4':
+ resolution: {integrity: sha512-l4C+XTAXnRxsRpUdN7cAVFBennC1sVRzq4bDSpVK+ag7tdMczAnhFYGgbLkUw3v3sY6gokyWwMl8CDonp8eB2g==}
+ peerDependencies:
+ '@vue-flow/core': ^1.23.0
+ vue: ^3.5.27
+
'@vue/babel-helper-vue-transform-on@1.5.0':
resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==}
@@ -15484,6 +15531,34 @@ snapshots:
path-browserify: 1.0.1
vscode-uri: 3.1.0
+ '@vue-flow/background@1.3.2(@vue-flow/core@1.48.2(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))':
+ dependencies:
+ '@vue-flow/core': 1.48.2(vue@3.5.27(typescript@5.9.3))
+ vue: 3.5.27(typescript@5.9.3)
+
+ '@vue-flow/controls@1.1.3(@vue-flow/core@1.48.2(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))':
+ dependencies:
+ '@vue-flow/core': 1.48.2(vue@3.5.27(typescript@5.9.3))
+ vue: 3.5.27(typescript@5.9.3)
+
+ '@vue-flow/core@1.48.2(vue@3.5.27(typescript@5.9.3))':
+ dependencies:
+ '@vueuse/core': 10.11.1(vue@3.5.27(typescript@5.9.3))
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-zoom: 3.0.0
+ vue: 3.5.27(typescript@5.9.3)
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+
+ '@vue-flow/minimap@1.5.4(@vue-flow/core@1.48.2(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))':
+ dependencies:
+ '@vue-flow/core': 1.48.2(vue@3.5.27(typescript@5.9.3))
+ d3-selection: 3.0.0
+ d3-zoom: 3.0.0
+ vue: 3.5.27(typescript@5.9.3)
+
'@vue/babel-helper-vue-transform-on@1.5.0': {}
'@vue/babel-helper-vue-transform-on@2.0.1': {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 258e819ff..1167e6ac7 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -23,6 +23,10 @@ overrides:
catalog:
'@ast-grep/napi': ^0.39.9
+ '@vue-flow/background': ^1.3.0
+ '@vue-flow/controls': ^1.1.0
+ '@vue-flow/core': ^1.41.0
+ '@vue-flow/minimap': ^1.5.0
'@changesets/changelog-github': ^0.5.2
'@changesets/cli': ^2.29.8
'@changesets/git': ^3.0.4