@huanban/rulego-editor-ui 1.0.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.
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # @huanban/editor-ui
2
+
3
+ > RuleGo 编辑器 UI Kit — 纯 DOM 组件集 (零框架依赖)
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@huanban/editor-ui.svg)](https://www.npmjs.com/package/@huanban/editor-ui)
6
+ [![license](https://img.shields.io/npm/l/@huanban/editor-ui.svg)](https://github.com/openclaw/rulego-editor/blob/main/LICENSE)
7
+
8
+ ## ✨ 特性
9
+
10
+ - **零框架依赖** — 纯原生 DOM 操作,可用于任何前端框架
11
+ - **组件丰富** — 提供 Sidebar、Toolbar、PropertyPanel、ContextMenu 四大组件
12
+ - **自动绑定** — 组件自动连接 EditorCore 实例,响应状态变化
13
+ - **样式内聚** — 每个组件自带完整样式,即插即用
14
+
15
+ ## 📦 安装
16
+
17
+ ```bash
18
+ npm install @huanban/editor-ui @huanban/editor-core
19
+ ```
20
+
21
+ ## 🚀 快速开始
22
+
23
+ ```typescript
24
+ import { EditorCore } from '@huanban/editor-core'
25
+ import { Sidebar, Toolbar, PropertyPanel, ContextMenu } from '@huanban/editor-ui'
26
+
27
+ const core = new EditorCore({ container: document.getElementById('editor')! })
28
+
29
+ // 挂载侧边栏 (组件分类拖拽面板)
30
+ new Sidebar(document.getElementById('sidebar')!, { core })
31
+
32
+ // 挂载工具栏 (撤销/重做/保存/缩放)
33
+ new Toolbar(document.getElementById('toolbar')!, { core })
34
+
35
+ // 挂载属性面板 (节点配置编辑)
36
+ new PropertyPanel(document.getElementById('panel')!, { core })
37
+ ```
38
+
39
+ ## 📖 组件说明
40
+
41
+ | 组件 | 说明 |
42
+ |------|------|
43
+ | `Sidebar` | 左侧组件分类面板,支持拖拽添加节点 |
44
+ | `Toolbar` | 顶部工具栏,内置撤销/重做/保存/缩放/删除 |
45
+ | `PropertyPanel` | 右侧属性面板,动态渲染节点配置表单 |
46
+ | `ContextMenu` | 右键上下文菜单 |
47
+
48
+ ## 🔗 相关包
49
+
50
+ - [`@huanban/editor-core`](https://www.npmjs.com/package/@huanban/editor-core) — 核心引擎 (必需)
51
+ - [`@huanban/editor-react`](https://www.npmjs.com/package/@huanban/editor-react) — React 适配层
52
+ - [`@huanban/editor-vue`](https://www.npmjs.com/package/@huanban/editor-vue) — Vue 3 适配层
53
+
54
+ ## 📄 协议
55
+
56
+ Apache-2.0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * @file components/ContextMenu.ts
3
+ * @description 右键上下文菜单 — 纯 DOM 实现 (零框架依赖)
4
+ *
5
+ * 功能:
6
+ * - 支持节点右键菜单 (编辑 / 删除 / 复制 等)
7
+ * - 支持画布空白处右键菜单 (粘贴 / 全选 等)
8
+ * - 支持边右键菜单 (删除连线 / 编辑标签 等)
9
+ * - 自动定位到鼠标位置
10
+ * - 点击外部自动关闭
11
+ * - 支持自定义菜单项扩展
12
+ *
13
+ * 使用方式:
14
+ * ```typescript
15
+ * import { ContextMenu } from '@huanban/editor-ui'
16
+ *
17
+ * const menu = new ContextMenu(container, {
18
+ * core: editorCore,
19
+ * items: [...] // 可选: 自定义菜单项
20
+ * })
21
+ * menu.destroy()
22
+ * ```
23
+ */
24
+ import type { EditorCore } from '@huanban/rulego-editor-core';
25
+ /**
26
+ * 菜单项类型
27
+ */
28
+ export type MenuItemType = 'node' | 'edge' | 'canvas' | 'all';
29
+ /**
30
+ * 菜单项定义
31
+ */
32
+ export interface MenuItem {
33
+ /** 菜单项唯一标识 */
34
+ id: string;
35
+ /** 显示文本 */
36
+ label: string;
37
+ /** 图标 (emoji 或 SVG) */
38
+ icon?: string;
39
+ /** 适用的目标类型 */
40
+ target: MenuItemType | MenuItemType[];
41
+ /** 是否禁用 (可动态) */
42
+ disabled?: boolean | (() => boolean);
43
+ /** 是否在此项之后添加分隔线 */
44
+ separator?: boolean;
45
+ /** 点击回调, 接收目标元素的信息 */
46
+ onClick: (context: MenuContext) => void;
47
+ }
48
+ /**
49
+ * 菜单上下文 — 右键时附带的目标信息
50
+ */
51
+ export interface MenuContext {
52
+ /** 目标类型 */
53
+ type: MenuItemType;
54
+ /** 目标元素 ID (节点/边 LogicFlow ID) */
55
+ id?: string;
56
+ /** 鼠标坐标 */
57
+ position: {
58
+ x: number;
59
+ y: number;
60
+ };
61
+ /** 附加数据 */
62
+ data?: Record<string, unknown>;
63
+ }
64
+ /**
65
+ * ContextMenu 配置选项
66
+ */
67
+ export interface ContextMenuOptions {
68
+ /** EditorCore 实例 (必须) */
69
+ core: EditorCore;
70
+ /** 自定义菜单项 (追加到内置菜单项之后) */
71
+ items?: MenuItem[];
72
+ /** 是否包含内置菜单项, 默认 true */
73
+ includeBuiltin?: boolean;
74
+ }
75
+ /**
76
+ * 纯 DOM 实现的右键上下文菜单
77
+ *
78
+ * 生命周期:
79
+ * 1. 构造时创建隐藏的菜单 DOM
80
+ * 2. 监听右键事件, 判断目标类型并显示对应菜单
81
+ * 3. 点击菜单项后执行回调并关闭
82
+ * 4. 点击外部区域自动关闭
83
+ */
84
+ export declare class ContextMenu {
85
+ /** 画布容器 (用于事件监听范围) */
86
+ private container;
87
+ /** EditorCore 引用 */
88
+ private core;
89
+ /** 菜单项列表 */
90
+ private items;
91
+ /** 菜单根 DOM 元素 */
92
+ private menuEl;
93
+ /** 当前上下文 */
94
+ private currentContext;
95
+ /** 外部点击关闭的监听函数引用 */
96
+ private outsideClickHandler;
97
+ constructor(container: HTMLElement, options: ContextMenuOptions);
98
+ /**
99
+ * 创建菜单 DOM (初始隐藏)
100
+ */
101
+ private mount;
102
+ /**
103
+ * 绑定事件
104
+ */
105
+ private bindEvents;
106
+ /**
107
+ * 销毁菜单
108
+ */
109
+ destroy(): void;
110
+ /**
111
+ * 处理右键事件
112
+ */
113
+ private handleContextMenu;
114
+ /**
115
+ * 解析右键目标的上下文
116
+ */
117
+ private resolveContext;
118
+ /**
119
+ * 渲染并显示菜单
120
+ */
121
+ private renderMenu;
122
+ /**
123
+ * 隐藏菜单
124
+ */
125
+ hide(): void;
126
+ /**
127
+ * 获取内置菜单项
128
+ */
129
+ private getBuiltinItems;
130
+ /**
131
+ * 动态添加菜单项
132
+ * @param item - 菜单项定义
133
+ */
134
+ addItem(item: MenuItem): void;
135
+ /**
136
+ * 移除菜单项
137
+ * @param id - 菜单项 ID
138
+ */
139
+ removeItem(id: string): void;
140
+ /**
141
+ * 获取菜单 DOM 元素
142
+ */
143
+ getElement(): HTMLElement | null;
144
+ }
145
+ //# sourceMappingURL=ContextMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContextMenu.d.ts","sourceRoot":"","sources":["../../src/components/ContextMenu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAM7D;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;AAE7D;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,cAAc;IACd,EAAE,EAAE,MAAM,CAAA;IACV,WAAW;IACX,KAAK,EAAE,MAAM,CAAA;IACb,uBAAuB;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,cAAc;IACd,MAAM,EAAE,YAAY,GAAG,YAAY,EAAE,CAAA;IACrC,iBAAiB;IACjB,QAAQ,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,CAAA;IACpC,mBAAmB;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,sBAAsB;IACtB,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,WAAW;IACX,IAAI,EAAE,YAAY,CAAA;IAClB,kCAAkC;IAClC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,WAAW;IACX,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAClC,WAAW;IACX,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,yBAAyB;IACzB,IAAI,EAAE,UAAU,CAAA;IAChB,0BAA0B;IAC1B,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;IAClB,yBAAyB;IACzB,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAMD;;;;;;;;GAQG;AACH,qBAAa,WAAW;IACtB,sBAAsB;IACtB,OAAO,CAAC,SAAS,CAAa;IAC9B,oBAAoB;IACpB,OAAO,CAAC,IAAI,CAAY;IACxB,YAAY;IACZ,OAAO,CAAC,KAAK,CAAY;IACzB,iBAAiB;IACjB,OAAO,CAAC,MAAM,CAA2B;IACzC,YAAY;IACZ,OAAO,CAAC,cAAc,CAA2B;IACjD,oBAAoB;IACpB,OAAO,CAAC,mBAAmB,CAAyC;gBAExD,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB;IAgB/D;;OAEG;IACH,OAAO,CAAC,KAAK;IAOb;;OAEG;IACH,OAAO,CAAC,UAAU;IAuBlB;;OAEG;IACH,OAAO,IAAI,IAAI;IAef;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAmBzB;;OAEG;IACH,OAAO,CAAC,cAAc;IAgCtB;;OAEG;IACH,OAAO,CAAC,UAAU;IAiElB;;OAEG;IACH,IAAI,IAAI,IAAI;IAWZ;;OAEG;IACH,OAAO,CAAC,eAAe;IAmDvB;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IAI7B;;;OAGG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAI5B;;OAEG;IACH,UAAU,IAAI,WAAW,GAAG,IAAI;CAGjC"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @file components/PropertyPanel.ts
3
+ * @description 节点/边属性面板 — 纯 DOM 实现 (零框架依赖)
4
+ *
5
+ * 功能:
6
+ * - 监听节点/边选中事件, 自动展示对应的属性表单
7
+ * - 根据组件定义动态生成表单字段
8
+ * - 支持字段类型: string, int, bool, select, code, json
9
+ * - 字段变更实时回传给 EditorCore
10
+ * - 空白处点击时自动关闭面板
11
+ *
12
+ * 使用方式:
13
+ * ```typescript
14
+ * import { PropertyPanel } from '@huanban/editor-ui'
15
+ *
16
+ * const panel = new PropertyPanel(container, { core: editorCore })
17
+ * panel.destroy()
18
+ * ```
19
+ *
20
+ * @see ComponentField — 字段定义类型
21
+ * @see EditorCore — 编辑器核心引擎
22
+ */
23
+ import type { EditorCore } from '@huanban/rulego-editor-core';
24
+ import type { ComponentDefinition } from '@huanban/rulego-editor-core';
25
+ /**
26
+ * PropertyPanel 配置选项
27
+ */
28
+ export interface PropertyPanelOptions {
29
+ /** EditorCore 实例 (必须) */
30
+ core: EditorCore;
31
+ /** 面板标题文本 */
32
+ title?: string;
33
+ /** 宽度, 默认 '320px' */
34
+ width?: string;
35
+ /** 位置, 默认 'right' */
36
+ position?: 'left' | 'right';
37
+ }
38
+ /**
39
+ * 纯 DOM 实现的属性编辑面板
40
+ *
41
+ * 工作流:
42
+ * 1. 监听 core.store 中 currentNodeView / currentNodeModel 变化
43
+ * 2. 根据 ComponentDefinition.fields 动态生成表单
44
+ * 3. 字段变更时通过 core.eventBus 发送 'node:property-update' 事件
45
+ * 4. 画布空白点击时清空面板内容
46
+ */
47
+ export declare class PropertyPanel {
48
+ /** 外层容器 */
49
+ private container;
50
+ /** EditorCore 引用 */
51
+ private core;
52
+ /** 配置选项 */
53
+ private options;
54
+ /** 根 DOM 元素 */
55
+ private rootEl;
56
+ /** 表单容器 */
57
+ private formContainer;
58
+ /** 面板标题元素 */
59
+ private titleEl;
60
+ /** 状态订阅取消函数 */
61
+ private unsubscribe;
62
+ /** 事件取消列表 */
63
+ private eventCleanups;
64
+ /** 当前编辑的节点 LF ID */
65
+ private currentNodeId;
66
+ constructor(container: HTMLElement, options: PropertyPanelOptions);
67
+ /**
68
+ * 创建面板 DOM
69
+ */
70
+ private mount;
71
+ /**
72
+ * 绑定事件监听
73
+ */
74
+ private bindEvents;
75
+ /**
76
+ * 销毁面板
77
+ */
78
+ destroy(): void;
79
+ /**
80
+ * 显示面板并渲染表单
81
+ * @param view - 组件定义 (含字段列表)
82
+ * @param model - 当前节点模型数据
83
+ */
84
+ private showPanel;
85
+ /**
86
+ * 清空面板并隐藏
87
+ */
88
+ private clearPanel;
89
+ /**
90
+ * 渲染基础信息区 (节点名称等)
91
+ * @param model - 节点模型
92
+ */
93
+ private renderBasicInfo;
94
+ /**
95
+ * 创建单个表单字段元素
96
+ * @param field - 字段定义
97
+ * @param model - 节点模型数据
98
+ * @returns 字段 wrapper HTMLElement
99
+ */
100
+ private createFieldElement;
101
+ /**
102
+ * 文本输入框
103
+ */
104
+ private createTextField;
105
+ /**
106
+ * 数值输入框
107
+ */
108
+ private createNumberField;
109
+ /**
110
+ * 布尔开关
111
+ */
112
+ private createBoolField;
113
+ /**
114
+ * 下拉选择
115
+ */
116
+ private createSelectField;
117
+ /**
118
+ * 多行文本 / 代码编辑器
119
+ */
120
+ private createTextareaField;
121
+ /**
122
+ * 从模型中获取字段当前值
123
+ * @param field - 字段定义
124
+ * @param model - 节点模型
125
+ * @returns 字段值
126
+ */
127
+ private getModelValue;
128
+ /**
129
+ * 处理字段变更, 通知 EditorCore
130
+ * @param fieldName - 字段名
131
+ * @param value - 新值
132
+ */
133
+ private handleFieldChange;
134
+ /**
135
+ * 获取根 DOM 元素
136
+ */
137
+ getElement(): HTMLElement | null;
138
+ /**
139
+ * 手动设置当前编辑的节点 ID
140
+ * @param nodeId - LogicFlow 节点 ID
141
+ */
142
+ setCurrentNodeId(nodeId: string): void;
143
+ /**
144
+ * 手动显示面板
145
+ * @param view - 组件定义
146
+ * @param model - 节点模型
147
+ * @param nodeId - 节点 ID
148
+ */
149
+ show(view: ComponentDefinition, model: Record<string, unknown>, nodeId?: string): void;
150
+ /**
151
+ * 手动隐藏面板
152
+ */
153
+ hide(): void;
154
+ }
155
+ //# sourceMappingURL=PropertyPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PropertyPanel.d.ts","sourceRoot":"","sources":["../../src/components/PropertyPanel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAC7D,OAAO,KAAK,EAAkB,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAMtF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,yBAAyB;IACzB,IAAI,EAAE,UAAU,CAAA;IAChB,aAAa;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qBAAqB;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAC5B;AAMD;;;;;;;;GAQG;AACH,qBAAa,aAAa;IACxB,WAAW;IACX,OAAO,CAAC,SAAS,CAAa;IAC9B,oBAAoB;IACpB,OAAO,CAAC,IAAI,CAAY;IACxB,WAAW;IACX,OAAO,CAAC,OAAO,CAAgC;IAC/C,eAAe;IACf,OAAO,CAAC,MAAM,CAA2B;IACzC,WAAW;IACX,OAAO,CAAC,aAAa,CAA2B;IAChD,aAAa;IACb,OAAO,CAAC,OAAO,CAA2B;IAC1C,eAAe;IACf,OAAO,CAAC,WAAW,CAA4B;IAC/C,aAAa;IACb,OAAO,CAAC,aAAa,CAAwB;IAC7C,oBAAoB;IACpB,OAAO,CAAC,aAAa,CAAsB;gBAE/B,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,oBAAoB;IAmBjE;;OAEG;IACH,OAAO,CAAC,KAAK;IAgCb;;OAEG;IACH,OAAO,CAAC,UAAU;IAkBlB;;OAEG;IACH,OAAO,IAAI,IAAI;IAoBf;;;;OAIG;IACH,OAAO,CAAC,SAAS;IAqBjB;;OAEG;IACH,OAAO,CAAC,UAAU;IAWlB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAqBvB;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAiD1B;;OAEG;IACH,OAAO,CAAC,eAAe;IAevB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAczB;;OAEG;IACH,OAAO,CAAC,eAAe;IAyBvB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA2BzB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAwB3B;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAYrB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAczB;;OAEG;IACH,UAAU,IAAI,WAAW,GAAG,IAAI;IAIhC;;;OAGG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAItC;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAOtF;;OAEG;IACH,IAAI,IAAI,IAAI;CAGb"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @file components/Sidebar.ts
3
+ * @description 组件侧边栏 — 纯 DOM 实现 (零框架依赖)
4
+ *
5
+ * 功能:
6
+ * - 按分类分组展示可用组件
7
+ * - 支持关键字搜索过滤
8
+ * - 支持分组折叠/展开
9
+ * - 拖拽组件到画布 (DnD)
10
+ * - 自动监听 EditorCore 状态变化, 更新组件列表
11
+ *
12
+ * 使用方式:
13
+ * ```typescript
14
+ * import { Sidebar } from '@huanban/editor-ui'
15
+ *
16
+ * const sidebar = new Sidebar(container, { core: editorCore })
17
+ * // 卸载时
18
+ * sidebar.destroy()
19
+ * ```
20
+ *
21
+ * @see ComponentGroup — 组件分组定义
22
+ * @see EditorCore — 编辑器核心引擎
23
+ */
24
+ import type { EditorCore } from '@huanban/rulego-editor-core';
25
+ /**
26
+ * Sidebar 配置选项
27
+ */
28
+ export interface SidebarOptions {
29
+ /** EditorCore 实例 (必须) */
30
+ core: EditorCore;
31
+ /** 是否显示搜索框, 默认 true */
32
+ searchable?: boolean;
33
+ /** 默认折叠状态, 默认 false (全部展开) */
34
+ defaultCollapsed?: boolean;
35
+ /** 搜索框占位符文本 */
36
+ searchPlaceholder?: string;
37
+ /** 宽度 (CSS值), 默认 '220px' */
38
+ width?: string;
39
+ }
40
+ /**
41
+ * 纯 DOM 实现的组件侧边栏
42
+ *
43
+ * 内部维护:
44
+ * - componentGroups: 当前显示的组件分组
45
+ * - collapsedGroups: 折叠状态 Set
46
+ * - searchKeyword: 搜索关键字
47
+ *
48
+ * 通过 EditorCore.store.subscribe 监听状态更新
49
+ */
50
+ export declare class Sidebar {
51
+ /** 外层容器 */
52
+ private container;
53
+ /** EditorCore 引用 */
54
+ private core;
55
+ /** 配置选项 */
56
+ private options;
57
+ /** 已折叠的分组 Set */
58
+ private collapsedGroups;
59
+ /** 当前搜索关键字 */
60
+ private searchKeyword;
61
+ /** 根 DOM 元素 */
62
+ private rootEl;
63
+ /** 状态订阅取消函数 */
64
+ private unsubscribe;
65
+ constructor(container: HTMLElement, options: SidebarOptions);
66
+ /**
67
+ * 挂载侧边栏 DOM 到容器
68
+ */
69
+ private mount;
70
+ /**
71
+ * 销毁侧边栏, 清理所有 DOM 和事件
72
+ */
73
+ destroy(): void;
74
+ /**
75
+ * 渲染所有组件分组
76
+ * @param groups - 原始组件分组列表
77
+ */
78
+ private renderGroups;
79
+ /**
80
+ * 创建单个分组 DOM 元素
81
+ * @param group - 组件分组定义
82
+ * @returns 分组 HTMLElement
83
+ */
84
+ private createGroupElement;
85
+ /**
86
+ * 创建单个组件 DOM 元素 (可拖拽)
87
+ * @param comp - 组件定义
88
+ * @param groupColor - 分组颜色
89
+ * @returns 组件 HTMLElement
90
+ */
91
+ private createComponentElement;
92
+ /**
93
+ * 根据搜索关键字过滤组件分组
94
+ * @param groups - 原始分组列表
95
+ * @returns 过滤后的分组列表 (空分组会被移除)
96
+ */
97
+ private filterGroups;
98
+ /**
99
+ * 手动触发刷新
100
+ */
101
+ refresh(): void;
102
+ /**
103
+ * 展开所有分组
104
+ */
105
+ expandAll(): void;
106
+ /**
107
+ * 折叠所有分组
108
+ */
109
+ collapseAll(): void;
110
+ /**
111
+ * 获取根 DOM 元素
112
+ */
113
+ getElement(): HTMLElement | null;
114
+ }
115
+ //# sourceMappingURL=Sidebar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sidebar.d.ts","sourceRoot":"","sources":["../../src/components/Sidebar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAO7D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,yBAAyB;IACzB,IAAI,EAAE,UAAU,CAAA;IAChB,uBAAuB;IACvB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,8BAA8B;IAC9B,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,eAAe;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAMD;;;;;;;;;GASG;AACH,qBAAa,OAAO;IAClB,WAAW;IACX,OAAO,CAAC,SAAS,CAAa;IAC9B,oBAAoB;IACpB,OAAO,CAAC,IAAI,CAAY;IACxB,WAAW;IACX,OAAO,CAAC,OAAO,CAA0B;IACzC,iBAAiB;IACjB,OAAO,CAAC,eAAe,CAAyB;IAChD,cAAc;IACd,OAAO,CAAC,aAAa,CAAK;IAC1B,eAAe;IACf,OAAO,CAAC,MAAM,CAA2B;IACzC,eAAe;IACf,OAAO,CAAC,WAAW,CAA4B;gBAEnC,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc;IA8B3D;;OAEG;IACH,OAAO,CAAC,KAAK;IA2Cb;;OAEG;IACH,OAAO,IAAI,IAAI;IAef;;;OAGG;IACH,OAAO,CAAC,YAAY;IA2BpB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IA2D1B;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IA4C9B;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAmBpB;;OAEG;IACH,OAAO,IAAI,IAAI;IAKf;;OAEG;IACH,SAAS,IAAI,IAAI;IAKjB;;OAEG;IACH,WAAW,IAAI,IAAI;IAQnB;;OAEG;IACH,UAAU,IAAI,WAAW,GAAG,IAAI;CAGjC"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @file components/Toolbar.ts
3
+ * @description 编辑器工具栏 — 纯 DOM 实现 (零框架依赖)
4
+ *
5
+ * 功能:
6
+ * - 撤销 / 重做 按钮
7
+ * - 删除选中元素
8
+ * - 保存 / 全屏切换
9
+ * - 缩放控制 (放大 / 缩小 / 适应画布)
10
+ * - 自定义按钮扩展
11
+ * - 自动监听 EditorCore 状态, 按钮禁用联动
12
+ *
13
+ * 使用方式:
14
+ * ```typescript
15
+ * import { Toolbar } from '@huanban/editor-ui'
16
+ *
17
+ * const toolbar = new Toolbar(container, { core: editorCore })
18
+ * toolbar.destroy()
19
+ * ```
20
+ */
21
+ import type { EditorCore } from '@huanban/rulego-editor-core';
22
+ /**
23
+ * 工具栏按钮定义
24
+ */
25
+ export interface ToolbarButton {
26
+ /** 按钮唯一标识 */
27
+ id: string;
28
+ /** 按钮显示文本 (可选, 图标模式下可不设) */
29
+ label?: string;
30
+ /** 按钮图标 (emoji 或 SVG 字符串) */
31
+ icon: string;
32
+ /** 提示文本 (tooltip) */
33
+ title: string;
34
+ /** 是否禁用 (可以是函数, 动态判断) */
35
+ disabled?: boolean | (() => boolean);
36
+ /** 分隔线 (在此按钮之前插入分隔线) */
37
+ divider?: boolean;
38
+ /** 点击回调 */
39
+ onClick: () => void;
40
+ }
41
+ /**
42
+ * Toolbar 配置选项
43
+ */
44
+ export interface ToolbarOptions {
45
+ /** EditorCore 实例 (必须) */
46
+ core: EditorCore;
47
+ /** 自定义按钮列表 (追加到内置按钮之后) */
48
+ extraButtons?: ToolbarButton[];
49
+ /** 是否显示缩放控制, 默认 true */
50
+ showZoom?: boolean;
51
+ /** 高度 (CSS值), 默认 '40px' */
52
+ height?: string;
53
+ }
54
+ /**
55
+ * 纯 DOM 实现的编辑器工具栏
56
+ *
57
+ * 内置按钮:
58
+ * - ↩ 撤销 | ↪ 重做 | 🗑 删除 | 💾 保存 | ⛶ 全屏
59
+ * - 🔍+ 放大 | 🔍- 缩小 | 🔲 适应画布
60
+ *
61
+ * 通过 EditorCore.store.subscribe 监听 canUndo/canRedo 等状态,
62
+ * 动态切换按钮的 disabled 状态。
63
+ */
64
+ export declare class Toolbar {
65
+ /** 外层容器 */
66
+ private container;
67
+ /** EditorCore 引用 */
68
+ private core;
69
+ /** 配置选项 */
70
+ private options;
71
+ /** 根 DOM 元素 */
72
+ private rootEl;
73
+ /** 状态订阅取消函数 */
74
+ private unsubscribe;
75
+ /** 按钮 DOM 映射 (id => element) */
76
+ private buttonElements;
77
+ constructor(container: HTMLElement, options: ToolbarOptions);
78
+ /**
79
+ * 挂载工具栏 DOM
80
+ */
81
+ private mount;
82
+ /**
83
+ * 销毁工具栏
84
+ */
85
+ destroy(): void;
86
+ /**
87
+ * 获取内置按钮列表
88
+ * @returns 内置按钮定义数组
89
+ */
90
+ private getBuiltinButtons;
91
+ /**
92
+ * 根据 EditorCore 的状态更新按钮的 disabled 属性
93
+ */
94
+ private updateButtonStates;
95
+ /**
96
+ * 获取根 DOM 元素
97
+ */
98
+ getElement(): HTMLElement | null;
99
+ /**
100
+ * 动态添加自定义按钮
101
+ * @param button - 按钮定义
102
+ */
103
+ addButton(button: ToolbarButton): void;
104
+ }
105
+ //# sourceMappingURL=Toolbar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAM7D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,aAAa;IACb,EAAE,EAAE,MAAM,CAAA;IACV,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,yBAAyB;IACzB,QAAQ,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,CAAA;IACpC,wBAAwB;IACxB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,WAAW;IACX,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,yBAAyB;IACzB,IAAI,EAAE,UAAU,CAAA;IAChB,0BAA0B;IAC1B,YAAY,CAAC,EAAE,aAAa,EAAE,CAAA;IAC9B,wBAAwB;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAMD;;;;;;;;;GASG;AACH,qBAAa,OAAO;IAClB,WAAW;IACX,OAAO,CAAC,SAAS,CAAa;IAC9B,oBAAoB;IACpB,OAAO,CAAC,IAAI,CAAY;IACxB,WAAW;IACX,OAAO,CAAC,OAAO,CAA0B;IACzC,eAAe;IACf,OAAO,CAAC,MAAM,CAA2B;IACzC,eAAe;IACf,OAAO,CAAC,WAAW,CAA4B;IAC/C,gCAAgC;IAChC,OAAO,CAAC,cAAc,CAA4C;gBAEtD,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc;IAuB3D;;OAEG;IACH,OAAO,CAAC,KAAK;IAyDb;;OAEG;IACH,OAAO,IAAI,IAAI;IAgBf;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAyEzB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAc1B;;OAEG;IACH,UAAU,IAAI,WAAW,GAAG,IAAI;IAIhC;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;CAevC"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class u{constructor(e,t){this.collapsedGroups=new Set,this.searchKeyword="",this.rootEl=null,this.unsubscribe=null,this.container=e,this.core=t.core,this.options={core:t.core,searchable:t.searchable??!0,defaultCollapsed:t.defaultCollapsed??!1,searchPlaceholder:t.searchPlaceholder||this.core.i18n.t("sidebar.searchPlaceholder")||"搜索组件...",width:t.width??"220px"},this.mount(),this.unsubscribe=this.core.store.select(n=>n.componentGroups).subscribe(n=>{this.renderGroups(n)})}mount(){if(this.rootEl=document.createElement("div"),this.rootEl.className="rulego-sidebar",this.rootEl.style.width=this.options.width,this.options.searchable){const n=document.createElement("div");n.className="rulego-sidebar__search-wrapper";const o=document.createElement("span");o.className="rulego-sidebar__search-icon",o.innerHTML="🔍";const s=document.createElement("input");s.type="text",s.className="rulego-sidebar__search",s.placeholder=this.options.searchPlaceholder,s.addEventListener("input",i=>{this.searchKeyword=i.target.value.toLowerCase();const l=this.core.store.getState().componentGroups;this.renderGroups(l)}),n.appendChild(o),n.appendChild(s),this.rootEl.appendChild(n)}const e=document.createElement("div");e.className="rulego-sidebar__groups",this.rootEl.appendChild(e),this.container.appendChild(this.rootEl);const t=this.core.store.getState().componentGroups;t&&t.length>0&&this.renderGroups(t)}destroy(){this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null),this.rootEl&&this.rootEl.parentNode&&this.rootEl.parentNode.removeChild(this.rootEl),this.rootEl=null}renderGroups(e){if(!this.rootEl)return;const t=this.rootEl.querySelector(".rulego-sidebar__groups");if(!t)return;t.innerHTML="";const n=this.filterGroups(e);if(n.length===0){const o=document.createElement("div");o.className="rulego-sidebar__empty",o.textContent=this.core.i18n.t("sidebar.noResults")||"无匹配组件",t.appendChild(o);return}for(const o of n){const s=this.createGroupElement(o);t.appendChild(s)}}createGroupElement(e){const t=document.createElement("div");t.className="rulego-sidebar__group";const n=document.createElement("div");n.className="rulego-sidebar__group-header",n.style.borderLeftColor=e.color||"#ccc";const o=this.collapsedGroups.has(e.category),s=document.createElement("span");s.className=`rulego-sidebar__arrow ${o?"rulego-sidebar__arrow--collapsed":""}`,s.textContent="▼";const i=document.createElement("span");i.className="rulego-sidebar__group-label",i.textContent=e.label;const l=document.createElement("span");if(l.className="rulego-sidebar__group-count",l.textContent=`${e.components.length}`,n.appendChild(s),n.appendChild(i),n.appendChild(l),n.addEventListener("click",()=>{this.collapsedGroups.has(e.category)?this.collapsedGroups.delete(e.category):this.collapsedGroups.add(e.category);const r=this.core.store.getState().componentGroups;this.renderGroups(r)}),t.appendChild(n),!o){const r=document.createElement("div");r.className="rulego-sidebar__component-list";for(const c of e.components){const d=this.createComponentElement(c,e.color);r.appendChild(d)}t.appendChild(r)}return t}createComponentElement(e,t){const n=document.createElement("div");n.className="rulego-sidebar__component",n.setAttribute("draggable","true"),n.title=e.desc||e.label;const o=document.createElement("span");o.className="rulego-sidebar__component-color",o.style.backgroundColor=e.color||t||"#ccc";const s=document.createElement("span");return s.className="rulego-sidebar__component-name",s.textContent=e.label,n.appendChild(o),n.appendChild(s),n.addEventListener("dragstart",i=>{i.dataTransfer&&(i.dataTransfer.setData("application/rulego-component",JSON.stringify({type:e.type,category:e.category,label:e.label,color:e.color||t})),i.dataTransfer.effectAllowed="copy"),n.classList.add("rulego-sidebar__component--dragging")}),n.addEventListener("dragend",()=>{n.classList.remove("rulego-sidebar__component--dragging")}),n}filterGroups(e){return this.searchKeyword?e.map(t=>({...t,components:t.components.filter(n=>n.label.toLowerCase().includes(this.searchKeyword)||n.type.toLowerCase().includes(this.searchKeyword)||n.desc&&n.desc.toLowerCase().includes(this.searchKeyword))})).filter(t=>t.components.length>0):e}refresh(){const e=this.core.store.getState().componentGroups;this.renderGroups(e)}expandAll(){this.collapsedGroups.clear(),this.refresh()}collapseAll(){const e=this.core.store.getState().componentGroups;for(const t of e)this.collapsedGroups.add(t.category);this.refresh()}getElement(){return this.rootEl}}class h{constructor(e,t){this.rootEl=null,this.unsubscribe=null,this.buttonElements=new Map,this.container=e,this.core=t.core,this.options={core:t.core,extraButtons:t.extraButtons??[],showZoom:t.showZoom??!0,height:t.height??"40px"},this.mount(),this.unsubscribe=this.core.store.subscribe(()=>{this.updateButtonStates()})}mount(){this.rootEl=document.createElement("div"),this.rootEl.className="rulego-toolbar",this.rootEl.style.height=this.options.height;const e=this.getBuiltinButtons();this.options.extraButtons.length>0&&(e.push({id:"__divider_extra",icon:"",title:"",divider:!0,onClick:()=>{}}),e.push(...this.options.extraButtons));for(const t of e){if(t.divider){const o=document.createElement("span");o.className="rulego-toolbar__divider",this.rootEl.appendChild(o)}if(!t.icon&&t.divider)continue;const n=document.createElement("button");if(n.className="rulego-toolbar__button",n.title=t.title,n.innerHTML=t.icon,t.label){const o=document.createElement("span");o.className="rulego-toolbar__button-label",o.textContent=t.label,n.appendChild(o)}n.addEventListener("click",o=>{o.preventDefault(),o.stopPropagation(),t.onClick()}),this.buttonElements.set(t.id,n),this.rootEl.appendChild(n)}this.container.appendChild(this.rootEl),this.updateButtonStates()}destroy(){this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null),this.rootEl&&this.rootEl.parentNode&&this.rootEl.parentNode.removeChild(this.rootEl),this.rootEl=null,this.buttonElements.clear()}getBuiltinButtons(){const e=this.core.i18n,t=[{id:"undo",icon:"↩",title:e.t("toolbar.undo")||"撤销",disabled:()=>!this.core.store.getState().canUndo,onClick:()=>this.core.eventBus.emit("editor:reset")},{id:"redo",icon:"↪",title:e.t("toolbar.redo")||"重做",disabled:()=>!this.core.store.getState().canRedo,onClick:()=>{}},{id:"delete",icon:"🗑",title:e.t("toolbar.delete")||"删除选中",divider:!0,onClick:()=>this.core.eventBus.emit("editor:delete-selected")},{id:"save",icon:"💾",title:e.t("toolbar.save")||"保存",onClick:()=>this.core.eventBus.emit("editor:save",{data:{},success:!1})},{id:"fullscreen",icon:"⛶",title:e.t("toolbar.fullscreen")||"全屏",onClick:()=>this.core.eventBus.emit("editor:fullscreen")}];return this.options.showZoom&&t.push({id:"zoom-in",icon:"🔍+",title:e.t("toolbar.zoomIn")||"放大",divider:!0,onClick:()=>this.core.eventBus.emit("canvas:zoom",{scale:1.1})},{id:"zoom-out",icon:"🔍-",title:e.t("toolbar.zoomOut")||"缩小",onClick:()=>this.core.eventBus.emit("canvas:zoom",{scale:.9})},{id:"zoom-fit",icon:"🔲",title:e.t("toolbar.fitView")||"适应画布",onClick:()=>this.core.eventBus.emit("canvas:zoom",{scale:1})}),t}updateButtonStates(){for(const[e,t]of this.buttonElements){const n=this.getBuiltinButtons().find(o=>o.id===e);n&&typeof n.disabled=="function"&&(t.disabled=n.disabled(),t.classList.toggle("rulego-toolbar__button--disabled",t.disabled))}}getElement(){return this.rootEl}addButton(e){if(!this.rootEl)return;const t=document.createElement("button");t.className="rulego-toolbar__button",t.title=e.title,t.innerHTML=e.icon,t.addEventListener("click",n=>{n.preventDefault(),e.onClick()}),this.buttonElements.set(e.id,t),this.rootEl.appendChild(t)}}class p{constructor(e,t){this.rootEl=null,this.formContainer=null,this.titleEl=null,this.unsubscribe=null,this.eventCleanups=[],this.currentNodeId=null,this.container=e,this.core=t.core,this.options={core:t.core,title:t.title||this.core.i18n.t("panel.title")||"属性编辑",width:t.width??"320px",position:t.position??"right"},this.mount(),this.bindEvents()}mount(){this.rootEl=document.createElement("div"),this.rootEl.className=`rulego-property-panel rulego-property-panel--${this.options.position}`,this.rootEl.style.width=this.options.width;const e=document.createElement("div");e.className="rulego-property-panel__header",this.titleEl=document.createElement("h3"),this.titleEl.className="rulego-property-panel__title",this.titleEl.textContent=this.options.title;const t=document.createElement("button");t.className="rulego-property-panel__close",t.textContent="✕",t.addEventListener("click",()=>this.clearPanel()),e.appendChild(this.titleEl),e.appendChild(t),this.rootEl.appendChild(e),this.formContainer=document.createElement("div"),this.formContainer.className="rulego-property-panel__form",this.rootEl.appendChild(this.formContainer),this.rootEl.style.display="none",this.container.appendChild(this.rootEl)}bindEvents(){this.unsubscribe=this.core.store.subscribe(t=>{const n=t.currentNodeView,o=t.currentNodeModel;n&&o&&Object.keys(o).length>0&&this.showPanel(n,o)});const e=this.core.eventBus.on("blank:click",()=>{this.clearPanel()});this.eventCleanups.push(e)}destroy(){this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null);for(const e of this.eventCleanups)e();this.eventCleanups=[],this.rootEl&&this.rootEl.parentNode&&this.rootEl.parentNode.removeChild(this.rootEl),this.rootEl=null,this.formContainer=null}showPanel(e,t){if(!(!this.rootEl||!this.formContainer||!this.titleEl)&&(this.rootEl.style.display="flex",this.titleEl.textContent=e.label||this.options.title,this.formContainer.innerHTML="",this.renderBasicInfo(t),e.fields&&e.fields.length>0))for(const n of e.fields){const o=this.createFieldElement(n,t);this.formContainer.appendChild(o)}}clearPanel(){!this.rootEl||!this.formContainer||(this.formContainer.innerHTML="",this.rootEl.style.display="none",this.currentNodeId=null)}renderBasicInfo(e){if(!this.formContainer)return;const t=document.createElement("div");t.className="rulego-property-panel__section";const n=this.createTextField({name:"name",type:"string",defaultValue:"",label:this.core.i18n.t("panel.nodeName")||"节点名称",desc:"",validate:"",fields:null},e);t.appendChild(n),this.formContainer.appendChild(t)}createFieldElement(e,t){const n=document.createElement("div");n.className="rulego-property-panel__field";const o=document.createElement("label");o.className="rulego-property-panel__label",o.textContent=e.label||e.name,e.desc&&(o.title=e.desc),n.appendChild(o);let s;switch(e.type){case"bool":s=this.createBoolField(e,t);break;case"select":s=this.createSelectField(e,t);break;case"code":case"json":s=this.createTextareaField(e,t);break;case"int":s=this.createNumberField(e,t);break;default:s=this.createTextField(e,t);break}if(n.appendChild(s),e.desc){const i=document.createElement("span");i.className="rulego-property-panel__desc",i.textContent=e.desc,n.appendChild(i)}return n}createTextField(e,t){const n=document.createElement("input");return n.type="text",n.className="rulego-property-panel__input",n.name=e.name,n.value=String(this.getModelValue(e,t)??""),n.placeholder=e.desc||"",n.addEventListener("change",()=>{this.handleFieldChange(e.name,n.value)}),n}createNumberField(e,t){const n=document.createElement("input");return n.type="number",n.className="rulego-property-panel__input",n.name=e.name,n.value=String(this.getModelValue(e,t)??0),n.addEventListener("change",()=>{this.handleFieldChange(e.name,Number(n.value))}),n}createBoolField(e,t){const n=document.createElement("div");n.className="rulego-property-panel__switch-wrapper";const o=`field-${e.name}-${Date.now()}`,s=document.createElement("input");s.type="checkbox",s.className="rulego-property-panel__checkbox",s.id=o,s.checked=!!this.getModelValue(e,t);const i=document.createElement("label");return i.className="rulego-property-panel__switch",i.htmlFor=o,s.addEventListener("change",()=>{this.handleFieldChange(e.name,s.checked)}),n.appendChild(s),n.appendChild(i),n}createSelectField(e,t){const n=document.createElement("select");n.className="rulego-property-panel__select",n.name=e.name;const o=String(this.getModelValue(e,t)??"");if(e.options&&e.options.length>0)for(const s of e.options){const i=document.createElement("option");i.value=s.value,i.textContent=s.label,s.value===o&&(i.selected=!0),n.appendChild(i)}return n.addEventListener("change",()=>{this.handleFieldChange(e.name,n.value)}),n}createTextareaField(e,t){const n=document.createElement("textarea");return n.className="rulego-property-panel__textarea",n.name=e.name,n.rows=6,n.value=String(this.getModelValue(e,t)??""),n.placeholder=e.desc||"",(e.type==="code"||e.type==="json")&&(n.spellcheck=!1,n.style.fontFamily="monospace"),n.addEventListener("change",()=>{this.handleFieldChange(e.name,n.value)}),n}getModelValue(e,t){const n=t.configuration;return n&&e.name in n?n[e.name]:e.name in t?t[e.name]:e.defaultValue}handleFieldChange(e,t){this.core.eventBus.emit("node:property-update",{id:this.currentNodeId||"",properties:{[e]:t}}),this.core.store.setState({isDirty:!0})}getElement(){return this.rootEl}setCurrentNodeId(e){this.currentNodeId=e}show(e,t,n){n&&(this.currentNodeId=n),this.showPanel(e,t)}hide(){this.clearPanel()}}class m{constructor(e,t){this.menuEl=null,this.currentContext=null,this.outsideClickHandler=null,this.container=e,this.core=t.core;const n=t.includeBuiltin!==!1?this.getBuiltinItems():[];this.items=[...n,...t.items??[]],this.mount(),this.bindEvents()}mount(){this.menuEl=document.createElement("div"),this.menuEl.className="rulego-context-menu",this.menuEl.style.display="none",document.body.appendChild(this.menuEl)}bindEvents(){this.container.addEventListener("contextmenu",e=>{e.preventDefault(),this.handleContextMenu(e)}),this.outsideClickHandler=e=>{this.menuEl&&!this.menuEl.contains(e.target)&&this.hide()},document.addEventListener("click",this.outsideClickHandler),document.addEventListener("keydown",e=>{e.key==="Escape"&&this.hide()})}destroy(){this.outsideClickHandler&&(document.removeEventListener("click",this.outsideClickHandler),this.outsideClickHandler=null),this.menuEl&&this.menuEl.parentNode&&this.menuEl.parentNode.removeChild(this.menuEl),this.menuEl=null}handleContextMenu(e){const t=e.target,n=this.resolveContext(t,e);this.currentContext=n;const o=this.items.filter(s=>{const i=Array.isArray(s.target)?s.target:[s.target];return i.includes(n.type)||i.includes("all")});o.length!==0&&this.renderMenu(o,e.clientX,e.clientY)}resolveContext(e,t){const n=e.closest(".lf-node");if(n)return{type:"node",id:n.getAttribute("data-node-id")||void 0,position:{x:t.clientX,y:t.clientY}};const o=e.closest(".lf-edge");return o?{type:"edge",id:o.getAttribute("data-edge-id")||void 0,position:{x:t.clientX,y:t.clientY}}:{type:"canvas",position:{x:t.clientX,y:t.clientY}}}renderMenu(e,t,n){if(this.menuEl){this.menuEl.innerHTML="";for(const o of e){const s=document.createElement("div");s.className="rulego-context-menu__item";const i=typeof o.disabled=="function"?o.disabled():o.disabled;if(i&&s.classList.add("rulego-context-menu__item--disabled"),o.icon){const r=document.createElement("span");r.className="rulego-context-menu__icon",r.innerHTML=o.icon,s.appendChild(r)}const l=document.createElement("span");if(l.className="rulego-context-menu__label",l.textContent=o.label,s.appendChild(l),i||s.addEventListener("click",()=>{this.currentContext&&o.onClick(this.currentContext),this.hide()}),this.menuEl.appendChild(s),o.separator){const r=document.createElement("div");r.className="rulego-context-menu__separator",this.menuEl.appendChild(r)}}this.menuEl.style.left=`${t}px`,this.menuEl.style.top=`${n}px`,this.menuEl.style.display="block",requestAnimationFrame(()=>{if(!this.menuEl)return;const o=this.menuEl.getBoundingClientRect();o.right>window.innerWidth&&(this.menuEl.style.left=`${t-o.width}px`),o.bottom>window.innerHeight&&(this.menuEl.style.top=`${n-o.height}px`)})}}hide(){this.menuEl&&(this.menuEl.style.display="none"),this.currentContext=null}getBuiltinItems(){const e=this.core.i18n;return[{id:"edit",label:e.t("contextMenu.edit")||"编辑",icon:"✏️",target:"node",onClick:t=>{t.id&&this.core.eventBus.emit("editor:show-edit-panel")}},{id:"delete",label:e.t("contextMenu.delete")||"删除",icon:"🗑️",target:["node","edge"],separator:!0,onClick:t=>{t.id&&this.core.eventBus.emit("node:delete",{id:t.id})}},{id:"copy",label:e.t("contextMenu.copy")||"复制",icon:"📋",target:"node",onClick:()=>{}},{id:"select-all",label:e.t("contextMenu.selectAll")||"全选",icon:"⬜",target:"canvas",onClick:()=>{}}]}addItem(e){this.items.push(e)}removeItem(e){this.items=this.items.filter(t=>t.id!==e)}getElement(){return this.menuEl}}exports.ContextMenu=m;exports.PropertyPanel=p;exports.Sidebar=u;exports.Toolbar=h;
2
+ //# sourceMappingURL=index.cjs.js.map