@huanban/rulego-editor-ui 1.0.0 → 1.0.15

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 ADDED
@@ -0,0 +1,190 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to the Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by the Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding any notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ Copyright 2024 OpenClaw
179
+
180
+ Licensed under the Apache License, Version 2.0 (the "License");
181
+ you may not use this file except in compliance with the License.
182
+ You may obtain a copy of the License at
183
+
184
+ http://www.apache.org/licenses/LICENSE-2.0
185
+
186
+ Unless required by applicable law or agreed to in writing, software
187
+ distributed under the License is distributed on an "AS IS" BASIS,
188
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+ See the License for the specific language governing permissions and
190
+ limitations under the License.
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # @huanban/editor-ui
1
+ # @huanban/rulego-editor-ui
2
2
 
3
3
  > RuleGo 编辑器 UI Kit — 纯 DOM 组件集 (零框架依赖)
4
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)
5
+ [![npm version](https://img.shields.io/npm/v/@huanban/rulego-editor-ui.svg)](https://www.npmjs.com/package/@huanban/rulego-editor-ui)
6
+ [![license](https://img.shields.io/npm/l/@huanban/rulego-editor-ui.svg)](https://github.com/openclaw/rulego-editor/blob/main/LICENSE)
7
7
 
8
8
  ## ✨ 特性
9
9
 
@@ -15,14 +15,14 @@
15
15
  ## 📦 安装
16
16
 
17
17
  ```bash
18
- npm install @huanban/editor-ui @huanban/editor-core
18
+ npm install @huanban/rulego-editor-ui @huanban/rulego-editor-core
19
19
  ```
20
20
 
21
21
  ## 🚀 快速开始
22
22
 
23
23
  ```typescript
24
- import { EditorCore } from '@huanban/editor-core'
25
- import { Sidebar, Toolbar, PropertyPanel, ContextMenu } from '@huanban/editor-ui'
24
+ import { EditorCore } from '@huanban/rulego-editor-core'
25
+ import { Sidebar, Toolbar, PropertyPanel, ContextMenu } from '@huanban/rulego-editor-ui'
26
26
 
27
27
  const core = new EditorCore({ container: document.getElementById('editor')! })
28
28
 
@@ -47,9 +47,9 @@ new PropertyPanel(document.getElementById('panel')!, { core })
47
47
 
48
48
  ## 🔗 相关包
49
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 适配层
50
+ - [`@huanban/rulego-editor-core`](https://www.npmjs.com/package/@huanban/rulego-editor-core) — 核心引擎 (必需)
51
+ - [`@huanban/rulego-editor-react`](https://www.npmjs.com/package/@huanban/rulego-editor-react) — React 适配层
52
+ - [`@huanban/rulego-editor-vue`](https://www.npmjs.com/package/@huanban/rulego-editor-vue) — Vue 3 适配层
53
53
 
54
54
  ## 📄 协议
55
55
 
package/dist/index.cjs.js CHANGED
@@ -1,2 +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;
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.push({id:"auto-layout",icon:"📐",title:e.t("toolbar.autoLayout")||"自动排版",divider:!0,onClick:()=>this.core.eventBus.emit("editor:auto-layout")}),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
2
  //# sourceMappingURL=index.cjs.js.map
package/dist/index.esm.js CHANGED
@@ -286,7 +286,13 @@ class h {
286
286
  title: e.t("toolbar.fitView") || "适应画布",
287
287
  onClick: () => this.core.eventBus.emit("canvas:zoom", { scale: 1 })
288
288
  }
289
- ), t;
289
+ ), t.push({
290
+ id: "auto-layout",
291
+ icon: "📐",
292
+ title: e.t("toolbar.autoLayout") || "自动排版",
293
+ divider: !0,
294
+ onClick: () => this.core.eventBus.emit("editor:auto-layout")
295
+ }), t;
290
296
  }
291
297
  // ============================================================
292
298
  // 状态更新
package/dist/index.umd.js CHANGED
@@ -1,2 +1,2 @@
1
- (function(r,c){typeof exports=="object"&&typeof module<"u"?c(exports):typeof define=="function"&&define.amd?define(["exports"],c):(r=typeof globalThis<"u"?globalThis:r||self,c(r.RuleGoEditorUI={}))})(this,function(r){"use strict";class c{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 a=this.core.store.getState().componentGroups;this.renderGroups(a)}),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 a=document.createElement("span");if(a.className="rulego-sidebar__group-count",a.textContent=`${e.components.length}`,n.appendChild(s),n.appendChild(i),n.appendChild(a),n.addEventListener("click",()=>{this.collapsedGroups.has(e.category)?this.collapsedGroups.delete(e.category):this.collapsedGroups.add(e.category);const l=this.core.store.getState().componentGroups;this.renderGroups(l)}),t.appendChild(n),!o){const l=document.createElement("div");l.className="rulego-sidebar__component-list";for(const m of e.components){const E=this.createComponentElement(m,e.color);l.appendChild(E)}t.appendChild(l)}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 u{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 h{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 p{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 l=document.createElement("span");l.className="rulego-context-menu__icon",l.innerHTML=o.icon,s.appendChild(l)}const a=document.createElement("span");if(a.className="rulego-context-menu__label",a.textContent=o.label,s.appendChild(a),i||s.addEventListener("click",()=>{this.currentContext&&o.onClick(this.currentContext),this.hide()}),this.menuEl.appendChild(s),o.separator){const l=document.createElement("div");l.className="rulego-context-menu__separator",this.menuEl.appendChild(l)}}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}}r.ContextMenu=p,r.PropertyPanel=h,r.Sidebar=c,r.Toolbar=u,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})});
1
+ (function(r,c){typeof exports=="object"&&typeof module<"u"?c(exports):typeof define=="function"&&define.amd?define(["exports"],c):(r=typeof globalThis<"u"?globalThis:r||self,c(r.RuleGoEditorUI={}))})(this,function(r){"use strict";class c{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 a=this.core.store.getState().componentGroups;this.renderGroups(a)}),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 a=document.createElement("span");if(a.className="rulego-sidebar__group-count",a.textContent=`${e.components.length}`,n.appendChild(s),n.appendChild(i),n.appendChild(a),n.addEventListener("click",()=>{this.collapsedGroups.has(e.category)?this.collapsedGroups.delete(e.category):this.collapsedGroups.add(e.category);const l=this.core.store.getState().componentGroups;this.renderGroups(l)}),t.appendChild(n),!o){const l=document.createElement("div");l.className="rulego-sidebar__component-list";for(const m of e.components){const E=this.createComponentElement(m,e.color);l.appendChild(E)}t.appendChild(l)}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 u{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.push({id:"auto-layout",icon:"📐",title:e.t("toolbar.autoLayout")||"自动排版",divider:!0,onClick:()=>this.core.eventBus.emit("editor:auto-layout")}),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 h{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 p{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 l=document.createElement("span");l.className="rulego-context-menu__icon",l.innerHTML=o.icon,s.appendChild(l)}const a=document.createElement("span");if(a.className="rulego-context-menu__label",a.textContent=o.label,s.appendChild(a),i||s.addEventListener("click",()=>{this.currentContext&&o.onClick(this.currentContext),this.hide()}),this.menuEl.appendChild(s),o.separator){const l=document.createElement("div");l.className="rulego-context-menu__separator",this.menuEl.appendChild(l)}}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}}r.ContextMenu=p,r.PropertyPanel=h,r.Sidebar=c,r.Toolbar=u,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})});
2
2
  //# sourceMappingURL=index.umd.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huanban/rulego-editor-ui",
3
- "version": "1.0.0",
3
+ "version": "1.0.15",
4
4
  "description": "RuleGo 编辑器 UI Kit - 纯 DOM 组件 (零框架依赖)",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
@@ -15,7 +15,11 @@
15
15
  "./style.css": "./dist/style.css"
16
16
  },
17
17
  "files": [
18
- "dist"
18
+ "dist/**/*.js",
19
+ "dist/**/*.d.ts",
20
+ "dist/**/*.css",
21
+ "!dist/**/*.js.map",
22
+ "!dist/**/*.d.ts.map"
19
23
  ],
20
24
  "publishConfig": {
21
25
  "registry": "https://registry.npmjs.org",
@@ -27,19 +31,13 @@
27
31
  "directory": "packages/editor-ui"
28
32
  },
29
33
  "homepage": "https://github.com/openclaw/rulego-editor#readme",
30
- "scripts": {
31
- "dev": "vite build --watch",
32
- "build": "vite build && tsc --emitDeclarationOnly --outDir dist",
33
- "lint": "tsc --noEmit",
34
- "clean": "rm -rf dist"
35
- },
36
34
  "peerDependencies": {
37
35
  "@huanban/rulego-editor-core": ">=0.1.0"
38
36
  },
39
37
  "devDependencies": {
40
- "@huanban/rulego-editor-core": "workspace:*",
41
38
  "typescript": "^5.4.0",
42
- "vite": "^5.4.0"
39
+ "vite": "^5.4.0",
40
+ "@huanban/rulego-editor-core": "1.0.15"
43
41
  },
44
42
  "keywords": [
45
43
  "rulego",
@@ -50,5 +48,11 @@
50
48
  "property-panel"
51
49
  ],
52
50
  "author": "HuanBan Team",
53
- "license": "Apache-2.0"
54
- }
51
+ "license": "Apache-2.0",
52
+ "scripts": {
53
+ "dev": "vite build --watch",
54
+ "build": "vite build && tsc --emitDeclarationOnly --outDir dist",
55
+ "lint": "tsc --noEmit",
56
+ "clean": "rm -rf dist"
57
+ }
58
+ }
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/components/Sidebar.ts","../src/components/Toolbar.ts","../src/components/PropertyPanel.ts","../src/components/ContextMenu.ts"],"sourcesContent":["/**\n * @file components/Sidebar.ts\n * @description 组件侧边栏 — 纯 DOM 实现 (零框架依赖)\n *\n * 功能:\n * - 按分类分组展示可用组件\n * - 支持关键字搜索过滤\n * - 支持分组折叠/展开\n * - 拖拽组件到画布 (DnD)\n * - 自动监听 EditorCore 状态变化, 更新组件列表\n *\n * 使用方式:\n * ```typescript\n * import { Sidebar } from '@huanban/editor-ui'\n *\n * const sidebar = new Sidebar(container, { core: editorCore })\n * // 卸载时\n * sidebar.destroy()\n * ```\n *\n * @see ComponentGroup — 组件分组定义\n * @see EditorCore — 编辑器核心引擎\n */\n\nimport type { EditorCore } from '@huanban/rulego-editor-core'\nimport type { ComponentDefinition, ComponentGroup } from '@huanban/rulego-editor-core'\n\n// ============================================================\n// 配置接口\n// ============================================================\n\n/**\n * Sidebar 配置选项\n */\nexport interface SidebarOptions {\n /** EditorCore 实例 (必须) */\n core: EditorCore\n /** 是否显示搜索框, 默认 true */\n searchable?: boolean\n /** 默认折叠状态, 默认 false (全部展开) */\n defaultCollapsed?: boolean\n /** 搜索框占位符文本 */\n searchPlaceholder?: string\n /** 宽度 (CSS值), 默认 '220px' */\n width?: string\n}\n\n// ============================================================\n// Sidebar 组件\n// ============================================================\n\n/**\n * 纯 DOM 实现的组件侧边栏\n *\n * 内部维护:\n * - componentGroups: 当前显示的组件分组\n * - collapsedGroups: 折叠状态 Set\n * - searchKeyword: 搜索关键字\n *\n * 通过 EditorCore.store.subscribe 监听状态更新\n */\nexport class Sidebar {\n /** 外层容器 */\n private container: HTMLElement\n /** EditorCore 引用 */\n private core: EditorCore\n /** 配置选项 */\n private options: Required<SidebarOptions>\n /** 已折叠的分组 Set */\n private collapsedGroups: Set<string> = new Set()\n /** 当前搜索关键字 */\n private searchKeyword = ''\n /** 根 DOM 元素 */\n private rootEl: HTMLElement | null = null\n /** 状态订阅取消函数 */\n private unsubscribe: (() => void) | null = null\n\n constructor(container: HTMLElement, options: SidebarOptions) {\n this.container = container\n this.core = options.core\n\n // 合并默认配置\n this.options = {\n core: options.core,\n searchable: options.searchable ?? true,\n defaultCollapsed: options.defaultCollapsed ?? false,\n searchPlaceholder: options.searchPlaceholder || this.core.i18n.t('sidebar.searchPlaceholder') || '搜索组件...',\n width: options.width ?? '220px',\n }\n\n // 初始渲染\n this.mount()\n\n // 监听组件列表变化\n this.unsubscribe = this.core.store.select(\n (state) => state.componentGroups as ComponentGroup[]\n ).subscribe(\n (groups: ComponentGroup[]) => {\n this.renderGroups(groups)\n }\n )\n }\n\n // ============================================================\n // 生命周期\n // ============================================================\n\n /**\n * 挂载侧边栏 DOM 到容器\n */\n private mount(): void {\n this.rootEl = document.createElement('div')\n this.rootEl.className = 'rulego-sidebar'\n this.rootEl.style.width = this.options.width\n\n // 搜索框\n if (this.options.searchable) {\n const searchWrapper = document.createElement('div')\n searchWrapper.className = 'rulego-sidebar__search-wrapper'\n\n const searchIcon = document.createElement('span')\n searchIcon.className = 'rulego-sidebar__search-icon'\n searchIcon.innerHTML = '🔍'\n\n const searchInput = document.createElement('input')\n searchInput.type = 'text'\n searchInput.className = 'rulego-sidebar__search'\n searchInput.placeholder = this.options.searchPlaceholder\n searchInput.addEventListener('input', (e) => {\n this.searchKeyword = (e.target as HTMLInputElement).value.toLowerCase()\n const currentGroups = this.core.store.getState().componentGroups as ComponentGroup[]\n this.renderGroups(currentGroups)\n })\n\n searchWrapper.appendChild(searchIcon)\n searchWrapper.appendChild(searchInput)\n this.rootEl.appendChild(searchWrapper)\n }\n\n // 分组容器\n const groupsContainer = document.createElement('div')\n groupsContainer.className = 'rulego-sidebar__groups'\n this.rootEl.appendChild(groupsContainer)\n\n this.container.appendChild(this.rootEl)\n\n // 首次渲染\n const groups = this.core.store.getState().componentGroups as ComponentGroup[]\n if (groups && groups.length > 0) {\n this.renderGroups(groups)\n }\n }\n\n /**\n * 销毁侧边栏, 清理所有 DOM 和事件\n */\n destroy(): void {\n if (this.unsubscribe) {\n this.unsubscribe()\n this.unsubscribe = null\n }\n if (this.rootEl && this.rootEl.parentNode) {\n this.rootEl.parentNode.removeChild(this.rootEl)\n }\n this.rootEl = null\n }\n\n // ============================================================\n // 渲染\n // ============================================================\n\n /**\n * 渲染所有组件分组\n * @param groups - 原始组件分组列表\n */\n private renderGroups(groups: ComponentGroup[]): void {\n if (!this.rootEl) return\n\n const groupsContainer = this.rootEl.querySelector('.rulego-sidebar__groups')\n if (!groupsContainer) return\n\n // 清空旧内容\n groupsContainer.innerHTML = ''\n\n // 按搜索关键字过滤\n const filteredGroups = this.filterGroups(groups)\n\n if (filteredGroups.length === 0) {\n const emptyEl = document.createElement('div')\n emptyEl.className = 'rulego-sidebar__empty'\n emptyEl.textContent = this.core.i18n.t('sidebar.noResults') || '无匹配组件'\n groupsContainer.appendChild(emptyEl)\n return\n }\n\n // 渲染每个分组\n for (const group of filteredGroups) {\n const groupEl = this.createGroupElement(group)\n groupsContainer.appendChild(groupEl)\n }\n }\n\n /**\n * 创建单个分组 DOM 元素\n * @param group - 组件分组定义\n * @returns 分组 HTMLElement\n */\n private createGroupElement(group: ComponentGroup): HTMLElement {\n const groupEl = document.createElement('div')\n groupEl.className = 'rulego-sidebar__group'\n\n // 分组标题 (可折叠)\n const headerEl = document.createElement('div')\n headerEl.className = 'rulego-sidebar__group-header'\n headerEl.style.borderLeftColor = group.color || '#ccc'\n\n const isCollapsed = this.collapsedGroups.has(group.category)\n\n // 折叠图标\n const arrowEl = document.createElement('span')\n arrowEl.className = `rulego-sidebar__arrow ${isCollapsed ? 'rulego-sidebar__arrow--collapsed' : ''}`\n arrowEl.textContent = '▼'\n\n // 分组名称\n const labelEl = document.createElement('span')\n labelEl.className = 'rulego-sidebar__group-label'\n labelEl.textContent = group.label\n\n // 组件数量\n const countEl = document.createElement('span')\n countEl.className = 'rulego-sidebar__group-count'\n countEl.textContent = `${group.components.length}`\n\n headerEl.appendChild(arrowEl)\n headerEl.appendChild(labelEl)\n headerEl.appendChild(countEl)\n\n // 点击折叠/展开\n headerEl.addEventListener('click', () => {\n if (this.collapsedGroups.has(group.category)) {\n this.collapsedGroups.delete(group.category)\n } else {\n this.collapsedGroups.add(group.category)\n }\n const currentGroups = this.core.store.getState().componentGroups as ComponentGroup[]\n this.renderGroups(currentGroups)\n })\n\n groupEl.appendChild(headerEl)\n\n // 组件列表 (折叠时不渲染)\n if (!isCollapsed) {\n const listEl = document.createElement('div')\n listEl.className = 'rulego-sidebar__component-list'\n\n for (const comp of group.components) {\n const compEl = this.createComponentElement(comp, group.color)\n listEl.appendChild(compEl)\n }\n\n groupEl.appendChild(listEl)\n }\n\n return groupEl\n }\n\n /**\n * 创建单个组件 DOM 元素 (可拖拽)\n * @param comp - 组件定义\n * @param groupColor - 分组颜色\n * @returns 组件 HTMLElement\n */\n private createComponentElement(comp: ComponentDefinition, groupColor: string): HTMLElement {\n const compEl = document.createElement('div')\n compEl.className = 'rulego-sidebar__component'\n compEl.setAttribute('draggable', 'true')\n compEl.title = comp.desc || comp.label\n\n // 颜色条\n const colorBar = document.createElement('span')\n colorBar.className = 'rulego-sidebar__component-color'\n colorBar.style.backgroundColor = comp.color || groupColor || '#ccc'\n\n // 组件名称\n const nameEl = document.createElement('span')\n nameEl.className = 'rulego-sidebar__component-name'\n nameEl.textContent = comp.label\n\n compEl.appendChild(colorBar)\n compEl.appendChild(nameEl)\n\n // 拖拽开始 — 存储组件类型数据, 供 LogicFlow DnD 消费\n compEl.addEventListener('dragstart', (e: DragEvent) => {\n if (e.dataTransfer) {\n e.dataTransfer.setData('application/rulego-component', JSON.stringify({\n type: comp.type,\n category: comp.category,\n label: comp.label,\n color: comp.color || groupColor,\n }))\n e.dataTransfer.effectAllowed = 'copy'\n }\n compEl.classList.add('rulego-sidebar__component--dragging')\n })\n\n compEl.addEventListener('dragend', () => {\n compEl.classList.remove('rulego-sidebar__component--dragging')\n })\n\n return compEl\n }\n\n // ============================================================\n // 搜索过滤\n // ============================================================\n\n /**\n * 根据搜索关键字过滤组件分组\n * @param groups - 原始分组列表\n * @returns 过滤后的分组列表 (空分组会被移除)\n */\n private filterGroups(groups: ComponentGroup[]): ComponentGroup[] {\n if (!this.searchKeyword) return groups\n\n return groups\n .map((group) => ({\n ...group,\n components: group.components.filter((comp) =>\n comp.label.toLowerCase().includes(this.searchKeyword) ||\n comp.type.toLowerCase().includes(this.searchKeyword) ||\n (comp.desc && comp.desc.toLowerCase().includes(this.searchKeyword))\n ),\n }))\n .filter((group) => group.components.length > 0)\n }\n\n // ============================================================\n // 公共 API\n // ============================================================\n\n /**\n * 手动触发刷新\n */\n refresh(): void {\n const currentGroups = this.core.store.getState().componentGroups as ComponentGroup[]\n this.renderGroups(currentGroups)\n }\n\n /**\n * 展开所有分组\n */\n expandAll(): void {\n this.collapsedGroups.clear()\n this.refresh()\n }\n\n /**\n * 折叠所有分组\n */\n collapseAll(): void {\n const groups = this.core.store.getState().componentGroups as ComponentGroup[]\n for (const group of groups) {\n this.collapsedGroups.add(group.category)\n }\n this.refresh()\n }\n\n /**\n * 获取根 DOM 元素\n */\n getElement(): HTMLElement | null {\n return this.rootEl\n }\n}\n","/**\n * @file components/Toolbar.ts\n * @description 编辑器工具栏 — 纯 DOM 实现 (零框架依赖)\n *\n * 功能:\n * - 撤销 / 重做 按钮\n * - 删除选中元素\n * - 保存 / 全屏切换\n * - 缩放控制 (放大 / 缩小 / 适应画布)\n * - 自定义按钮扩展\n * - 自动监听 EditorCore 状态, 按钮禁用联动\n *\n * 使用方式:\n * ```typescript\n * import { Toolbar } from '@huanban/editor-ui'\n *\n * const toolbar = new Toolbar(container, { core: editorCore })\n * toolbar.destroy()\n * ```\n */\n\nimport type { EditorCore } from '@huanban/rulego-editor-core'\n\n// ============================================================\n// 类型定义\n// ============================================================\n\n/**\n * 工具栏按钮定义\n */\nexport interface ToolbarButton {\n /** 按钮唯一标识 */\n id: string\n /** 按钮显示文本 (可选, 图标模式下可不设) */\n label?: string\n /** 按钮图标 (emoji 或 SVG 字符串) */\n icon: string\n /** 提示文本 (tooltip) */\n title: string\n /** 是否禁用 (可以是函数, 动态判断) */\n disabled?: boolean | (() => boolean)\n /** 分隔线 (在此按钮之前插入分隔线) */\n divider?: boolean\n /** 点击回调 */\n onClick: () => void\n}\n\n/**\n * Toolbar 配置选项\n */\nexport interface ToolbarOptions {\n /** EditorCore 实例 (必须) */\n core: EditorCore\n /** 自定义按钮列表 (追加到内置按钮之后) */\n extraButtons?: ToolbarButton[]\n /** 是否显示缩放控制, 默认 true */\n showZoom?: boolean\n /** 高度 (CSS值), 默认 '40px' */\n height?: string\n}\n\n// ============================================================\n// Toolbar 组件\n// ============================================================\n\n/**\n * 纯 DOM 实现的编辑器工具栏\n *\n * 内置按钮:\n * - ↩ 撤销 | ↪ 重做 | 🗑 删除 | 💾 保存 | ⛶ 全屏\n * - 🔍+ 放大 | 🔍- 缩小 | 🔲 适应画布\n *\n * 通过 EditorCore.store.subscribe 监听 canUndo/canRedo 等状态,\n * 动态切换按钮的 disabled 状态。\n */\nexport class Toolbar {\n /** 外层容器 */\n private container: HTMLElement\n /** EditorCore 引用 */\n private core: EditorCore\n /** 配置选项 */\n private options: Required<ToolbarOptions>\n /** 根 DOM 元素 */\n private rootEl: HTMLElement | null = null\n /** 状态订阅取消函数 */\n private unsubscribe: (() => void) | null = null\n /** 按钮 DOM 映射 (id => element) */\n private buttonElements: Map<string, HTMLButtonElement> = new Map()\n\n constructor(container: HTMLElement, options: ToolbarOptions) {\n this.container = container\n this.core = options.core\n\n this.options = {\n core: options.core,\n extraButtons: options.extraButtons ?? [],\n showZoom: options.showZoom ?? true,\n height: options.height ?? '40px',\n }\n\n this.mount()\n\n // 监听状态变化, 更新按钮禁用状态\n this.unsubscribe = this.core.store.subscribe(() => {\n this.updateButtonStates()\n })\n }\n\n // ============================================================\n // 生命周期\n // ============================================================\n\n /**\n * 挂载工具栏 DOM\n */\n private mount(): void {\n this.rootEl = document.createElement('div')\n this.rootEl.className = 'rulego-toolbar'\n this.rootEl.style.height = this.options.height\n\n // 构建内置按钮\n const buttons = this.getBuiltinButtons()\n\n // 添加自定义按钮\n if (this.options.extraButtons.length > 0) {\n buttons.push({\n id: '__divider_extra',\n icon: '',\n title: '',\n divider: true,\n onClick: () => { /* 分隔占位 */ },\n })\n buttons.push(...this.options.extraButtons)\n }\n\n // 渲染按钮\n for (const btn of buttons) {\n if (btn.divider) {\n const dividerEl = document.createElement('span')\n dividerEl.className = 'rulego-toolbar__divider'\n this.rootEl.appendChild(dividerEl)\n }\n\n if (!btn.icon && btn.divider) continue // 纯分隔线\n\n const buttonEl = document.createElement('button')\n buttonEl.className = 'rulego-toolbar__button'\n buttonEl.title = btn.title\n buttonEl.innerHTML = btn.icon\n if (btn.label) {\n const labelSpan = document.createElement('span')\n labelSpan.className = 'rulego-toolbar__button-label'\n labelSpan.textContent = btn.label\n buttonEl.appendChild(labelSpan)\n }\n\n buttonEl.addEventListener('click', (e) => {\n e.preventDefault()\n e.stopPropagation()\n btn.onClick()\n })\n\n this.buttonElements.set(btn.id, buttonEl)\n this.rootEl.appendChild(buttonEl)\n }\n\n this.container.appendChild(this.rootEl)\n\n // 初始状态更新\n this.updateButtonStates()\n }\n\n /**\n * 销毁工具栏\n */\n destroy(): void {\n if (this.unsubscribe) {\n this.unsubscribe()\n this.unsubscribe = null\n }\n if (this.rootEl && this.rootEl.parentNode) {\n this.rootEl.parentNode.removeChild(this.rootEl)\n }\n this.rootEl = null\n this.buttonElements.clear()\n }\n\n // ============================================================\n // 按钮定义\n // ============================================================\n\n /**\n * 获取内置按钮列表\n * @returns 内置按钮定义数组\n */\n private getBuiltinButtons(): ToolbarButton[] {\n const i18n = this.core.i18n\n const buttons: ToolbarButton[] = [\n {\n id: 'undo',\n icon: '↩',\n title: i18n.t('toolbar.undo') || '撤销',\n disabled: () => !(this.core.store.getState().canUndo as boolean),\n onClick: () => this.core.eventBus.emit('editor:reset'),\n },\n {\n id: 'redo',\n icon: '↪',\n title: i18n.t('toolbar.redo') || '重做',\n disabled: () => !(this.core.store.getState().canRedo as boolean),\n onClick: () => { /* 由 HistoryManager 处理 */ },\n },\n {\n id: 'delete',\n icon: '🗑',\n title: i18n.t('toolbar.delete') || '删除选中',\n divider: true,\n onClick: () => this.core.eventBus.emit('editor:delete-selected'),\n },\n {\n id: 'save',\n icon: '💾',\n title: i18n.t('toolbar.save') || '保存',\n onClick: () => this.core.eventBus.emit('editor:save', {\n data: {} as never,\n success: false,\n }),\n },\n {\n id: 'fullscreen',\n icon: '⛶',\n title: i18n.t('toolbar.fullscreen') || '全屏',\n onClick: () => this.core.eventBus.emit('editor:fullscreen'),\n },\n ]\n\n // 缩放控制\n if (this.options.showZoom) {\n buttons.push(\n {\n id: 'zoom-in',\n icon: '🔍+',\n title: i18n.t('toolbar.zoomIn') || '放大',\n divider: true,\n onClick: () => this.core.eventBus.emit('canvas:zoom', { scale: 1.1 }),\n },\n {\n id: 'zoom-out',\n icon: '🔍-',\n title: i18n.t('toolbar.zoomOut') || '缩小',\n onClick: () => this.core.eventBus.emit('canvas:zoom', { scale: 0.9 }),\n },\n {\n id: 'zoom-fit',\n icon: '🔲',\n title: i18n.t('toolbar.fitView') || '适应画布',\n onClick: () => this.core.eventBus.emit('canvas:zoom', { scale: 1 }),\n },\n )\n }\n\n return buttons\n }\n\n // ============================================================\n // 状态更新\n // ============================================================\n\n /**\n * 根据 EditorCore 的状态更新按钮的 disabled 属性\n */\n private updateButtonStates(): void {\n for (const [id, el] of this.buttonElements) {\n const btnDef = this.getBuiltinButtons().find((b) => b.id === id)\n if (btnDef && typeof btnDef.disabled === 'function') {\n el.disabled = btnDef.disabled()\n el.classList.toggle('rulego-toolbar__button--disabled', el.disabled)\n }\n }\n }\n\n // ============================================================\n // 公共 API\n // ============================================================\n\n /**\n * 获取根 DOM 元素\n */\n getElement(): HTMLElement | null {\n return this.rootEl\n }\n\n /**\n * 动态添加自定义按钮\n * @param button - 按钮定义\n */\n addButton(button: ToolbarButton): void {\n if (!this.rootEl) return\n\n const buttonEl = document.createElement('button')\n buttonEl.className = 'rulego-toolbar__button'\n buttonEl.title = button.title\n buttonEl.innerHTML = button.icon\n buttonEl.addEventListener('click', (e) => {\n e.preventDefault()\n button.onClick()\n })\n\n this.buttonElements.set(button.id, buttonEl)\n this.rootEl.appendChild(buttonEl)\n }\n}\n","/**\n * @file components/PropertyPanel.ts\n * @description 节点/边属性面板 — 纯 DOM 实现 (零框架依赖)\n *\n * 功能:\n * - 监听节点/边选中事件, 自动展示对应的属性表单\n * - 根据组件定义动态生成表单字段\n * - 支持字段类型: string, int, bool, select, code, json\n * - 字段变更实时回传给 EditorCore\n * - 空白处点击时自动关闭面板\n *\n * 使用方式:\n * ```typescript\n * import { PropertyPanel } from '@huanban/editor-ui'\n *\n * const panel = new PropertyPanel(container, { core: editorCore })\n * panel.destroy()\n * ```\n *\n * @see ComponentField — 字段定义类型\n * @see EditorCore — 编辑器核心引擎\n */\n\nimport type { EditorCore } from '@huanban/rulego-editor-core'\nimport type { ComponentField, ComponentDefinition } from '@huanban/rulego-editor-core'\n\n// ============================================================\n// 配置接口\n// ============================================================\n\n/**\n * PropertyPanel 配置选项\n */\nexport interface PropertyPanelOptions {\n /** EditorCore 实例 (必须) */\n core: EditorCore\n /** 面板标题文本 */\n title?: string\n /** 宽度, 默认 '320px' */\n width?: string\n /** 位置, 默认 'right' */\n position?: 'left' | 'right'\n}\n\n// ============================================================\n// PropertyPanel 组件\n// ============================================================\n\n/**\n * 纯 DOM 实现的属性编辑面板\n *\n * 工作流:\n * 1. 监听 core.store 中 currentNodeView / currentNodeModel 变化\n * 2. 根据 ComponentDefinition.fields 动态生成表单\n * 3. 字段变更时通过 core.eventBus 发送 'node:property-update' 事件\n * 4. 画布空白点击时清空面板内容\n */\nexport class PropertyPanel {\n /** 外层容器 */\n private container: HTMLElement\n /** EditorCore 引用 */\n private core: EditorCore\n /** 配置选项 */\n private options: Required<PropertyPanelOptions>\n /** 根 DOM 元素 */\n private rootEl: HTMLElement | null = null\n /** 表单容器 */\n private formContainer: HTMLElement | null = null\n /** 面板标题元素 */\n private titleEl: HTMLElement | null = null\n /** 状态订阅取消函数 */\n private unsubscribe: (() => void) | null = null\n /** 事件取消列表 */\n private eventCleanups: Array<() => void> = []\n /** 当前编辑的节点 LF ID */\n private currentNodeId: string | null = null\n\n constructor(container: HTMLElement, options: PropertyPanelOptions) {\n this.container = container\n this.core = options.core\n\n this.options = {\n core: options.core,\n title: options.title || this.core.i18n.t('panel.title') || '属性编辑',\n width: options.width ?? '320px',\n position: options.position ?? 'right',\n }\n\n this.mount()\n this.bindEvents()\n }\n\n // ============================================================\n // 生命周期\n // ============================================================\n\n /**\n * 创建面板 DOM\n */\n private mount(): void {\n this.rootEl = document.createElement('div')\n this.rootEl.className = `rulego-property-panel rulego-property-panel--${this.options.position}`\n this.rootEl.style.width = this.options.width\n\n // 面板头部\n const headerEl = document.createElement('div')\n headerEl.className = 'rulego-property-panel__header'\n\n this.titleEl = document.createElement('h3')\n this.titleEl.className = 'rulego-property-panel__title'\n this.titleEl.textContent = this.options.title\n\n const closeBtn = document.createElement('button')\n closeBtn.className = 'rulego-property-panel__close'\n closeBtn.textContent = '✕'\n closeBtn.addEventListener('click', () => this.clearPanel())\n\n headerEl.appendChild(this.titleEl)\n headerEl.appendChild(closeBtn)\n this.rootEl.appendChild(headerEl)\n\n // 表单容器\n this.formContainer = document.createElement('div')\n this.formContainer.className = 'rulego-property-panel__form'\n this.rootEl.appendChild(this.formContainer)\n\n // 初始隐藏\n this.rootEl.style.display = 'none'\n this.container.appendChild(this.rootEl)\n }\n\n /**\n * 绑定事件监听\n */\n private bindEvents(): void {\n // 监听节点视图和模型的组合变化\n this.unsubscribe = this.core.store.subscribe((state) => {\n const nodeView = state.currentNodeView as ComponentDefinition | null\n const nodeModel = state.currentNodeModel as Record<string, unknown>\n\n if (nodeView && nodeModel && Object.keys(nodeModel).length > 0) {\n this.showPanel(nodeView, nodeModel)\n }\n })\n\n // 画布空白点击 — 关闭面板\n const blankClickCleanup = this.core.eventBus.on('blank:click', () => {\n this.clearPanel()\n })\n this.eventCleanups.push(blankClickCleanup)\n }\n\n /**\n * 销毁面板\n */\n destroy(): void {\n if (this.unsubscribe) {\n this.unsubscribe()\n this.unsubscribe = null\n }\n for (const cleanup of this.eventCleanups) {\n cleanup()\n }\n this.eventCleanups = []\n if (this.rootEl && this.rootEl.parentNode) {\n this.rootEl.parentNode.removeChild(this.rootEl)\n }\n this.rootEl = null\n this.formContainer = null\n }\n\n // ============================================================\n // 面板展示 / 清空\n // ============================================================\n\n /**\n * 显示面板并渲染表单\n * @param view - 组件定义 (含字段列表)\n * @param model - 当前节点模型数据\n */\n private showPanel(view: ComponentDefinition, model: Record<string, unknown>): void {\n if (!this.rootEl || !this.formContainer || !this.titleEl) return\n\n this.rootEl.style.display = 'flex'\n this.titleEl.textContent = view.label || this.options.title\n\n // 清空旧表单\n this.formContainer.innerHTML = ''\n\n // 渲染基础信息区\n this.renderBasicInfo(model)\n\n // 渲染动态字段\n if (view.fields && view.fields.length > 0) {\n for (const field of view.fields) {\n const fieldEl = this.createFieldElement(field, model)\n this.formContainer.appendChild(fieldEl)\n }\n }\n }\n\n /**\n * 清空面板并隐藏\n */\n private clearPanel(): void {\n if (!this.rootEl || !this.formContainer) return\n this.formContainer.innerHTML = ''\n this.rootEl.style.display = 'none'\n this.currentNodeId = null\n }\n\n // ============================================================\n // 表单渲染\n // ============================================================\n\n /**\n * 渲染基础信息区 (节点名称等)\n * @param model - 节点模型\n */\n private renderBasicInfo(model: Record<string, unknown>): void {\n if (!this.formContainer) return\n\n const section = document.createElement('div')\n section.className = 'rulego-property-panel__section'\n\n // 节点名称\n const nameField = this.createTextField({\n name: 'name',\n type: 'string',\n defaultValue: '',\n label: this.core.i18n.t('panel.nodeName') || '节点名称',\n desc: '',\n validate: '',\n fields: null,\n }, model)\n\n section.appendChild(nameField)\n this.formContainer.appendChild(section)\n }\n\n /**\n * 创建单个表单字段元素\n * @param field - 字段定义\n * @param model - 节点模型数据\n * @returns 字段 wrapper HTMLElement\n */\n private createFieldElement(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const wrapper = document.createElement('div')\n wrapper.className = 'rulego-property-panel__field'\n\n // 字段标签\n const labelEl = document.createElement('label')\n labelEl.className = 'rulego-property-panel__label'\n labelEl.textContent = field.label || field.name\n\n if (field.desc) {\n labelEl.title = field.desc\n }\n\n wrapper.appendChild(labelEl)\n\n // 根据字段类型创建不同的输入控件\n let inputEl: HTMLElement\n switch (field.type) {\n case 'bool':\n inputEl = this.createBoolField(field, model)\n break\n case 'select':\n inputEl = this.createSelectField(field, model)\n break\n case 'code':\n case 'json':\n inputEl = this.createTextareaField(field, model)\n break\n case 'int':\n inputEl = this.createNumberField(field, model)\n break\n default:\n inputEl = this.createTextField(field, model)\n break\n }\n\n wrapper.appendChild(inputEl)\n\n // 描述文本\n if (field.desc) {\n const descEl = document.createElement('span')\n descEl.className = 'rulego-property-panel__desc'\n descEl.textContent = field.desc\n wrapper.appendChild(descEl)\n }\n\n return wrapper\n }\n\n /**\n * 文本输入框\n */\n private createTextField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const input = document.createElement('input')\n input.type = 'text'\n input.className = 'rulego-property-panel__input'\n input.name = field.name\n input.value = String(this.getModelValue(field, model) ?? '')\n input.placeholder = field.desc || ''\n\n input.addEventListener('change', () => {\n this.handleFieldChange(field.name, input.value)\n })\n\n return input\n }\n\n /**\n * 数值输入框\n */\n private createNumberField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const input = document.createElement('input')\n input.type = 'number'\n input.className = 'rulego-property-panel__input'\n input.name = field.name\n input.value = String(this.getModelValue(field, model) ?? 0)\n\n input.addEventListener('change', () => {\n this.handleFieldChange(field.name, Number(input.value))\n })\n\n return input\n }\n\n /**\n * 布尔开关\n */\n private createBoolField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const wrapper = document.createElement('div')\n wrapper.className = 'rulego-property-panel__switch-wrapper'\n\n const checkboxId = `field-${field.name}-${Date.now()}`\n const checkbox = document.createElement('input')\n checkbox.type = 'checkbox'\n checkbox.className = 'rulego-property-panel__checkbox'\n checkbox.id = checkboxId\n checkbox.checked = Boolean(this.getModelValue(field, model))\n\n const toggleLabel = document.createElement('label')\n toggleLabel.className = 'rulego-property-panel__switch'\n toggleLabel.htmlFor = checkboxId\n\n checkbox.addEventListener('change', () => {\n this.handleFieldChange(field.name, checkbox.checked)\n })\n\n wrapper.appendChild(checkbox)\n wrapper.appendChild(toggleLabel)\n\n return wrapper\n }\n\n /**\n * 下拉选择\n */\n private createSelectField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const select = document.createElement('select')\n select.className = 'rulego-property-panel__select'\n select.name = field.name\n\n const currentValue = String(this.getModelValue(field, model) ?? '')\n\n // 渲染选项\n if (field.options && field.options.length > 0) {\n for (const opt of field.options) {\n const optionEl = document.createElement('option')\n optionEl.value = opt.value\n optionEl.textContent = opt.label\n if (opt.value === currentValue) {\n optionEl.selected = true\n }\n select.appendChild(optionEl)\n }\n }\n\n select.addEventListener('change', () => {\n this.handleFieldChange(field.name, select.value)\n })\n\n return select\n }\n\n /**\n * 多行文本 / 代码编辑器\n */\n private createTextareaField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const textarea = document.createElement('textarea')\n textarea.className = 'rulego-property-panel__textarea'\n textarea.name = field.name\n textarea.rows = 6\n textarea.value = String(this.getModelValue(field, model) ?? '')\n textarea.placeholder = field.desc || ''\n\n if (field.type === 'code' || field.type === 'json') {\n textarea.spellcheck = false\n textarea.style.fontFamily = 'monospace'\n }\n\n textarea.addEventListener('change', () => {\n this.handleFieldChange(field.name, textarea.value)\n })\n\n return textarea\n }\n\n // ============================================================\n // 数据处理\n // ============================================================\n\n /**\n * 从模型中获取字段当前值\n * @param field - 字段定义\n * @param model - 节点模型\n * @returns 字段值\n */\n private getModelValue(field: ComponentField, model: Record<string, unknown>): unknown {\n // 先从 configuration 中找, 再从顶层找\n const config = model.configuration as Record<string, unknown> | undefined\n if (config && field.name in config) {\n return config[field.name]\n }\n if (field.name in model) {\n return model[field.name]\n }\n return field.defaultValue\n }\n\n /**\n * 处理字段变更, 通知 EditorCore\n * @param fieldName - 字段名\n * @param value - 新值\n */\n private handleFieldChange(fieldName: string, value: unknown): void {\n this.core.eventBus.emit('node:property-update', {\n id: this.currentNodeId || '',\n properties: { [fieldName]: value },\n })\n\n // 标记数据已修改\n this.core.store.setState({ isDirty: true })\n }\n\n // ============================================================\n // 公共 API\n // ============================================================\n\n /**\n * 获取根 DOM 元素\n */\n getElement(): HTMLElement | null {\n return this.rootEl\n }\n\n /**\n * 手动设置当前编辑的节点 ID\n * @param nodeId - LogicFlow 节点 ID\n */\n setCurrentNodeId(nodeId: string): void {\n this.currentNodeId = nodeId\n }\n\n /**\n * 手动显示面板\n * @param view - 组件定义\n * @param model - 节点模型\n * @param nodeId - 节点 ID\n */\n show(view: ComponentDefinition, model: Record<string, unknown>, nodeId?: string): void {\n if (nodeId) {\n this.currentNodeId = nodeId\n }\n this.showPanel(view, model)\n }\n\n /**\n * 手动隐藏面板\n */\n hide(): void {\n this.clearPanel()\n }\n}\n","/**\n * @file components/ContextMenu.ts\n * @description 右键上下文菜单 — 纯 DOM 实现 (零框架依赖)\n *\n * 功能:\n * - 支持节点右键菜单 (编辑 / 删除 / 复制 等)\n * - 支持画布空白处右键菜单 (粘贴 / 全选 等)\n * - 支持边右键菜单 (删除连线 / 编辑标签 等)\n * - 自动定位到鼠标位置\n * - 点击外部自动关闭\n * - 支持自定义菜单项扩展\n *\n * 使用方式:\n * ```typescript\n * import { ContextMenu } from '@huanban/editor-ui'\n *\n * const menu = new ContextMenu(container, {\n * core: editorCore,\n * items: [...] // 可选: 自定义菜单项\n * })\n * menu.destroy()\n * ```\n */\n\nimport type { EditorCore } from '@huanban/rulego-editor-core'\n\n// ============================================================\n// 类型定义\n// ============================================================\n\n/**\n * 菜单项类型\n */\nexport type MenuItemType = 'node' | 'edge' | 'canvas' | 'all'\n\n/**\n * 菜单项定义\n */\nexport interface MenuItem {\n /** 菜单项唯一标识 */\n id: string\n /** 显示文本 */\n label: string\n /** 图标 (emoji 或 SVG) */\n icon?: string\n /** 适用的目标类型 */\n target: MenuItemType | MenuItemType[]\n /** 是否禁用 (可动态) */\n disabled?: boolean | (() => boolean)\n /** 是否在此项之后添加分隔线 */\n separator?: boolean\n /** 点击回调, 接收目标元素的信息 */\n onClick: (context: MenuContext) => void\n}\n\n/**\n * 菜单上下文 — 右键时附带的目标信息\n */\nexport interface MenuContext {\n /** 目标类型 */\n type: MenuItemType\n /** 目标元素 ID (节点/边 LogicFlow ID) */\n id?: string\n /** 鼠标坐标 */\n position: { x: number; y: number }\n /** 附加数据 */\n data?: Record<string, unknown>\n}\n\n/**\n * ContextMenu 配置选项\n */\nexport interface ContextMenuOptions {\n /** EditorCore 实例 (必须) */\n core: EditorCore\n /** 自定义菜单项 (追加到内置菜单项之后) */\n items?: MenuItem[]\n /** 是否包含内置菜单项, 默认 true */\n includeBuiltin?: boolean\n}\n\n// ============================================================\n// ContextMenu 组件\n// ============================================================\n\n/**\n * 纯 DOM 实现的右键上下文菜单\n *\n * 生命周期:\n * 1. 构造时创建隐藏的菜单 DOM\n * 2. 监听右键事件, 判断目标类型并显示对应菜单\n * 3. 点击菜单项后执行回调并关闭\n * 4. 点击外部区域自动关闭\n */\nexport class ContextMenu {\n /** 画布容器 (用于事件监听范围) */\n private container: HTMLElement\n /** EditorCore 引用 */\n private core: EditorCore\n /** 菜单项列表 */\n private items: MenuItem[]\n /** 菜单根 DOM 元素 */\n private menuEl: HTMLElement | null = null\n /** 当前上下文 */\n private currentContext: MenuContext | null = null\n /** 外部点击关闭的监听函数引用 */\n private outsideClickHandler: ((e: MouseEvent) => void) | null = null\n\n constructor(container: HTMLElement, options: ContextMenuOptions) {\n this.container = container\n this.core = options.core\n\n // 合并菜单项\n const builtinItems = (options.includeBuiltin !== false) ? this.getBuiltinItems() : []\n this.items = [...builtinItems, ...(options.items ?? [])]\n\n this.mount()\n this.bindEvents()\n }\n\n // ============================================================\n // 生命周期\n // ============================================================\n\n /**\n * 创建菜单 DOM (初始隐藏)\n */\n private mount(): void {\n this.menuEl = document.createElement('div')\n this.menuEl.className = 'rulego-context-menu'\n this.menuEl.style.display = 'none'\n document.body.appendChild(this.menuEl)\n }\n\n /**\n * 绑定事件\n */\n private bindEvents(): void {\n // 右键拦截\n this.container.addEventListener('contextmenu', (e: MouseEvent) => {\n e.preventDefault()\n this.handleContextMenu(e)\n })\n\n // 外部点击关闭\n this.outsideClickHandler = (e: MouseEvent) => {\n if (this.menuEl && !this.menuEl.contains(e.target as Node)) {\n this.hide()\n }\n }\n document.addEventListener('click', this.outsideClickHandler)\n\n // ESC 键关闭\n document.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n this.hide()\n }\n })\n }\n\n /**\n * 销毁菜单\n */\n destroy(): void {\n if (this.outsideClickHandler) {\n document.removeEventListener('click', this.outsideClickHandler)\n this.outsideClickHandler = null\n }\n if (this.menuEl && this.menuEl.parentNode) {\n this.menuEl.parentNode.removeChild(this.menuEl)\n }\n this.menuEl = null\n }\n\n // ============================================================\n // 右键处理\n // ============================================================\n\n /**\n * 处理右键事件\n */\n private handleContextMenu(e: MouseEvent): void {\n const target = e.target as HTMLElement\n\n // 判断右键目标类型\n const context = this.resolveContext(target, e)\n this.currentContext = context\n\n // 过滤出适用的菜单项\n const applicableItems = this.items.filter((item) => {\n const targets = Array.isArray(item.target) ? item.target : [item.target]\n return targets.includes(context.type) || targets.includes('all')\n })\n\n if (applicableItems.length === 0) return\n\n // 渲染菜单\n this.renderMenu(applicableItems, e.clientX, e.clientY)\n }\n\n /**\n * 解析右键目标的上下文\n */\n private resolveContext(target: HTMLElement, e: MouseEvent): MenuContext {\n // 检查是否在节点上\n const nodeEl = target.closest('.lf-node') as HTMLElement | null\n if (nodeEl) {\n return {\n type: 'node',\n id: nodeEl.getAttribute('data-node-id') || undefined,\n position: { x: e.clientX, y: e.clientY },\n }\n }\n\n // 检查是否在边上\n const edgeEl = target.closest('.lf-edge') as HTMLElement | null\n if (edgeEl) {\n return {\n type: 'edge',\n id: edgeEl.getAttribute('data-edge-id') || undefined,\n position: { x: e.clientX, y: e.clientY },\n }\n }\n\n // 画布空白处\n return {\n type: 'canvas',\n position: { x: e.clientX, y: e.clientY },\n }\n }\n\n // ============================================================\n // 菜单渲染\n // ============================================================\n\n /**\n * 渲染并显示菜单\n */\n private renderMenu(items: MenuItem[], x: number, y: number): void {\n if (!this.menuEl) return\n\n this.menuEl.innerHTML = ''\n\n for (const item of items) {\n const itemEl = document.createElement('div')\n itemEl.className = 'rulego-context-menu__item'\n\n const isDisabled = typeof item.disabled === 'function' ? item.disabled() : item.disabled\n if (isDisabled) {\n itemEl.classList.add('rulego-context-menu__item--disabled')\n }\n\n // 图标\n if (item.icon) {\n const iconSpan = document.createElement('span')\n iconSpan.className = 'rulego-context-menu__icon'\n iconSpan.innerHTML = item.icon\n itemEl.appendChild(iconSpan)\n }\n\n // 文字\n const labelSpan = document.createElement('span')\n labelSpan.className = 'rulego-context-menu__label'\n labelSpan.textContent = item.label\n itemEl.appendChild(labelSpan)\n\n if (!isDisabled) {\n itemEl.addEventListener('click', () => {\n if (this.currentContext) {\n item.onClick(this.currentContext)\n }\n this.hide()\n })\n }\n\n this.menuEl.appendChild(itemEl)\n\n // 分隔线\n if (item.separator) {\n const sep = document.createElement('div')\n sep.className = 'rulego-context-menu__separator'\n this.menuEl.appendChild(sep)\n }\n }\n\n // 定位\n this.menuEl.style.left = `${x}px`\n this.menuEl.style.top = `${y}px`\n this.menuEl.style.display = 'block'\n\n // 边界检测 — 防止菜单超出视口\n requestAnimationFrame(() => {\n if (!this.menuEl) return\n const rect = this.menuEl.getBoundingClientRect()\n if (rect.right > window.innerWidth) {\n this.menuEl.style.left = `${x - rect.width}px`\n }\n if (rect.bottom > window.innerHeight) {\n this.menuEl.style.top = `${y - rect.height}px`\n }\n })\n }\n\n /**\n * 隐藏菜单\n */\n hide(): void {\n if (this.menuEl) {\n this.menuEl.style.display = 'none'\n }\n this.currentContext = null\n }\n\n // ============================================================\n // 内置菜单项\n // ============================================================\n\n /**\n * 获取内置菜单项\n */\n private getBuiltinItems(): MenuItem[] {\n const i18n = this.core.i18n\n return [\n {\n id: 'edit',\n label: i18n.t('contextMenu.edit') || '编辑',\n icon: '✏️',\n target: 'node',\n onClick: (ctx) => {\n if (ctx.id) {\n this.core.eventBus.emit('editor:show-edit-panel')\n }\n },\n },\n {\n id: 'delete',\n label: i18n.t('contextMenu.delete') || '删除',\n icon: '🗑️',\n target: ['node', 'edge'],\n separator: true,\n onClick: (ctx) => {\n if (ctx.id) {\n this.core.eventBus.emit('node:delete', { id: ctx.id })\n }\n },\n },\n {\n id: 'copy',\n label: i18n.t('contextMenu.copy') || '复制',\n icon: '📋',\n target: 'node',\n onClick: () => {\n // 由 EditorCore 或插件处理剪贴板\n },\n },\n {\n id: 'select-all',\n label: i18n.t('contextMenu.selectAll') || '全选',\n icon: '⬜',\n target: 'canvas',\n onClick: () => {\n // 由 EditorCore 处理全选\n },\n },\n ]\n }\n\n // ============================================================\n // 公共 API\n // ============================================================\n\n /**\n * 动态添加菜单项\n * @param item - 菜单项定义\n */\n addItem(item: MenuItem): void {\n this.items.push(item)\n }\n\n /**\n * 移除菜单项\n * @param id - 菜单项 ID\n */\n removeItem(id: string): void {\n this.items = this.items.filter((item) => item.id !== id)\n }\n\n /**\n * 获取菜单 DOM 元素\n */\n getElement(): HTMLElement | null {\n return this.menuEl\n }\n}\n"],"names":["Sidebar","container","options","state","groups","searchWrapper","searchIcon","searchInput","e","currentGroups","groupsContainer","filteredGroups","emptyEl","group","groupEl","headerEl","isCollapsed","arrowEl","labelEl","countEl","listEl","comp","compEl","groupColor","colorBar","nameEl","Toolbar","buttons","btn","dividerEl","buttonEl","labelSpan","i18n","id","el","btnDef","b","button","PropertyPanel","closeBtn","nodeView","nodeModel","blankClickCleanup","cleanup","view","model","field","fieldEl","section","nameField","wrapper","inputEl","descEl","input","checkboxId","checkbox","toggleLabel","select","currentValue","opt","optionEl","textarea","config","fieldName","value","nodeId","ContextMenu","builtinItems","target","context","applicableItems","item","targets","nodeEl","edgeEl","items","x","y","itemEl","isDisabled","iconSpan","sep","rect","ctx"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAMH,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAC9C,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE1D,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAC9C,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEzE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC1D,YAAY,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAEtE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,YAAY,EACV,kBAAkB,EAClB,QAAQ,EACR,YAAY,EACZ,WAAW,GACZ,MAAM,0BAA0B,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/components/Sidebar.ts","../src/components/Toolbar.ts","../src/components/PropertyPanel.ts","../src/components/ContextMenu.ts"],"sourcesContent":["/**\n * @file components/Sidebar.ts\n * @description 组件侧边栏 — 纯 DOM 实现 (零框架依赖)\n *\n * 功能:\n * - 按分类分组展示可用组件\n * - 支持关键字搜索过滤\n * - 支持分组折叠/展开\n * - 拖拽组件到画布 (DnD)\n * - 自动监听 EditorCore 状态变化, 更新组件列表\n *\n * 使用方式:\n * ```typescript\n * import { Sidebar } from '@huanban/editor-ui'\n *\n * const sidebar = new Sidebar(container, { core: editorCore })\n * // 卸载时\n * sidebar.destroy()\n * ```\n *\n * @see ComponentGroup — 组件分组定义\n * @see EditorCore — 编辑器核心引擎\n */\n\nimport type { EditorCore } from '@huanban/rulego-editor-core'\nimport type { ComponentDefinition, ComponentGroup } from '@huanban/rulego-editor-core'\n\n// ============================================================\n// 配置接口\n// ============================================================\n\n/**\n * Sidebar 配置选项\n */\nexport interface SidebarOptions {\n /** EditorCore 实例 (必须) */\n core: EditorCore\n /** 是否显示搜索框, 默认 true */\n searchable?: boolean\n /** 默认折叠状态, 默认 false (全部展开) */\n defaultCollapsed?: boolean\n /** 搜索框占位符文本 */\n searchPlaceholder?: string\n /** 宽度 (CSS值), 默认 '220px' */\n width?: string\n}\n\n// ============================================================\n// Sidebar 组件\n// ============================================================\n\n/**\n * 纯 DOM 实现的组件侧边栏\n *\n * 内部维护:\n * - componentGroups: 当前显示的组件分组\n * - collapsedGroups: 折叠状态 Set\n * - searchKeyword: 搜索关键字\n *\n * 通过 EditorCore.store.subscribe 监听状态更新\n */\nexport class Sidebar {\n /** 外层容器 */\n private container: HTMLElement\n /** EditorCore 引用 */\n private core: EditorCore\n /** 配置选项 */\n private options: Required<SidebarOptions>\n /** 已折叠的分组 Set */\n private collapsedGroups: Set<string> = new Set()\n /** 当前搜索关键字 */\n private searchKeyword = ''\n /** 根 DOM 元素 */\n private rootEl: HTMLElement | null = null\n /** 状态订阅取消函数 */\n private unsubscribe: (() => void) | null = null\n\n constructor(container: HTMLElement, options: SidebarOptions) {\n this.container = container\n this.core = options.core\n\n // 合并默认配置\n this.options = {\n core: options.core,\n searchable: options.searchable ?? true,\n defaultCollapsed: options.defaultCollapsed ?? false,\n searchPlaceholder: options.searchPlaceholder || this.core.i18n.t('sidebar.searchPlaceholder') || '搜索组件...',\n width: options.width ?? '220px',\n }\n\n // 初始渲染\n this.mount()\n\n // 监听组件列表变化\n this.unsubscribe = this.core.store.select(\n (state) => state.componentGroups as ComponentGroup[]\n ).subscribe(\n (groups: ComponentGroup[]) => {\n this.renderGroups(groups)\n }\n )\n }\n\n // ============================================================\n // 生命周期\n // ============================================================\n\n /**\n * 挂载侧边栏 DOM 到容器\n */\n private mount(): void {\n this.rootEl = document.createElement('div')\n this.rootEl.className = 'rulego-sidebar'\n this.rootEl.style.width = this.options.width\n\n // 搜索框\n if (this.options.searchable) {\n const searchWrapper = document.createElement('div')\n searchWrapper.className = 'rulego-sidebar__search-wrapper'\n\n const searchIcon = document.createElement('span')\n searchIcon.className = 'rulego-sidebar__search-icon'\n searchIcon.innerHTML = '🔍'\n\n const searchInput = document.createElement('input')\n searchInput.type = 'text'\n searchInput.className = 'rulego-sidebar__search'\n searchInput.placeholder = this.options.searchPlaceholder\n searchInput.addEventListener('input', (e) => {\n this.searchKeyword = (e.target as HTMLInputElement).value.toLowerCase()\n const currentGroups = this.core.store.getState().componentGroups as ComponentGroup[]\n this.renderGroups(currentGroups)\n })\n\n searchWrapper.appendChild(searchIcon)\n searchWrapper.appendChild(searchInput)\n this.rootEl.appendChild(searchWrapper)\n }\n\n // 分组容器\n const groupsContainer = document.createElement('div')\n groupsContainer.className = 'rulego-sidebar__groups'\n this.rootEl.appendChild(groupsContainer)\n\n this.container.appendChild(this.rootEl)\n\n // 首次渲染\n const groups = this.core.store.getState().componentGroups as ComponentGroup[]\n if (groups && groups.length > 0) {\n this.renderGroups(groups)\n }\n }\n\n /**\n * 销毁侧边栏, 清理所有 DOM 和事件\n */\n destroy(): void {\n if (this.unsubscribe) {\n this.unsubscribe()\n this.unsubscribe = null\n }\n if (this.rootEl && this.rootEl.parentNode) {\n this.rootEl.parentNode.removeChild(this.rootEl)\n }\n this.rootEl = null\n }\n\n // ============================================================\n // 渲染\n // ============================================================\n\n /**\n * 渲染所有组件分组\n * @param groups - 原始组件分组列表\n */\n private renderGroups(groups: ComponentGroup[]): void {\n if (!this.rootEl) return\n\n const groupsContainer = this.rootEl.querySelector('.rulego-sidebar__groups')\n if (!groupsContainer) return\n\n // 清空旧内容\n groupsContainer.innerHTML = ''\n\n // 按搜索关键字过滤\n const filteredGroups = this.filterGroups(groups)\n\n if (filteredGroups.length === 0) {\n const emptyEl = document.createElement('div')\n emptyEl.className = 'rulego-sidebar__empty'\n emptyEl.textContent = this.core.i18n.t('sidebar.noResults') || '无匹配组件'\n groupsContainer.appendChild(emptyEl)\n return\n }\n\n // 渲染每个分组\n for (const group of filteredGroups) {\n const groupEl = this.createGroupElement(group)\n groupsContainer.appendChild(groupEl)\n }\n }\n\n /**\n * 创建单个分组 DOM 元素\n * @param group - 组件分组定义\n * @returns 分组 HTMLElement\n */\n private createGroupElement(group: ComponentGroup): HTMLElement {\n const groupEl = document.createElement('div')\n groupEl.className = 'rulego-sidebar__group'\n\n // 分组标题 (可折叠)\n const headerEl = document.createElement('div')\n headerEl.className = 'rulego-sidebar__group-header'\n headerEl.style.borderLeftColor = group.color || '#ccc'\n\n const isCollapsed = this.collapsedGroups.has(group.category)\n\n // 折叠图标\n const arrowEl = document.createElement('span')\n arrowEl.className = `rulego-sidebar__arrow ${isCollapsed ? 'rulego-sidebar__arrow--collapsed' : ''}`\n arrowEl.textContent = '▼'\n\n // 分组名称\n const labelEl = document.createElement('span')\n labelEl.className = 'rulego-sidebar__group-label'\n labelEl.textContent = group.label\n\n // 组件数量\n const countEl = document.createElement('span')\n countEl.className = 'rulego-sidebar__group-count'\n countEl.textContent = `${group.components.length}`\n\n headerEl.appendChild(arrowEl)\n headerEl.appendChild(labelEl)\n headerEl.appendChild(countEl)\n\n // 点击折叠/展开\n headerEl.addEventListener('click', () => {\n if (this.collapsedGroups.has(group.category)) {\n this.collapsedGroups.delete(group.category)\n } else {\n this.collapsedGroups.add(group.category)\n }\n const currentGroups = this.core.store.getState().componentGroups as ComponentGroup[]\n this.renderGroups(currentGroups)\n })\n\n groupEl.appendChild(headerEl)\n\n // 组件列表 (折叠时不渲染)\n if (!isCollapsed) {\n const listEl = document.createElement('div')\n listEl.className = 'rulego-sidebar__component-list'\n\n for (const comp of group.components) {\n const compEl = this.createComponentElement(comp, group.color)\n listEl.appendChild(compEl)\n }\n\n groupEl.appendChild(listEl)\n }\n\n return groupEl\n }\n\n /**\n * 创建单个组件 DOM 元素 (可拖拽)\n * @param comp - 组件定义\n * @param groupColor - 分组颜色\n * @returns 组件 HTMLElement\n */\n private createComponentElement(comp: ComponentDefinition, groupColor: string): HTMLElement {\n const compEl = document.createElement('div')\n compEl.className = 'rulego-sidebar__component'\n compEl.setAttribute('draggable', 'true')\n compEl.title = comp.desc || comp.label\n\n // 颜色条\n const colorBar = document.createElement('span')\n colorBar.className = 'rulego-sidebar__component-color'\n colorBar.style.backgroundColor = comp.color || groupColor || '#ccc'\n\n // 组件名称\n const nameEl = document.createElement('span')\n nameEl.className = 'rulego-sidebar__component-name'\n nameEl.textContent = comp.label\n\n compEl.appendChild(colorBar)\n compEl.appendChild(nameEl)\n\n // 拖拽开始 — 存储组件类型数据, 供 LogicFlow DnD 消费\n compEl.addEventListener('dragstart', (e: DragEvent) => {\n if (e.dataTransfer) {\n e.dataTransfer.setData('application/rulego-component', JSON.stringify({\n type: comp.type,\n category: comp.category,\n label: comp.label,\n color: comp.color || groupColor,\n }))\n e.dataTransfer.effectAllowed = 'copy'\n }\n compEl.classList.add('rulego-sidebar__component--dragging')\n })\n\n compEl.addEventListener('dragend', () => {\n compEl.classList.remove('rulego-sidebar__component--dragging')\n })\n\n return compEl\n }\n\n // ============================================================\n // 搜索过滤\n // ============================================================\n\n /**\n * 根据搜索关键字过滤组件分组\n * @param groups - 原始分组列表\n * @returns 过滤后的分组列表 (空分组会被移除)\n */\n private filterGroups(groups: ComponentGroup[]): ComponentGroup[] {\n if (!this.searchKeyword) return groups\n\n return groups\n .map((group) => ({\n ...group,\n components: group.components.filter((comp) =>\n comp.label.toLowerCase().includes(this.searchKeyword) ||\n comp.type.toLowerCase().includes(this.searchKeyword) ||\n (comp.desc && comp.desc.toLowerCase().includes(this.searchKeyword))\n ),\n }))\n .filter((group) => group.components.length > 0)\n }\n\n // ============================================================\n // 公共 API\n // ============================================================\n\n /**\n * 手动触发刷新\n */\n refresh(): void {\n const currentGroups = this.core.store.getState().componentGroups as ComponentGroup[]\n this.renderGroups(currentGroups)\n }\n\n /**\n * 展开所有分组\n */\n expandAll(): void {\n this.collapsedGroups.clear()\n this.refresh()\n }\n\n /**\n * 折叠所有分组\n */\n collapseAll(): void {\n const groups = this.core.store.getState().componentGroups as ComponentGroup[]\n for (const group of groups) {\n this.collapsedGroups.add(group.category)\n }\n this.refresh()\n }\n\n /**\n * 获取根 DOM 元素\n */\n getElement(): HTMLElement | null {\n return this.rootEl\n }\n}\n","/**\n * @file components/Toolbar.ts\n * @description 编辑器工具栏 — 纯 DOM 实现 (零框架依赖)\n *\n * 功能:\n * - 撤销 / 重做 按钮\n * - 删除选中元素\n * - 保存 / 全屏切换\n * - 缩放控制 (放大 / 缩小 / 适应画布)\n * - 自定义按钮扩展\n * - 自动监听 EditorCore 状态, 按钮禁用联动\n *\n * 使用方式:\n * ```typescript\n * import { Toolbar } from '@huanban/editor-ui'\n *\n * const toolbar = new Toolbar(container, { core: editorCore })\n * toolbar.destroy()\n * ```\n */\n\nimport type { EditorCore } from '@huanban/rulego-editor-core'\n\n// ============================================================\n// 类型定义\n// ============================================================\n\n/**\n * 工具栏按钮定义\n */\nexport interface ToolbarButton {\n /** 按钮唯一标识 */\n id: string\n /** 按钮显示文本 (可选, 图标模式下可不设) */\n label?: string\n /** 按钮图标 (emoji 或 SVG 字符串) */\n icon: string\n /** 提示文本 (tooltip) */\n title: string\n /** 是否禁用 (可以是函数, 动态判断) */\n disabled?: boolean | (() => boolean)\n /** 分隔线 (在此按钮之前插入分隔线) */\n divider?: boolean\n /** 点击回调 */\n onClick: () => void\n}\n\n/**\n * Toolbar 配置选项\n */\nexport interface ToolbarOptions {\n /** EditorCore 实例 (必须) */\n core: EditorCore\n /** 自定义按钮列表 (追加到内置按钮之后) */\n extraButtons?: ToolbarButton[]\n /** 是否显示缩放控制, 默认 true */\n showZoom?: boolean\n /** 高度 (CSS值), 默认 '40px' */\n height?: string\n}\n\n// ============================================================\n// Toolbar 组件\n// ============================================================\n\n/**\n * 纯 DOM 实现的编辑器工具栏\n *\n * 内置按钮:\n * - ↩ 撤销 | ↪ 重做 | 🗑 删除 | 💾 保存 | ⛶ 全屏\n * - 🔍+ 放大 | 🔍- 缩小 | 🔲 适应画布\n *\n * 通过 EditorCore.store.subscribe 监听 canUndo/canRedo 等状态,\n * 动态切换按钮的 disabled 状态。\n */\nexport class Toolbar {\n /** 外层容器 */\n private container: HTMLElement\n /** EditorCore 引用 */\n private core: EditorCore\n /** 配置选项 */\n private options: Required<ToolbarOptions>\n /** 根 DOM 元素 */\n private rootEl: HTMLElement | null = null\n /** 状态订阅取消函数 */\n private unsubscribe: (() => void) | null = null\n /** 按钮 DOM 映射 (id => element) */\n private buttonElements: Map<string, HTMLButtonElement> = new Map()\n\n constructor(container: HTMLElement, options: ToolbarOptions) {\n this.container = container\n this.core = options.core\n\n this.options = {\n core: options.core,\n extraButtons: options.extraButtons ?? [],\n showZoom: options.showZoom ?? true,\n height: options.height ?? '40px',\n }\n\n this.mount()\n\n // 监听状态变化, 更新按钮禁用状态\n this.unsubscribe = this.core.store.subscribe(() => {\n this.updateButtonStates()\n })\n }\n\n // ============================================================\n // 生命周期\n // ============================================================\n\n /**\n * 挂载工具栏 DOM\n */\n private mount(): void {\n this.rootEl = document.createElement('div')\n this.rootEl.className = 'rulego-toolbar'\n this.rootEl.style.height = this.options.height\n\n // 构建内置按钮\n const buttons = this.getBuiltinButtons()\n\n // 添加自定义按钮\n if (this.options.extraButtons.length > 0) {\n buttons.push({\n id: '__divider_extra',\n icon: '',\n title: '',\n divider: true,\n onClick: () => { /* 分隔占位 */ },\n })\n buttons.push(...this.options.extraButtons)\n }\n\n // 渲染按钮\n for (const btn of buttons) {\n if (btn.divider) {\n const dividerEl = document.createElement('span')\n dividerEl.className = 'rulego-toolbar__divider'\n this.rootEl.appendChild(dividerEl)\n }\n\n if (!btn.icon && btn.divider) continue // 纯分隔线\n\n const buttonEl = document.createElement('button')\n buttonEl.className = 'rulego-toolbar__button'\n buttonEl.title = btn.title\n buttonEl.innerHTML = btn.icon\n if (btn.label) {\n const labelSpan = document.createElement('span')\n labelSpan.className = 'rulego-toolbar__button-label'\n labelSpan.textContent = btn.label\n buttonEl.appendChild(labelSpan)\n }\n\n buttonEl.addEventListener('click', (e) => {\n e.preventDefault()\n e.stopPropagation()\n btn.onClick()\n })\n\n this.buttonElements.set(btn.id, buttonEl)\n this.rootEl.appendChild(buttonEl)\n }\n\n this.container.appendChild(this.rootEl)\n\n // 初始状态更新\n this.updateButtonStates()\n }\n\n /**\n * 销毁工具栏\n */\n destroy(): void {\n if (this.unsubscribe) {\n this.unsubscribe()\n this.unsubscribe = null\n }\n if (this.rootEl && this.rootEl.parentNode) {\n this.rootEl.parentNode.removeChild(this.rootEl)\n }\n this.rootEl = null\n this.buttonElements.clear()\n }\n\n // ============================================================\n // 按钮定义\n // ============================================================\n\n /**\n * 获取内置按钮列表\n * @returns 内置按钮定义数组\n */\n private getBuiltinButtons(): ToolbarButton[] {\n const i18n = this.core.i18n\n const buttons: ToolbarButton[] = [\n {\n id: 'undo',\n icon: '↩',\n title: i18n.t('toolbar.undo') || '撤销',\n disabled: () => !(this.core.store.getState().canUndo as boolean),\n onClick: () => this.core.eventBus.emit('editor:reset'),\n },\n {\n id: 'redo',\n icon: '↪',\n title: i18n.t('toolbar.redo') || '重做',\n disabled: () => !(this.core.store.getState().canRedo as boolean),\n onClick: () => { /* 由 HistoryManager 处理 */ },\n },\n {\n id: 'delete',\n icon: '🗑',\n title: i18n.t('toolbar.delete') || '删除选中',\n divider: true,\n onClick: () => this.core.eventBus.emit('editor:delete-selected'),\n },\n {\n id: 'save',\n icon: '💾',\n title: i18n.t('toolbar.save') || '保存',\n onClick: () => this.core.eventBus.emit('editor:save', {\n data: {} as never,\n success: false,\n }),\n },\n {\n id: 'fullscreen',\n icon: '⛶',\n title: i18n.t('toolbar.fullscreen') || '全屏',\n onClick: () => this.core.eventBus.emit('editor:fullscreen'),\n },\n ]\n\n // 缩放控制\n if (this.options.showZoom) {\n buttons.push(\n {\n id: 'zoom-in',\n icon: '🔍+',\n title: i18n.t('toolbar.zoomIn') || '放大',\n divider: true,\n onClick: () => this.core.eventBus.emit('canvas:zoom', { scale: 1.1 }),\n },\n {\n id: 'zoom-out',\n icon: '🔍-',\n title: i18n.t('toolbar.zoomOut') || '缩小',\n onClick: () => this.core.eventBus.emit('canvas:zoom', { scale: 0.9 }),\n },\n {\n id: 'zoom-fit',\n icon: '🔲',\n title: i18n.t('toolbar.fitView') || '适应画布',\n onClick: () => this.core.eventBus.emit('canvas:zoom', { scale: 1 }),\n },\n )\n }\n\n return buttons\n }\n\n // ============================================================\n // 状态更新\n // ============================================================\n\n /**\n * 根据 EditorCore 的状态更新按钮的 disabled 属性\n */\n private updateButtonStates(): void {\n for (const [id, el] of this.buttonElements) {\n const btnDef = this.getBuiltinButtons().find((b) => b.id === id)\n if (btnDef && typeof btnDef.disabled === 'function') {\n el.disabled = btnDef.disabled()\n el.classList.toggle('rulego-toolbar__button--disabled', el.disabled)\n }\n }\n }\n\n // ============================================================\n // 公共 API\n // ============================================================\n\n /**\n * 获取根 DOM 元素\n */\n getElement(): HTMLElement | null {\n return this.rootEl\n }\n\n /**\n * 动态添加自定义按钮\n * @param button - 按钮定义\n */\n addButton(button: ToolbarButton): void {\n if (!this.rootEl) return\n\n const buttonEl = document.createElement('button')\n buttonEl.className = 'rulego-toolbar__button'\n buttonEl.title = button.title\n buttonEl.innerHTML = button.icon\n buttonEl.addEventListener('click', (e) => {\n e.preventDefault()\n button.onClick()\n })\n\n this.buttonElements.set(button.id, buttonEl)\n this.rootEl.appendChild(buttonEl)\n }\n}\n","/**\n * @file components/PropertyPanel.ts\n * @description 节点/边属性面板 — 纯 DOM 实现 (零框架依赖)\n *\n * 功能:\n * - 监听节点/边选中事件, 自动展示对应的属性表单\n * - 根据组件定义动态生成表单字段\n * - 支持字段类型: string, int, bool, select, code, json\n * - 字段变更实时回传给 EditorCore\n * - 空白处点击时自动关闭面板\n *\n * 使用方式:\n * ```typescript\n * import { PropertyPanel } from '@huanban/editor-ui'\n *\n * const panel = new PropertyPanel(container, { core: editorCore })\n * panel.destroy()\n * ```\n *\n * @see ComponentField — 字段定义类型\n * @see EditorCore — 编辑器核心引擎\n */\n\nimport type { EditorCore } from '@huanban/rulego-editor-core'\nimport type { ComponentField, ComponentDefinition } from '@huanban/rulego-editor-core'\n\n// ============================================================\n// 配置接口\n// ============================================================\n\n/**\n * PropertyPanel 配置选项\n */\nexport interface PropertyPanelOptions {\n /** EditorCore 实例 (必须) */\n core: EditorCore\n /** 面板标题文本 */\n title?: string\n /** 宽度, 默认 '320px' */\n width?: string\n /** 位置, 默认 'right' */\n position?: 'left' | 'right'\n}\n\n// ============================================================\n// PropertyPanel 组件\n// ============================================================\n\n/**\n * 纯 DOM 实现的属性编辑面板\n *\n * 工作流:\n * 1. 监听 core.store 中 currentNodeView / currentNodeModel 变化\n * 2. 根据 ComponentDefinition.fields 动态生成表单\n * 3. 字段变更时通过 core.eventBus 发送 'node:property-update' 事件\n * 4. 画布空白点击时清空面板内容\n */\nexport class PropertyPanel {\n /** 外层容器 */\n private container: HTMLElement\n /** EditorCore 引用 */\n private core: EditorCore\n /** 配置选项 */\n private options: Required<PropertyPanelOptions>\n /** 根 DOM 元素 */\n private rootEl: HTMLElement | null = null\n /** 表单容器 */\n private formContainer: HTMLElement | null = null\n /** 面板标题元素 */\n private titleEl: HTMLElement | null = null\n /** 状态订阅取消函数 */\n private unsubscribe: (() => void) | null = null\n /** 事件取消列表 */\n private eventCleanups: Array<() => void> = []\n /** 当前编辑的节点 LF ID */\n private currentNodeId: string | null = null\n\n constructor(container: HTMLElement, options: PropertyPanelOptions) {\n this.container = container\n this.core = options.core\n\n this.options = {\n core: options.core,\n title: options.title || this.core.i18n.t('panel.title') || '属性编辑',\n width: options.width ?? '320px',\n position: options.position ?? 'right',\n }\n\n this.mount()\n this.bindEvents()\n }\n\n // ============================================================\n // 生命周期\n // ============================================================\n\n /**\n * 创建面板 DOM\n */\n private mount(): void {\n this.rootEl = document.createElement('div')\n this.rootEl.className = `rulego-property-panel rulego-property-panel--${this.options.position}`\n this.rootEl.style.width = this.options.width\n\n // 面板头部\n const headerEl = document.createElement('div')\n headerEl.className = 'rulego-property-panel__header'\n\n this.titleEl = document.createElement('h3')\n this.titleEl.className = 'rulego-property-panel__title'\n this.titleEl.textContent = this.options.title\n\n const closeBtn = document.createElement('button')\n closeBtn.className = 'rulego-property-panel__close'\n closeBtn.textContent = '✕'\n closeBtn.addEventListener('click', () => this.clearPanel())\n\n headerEl.appendChild(this.titleEl)\n headerEl.appendChild(closeBtn)\n this.rootEl.appendChild(headerEl)\n\n // 表单容器\n this.formContainer = document.createElement('div')\n this.formContainer.className = 'rulego-property-panel__form'\n this.rootEl.appendChild(this.formContainer)\n\n // 初始隐藏\n this.rootEl.style.display = 'none'\n this.container.appendChild(this.rootEl)\n }\n\n /**\n * 绑定事件监听\n */\n private bindEvents(): void {\n // 监听节点视图和模型的组合变化\n this.unsubscribe = this.core.store.subscribe((state) => {\n const nodeView = state.currentNodeView as ComponentDefinition | null\n const nodeModel = state.currentNodeModel as Record<string, unknown>\n\n if (nodeView && nodeModel && Object.keys(nodeModel).length > 0) {\n this.showPanel(nodeView, nodeModel)\n }\n })\n\n // 画布空白点击 — 关闭面板\n const blankClickCleanup = this.core.eventBus.on('blank:click', () => {\n this.clearPanel()\n })\n this.eventCleanups.push(blankClickCleanup)\n }\n\n /**\n * 销毁面板\n */\n destroy(): void {\n if (this.unsubscribe) {\n this.unsubscribe()\n this.unsubscribe = null\n }\n for (const cleanup of this.eventCleanups) {\n cleanup()\n }\n this.eventCleanups = []\n if (this.rootEl && this.rootEl.parentNode) {\n this.rootEl.parentNode.removeChild(this.rootEl)\n }\n this.rootEl = null\n this.formContainer = null\n }\n\n // ============================================================\n // 面板展示 / 清空\n // ============================================================\n\n /**\n * 显示面板并渲染表单\n * @param view - 组件定义 (含字段列表)\n * @param model - 当前节点模型数据\n */\n private showPanel(view: ComponentDefinition, model: Record<string, unknown>): void {\n if (!this.rootEl || !this.formContainer || !this.titleEl) return\n\n this.rootEl.style.display = 'flex'\n this.titleEl.textContent = view.label || this.options.title\n\n // 清空旧表单\n this.formContainer.innerHTML = ''\n\n // 渲染基础信息区\n this.renderBasicInfo(model)\n\n // 渲染动态字段\n if (view.fields && view.fields.length > 0) {\n for (const field of view.fields) {\n const fieldEl = this.createFieldElement(field, model)\n this.formContainer.appendChild(fieldEl)\n }\n }\n }\n\n /**\n * 清空面板并隐藏\n */\n private clearPanel(): void {\n if (!this.rootEl || !this.formContainer) return\n this.formContainer.innerHTML = ''\n this.rootEl.style.display = 'none'\n this.currentNodeId = null\n }\n\n // ============================================================\n // 表单渲染\n // ============================================================\n\n /**\n * 渲染基础信息区 (节点名称等)\n * @param model - 节点模型\n */\n private renderBasicInfo(model: Record<string, unknown>): void {\n if (!this.formContainer) return\n\n const section = document.createElement('div')\n section.className = 'rulego-property-panel__section'\n\n // 节点名称\n const nameField = this.createTextField({\n name: 'name',\n type: 'string',\n defaultValue: '',\n label: this.core.i18n.t('panel.nodeName') || '节点名称',\n desc: '',\n validate: '',\n fields: null,\n }, model)\n\n section.appendChild(nameField)\n this.formContainer.appendChild(section)\n }\n\n /**\n * 创建单个表单字段元素\n * @param field - 字段定义\n * @param model - 节点模型数据\n * @returns 字段 wrapper HTMLElement\n */\n private createFieldElement(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const wrapper = document.createElement('div')\n wrapper.className = 'rulego-property-panel__field'\n\n // 字段标签\n const labelEl = document.createElement('label')\n labelEl.className = 'rulego-property-panel__label'\n labelEl.textContent = field.label || field.name\n\n if (field.desc) {\n labelEl.title = field.desc\n }\n\n wrapper.appendChild(labelEl)\n\n // 根据字段类型创建不同的输入控件\n let inputEl: HTMLElement\n switch (field.type) {\n case 'bool':\n inputEl = this.createBoolField(field, model)\n break\n case 'select':\n inputEl = this.createSelectField(field, model)\n break\n case 'code':\n case 'json':\n inputEl = this.createTextareaField(field, model)\n break\n case 'int':\n inputEl = this.createNumberField(field, model)\n break\n default:\n inputEl = this.createTextField(field, model)\n break\n }\n\n wrapper.appendChild(inputEl)\n\n // 描述文本\n if (field.desc) {\n const descEl = document.createElement('span')\n descEl.className = 'rulego-property-panel__desc'\n descEl.textContent = field.desc\n wrapper.appendChild(descEl)\n }\n\n return wrapper\n }\n\n /**\n * 文本输入框\n */\n private createTextField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const input = document.createElement('input')\n input.type = 'text'\n input.className = 'rulego-property-panel__input'\n input.name = field.name\n input.value = String(this.getModelValue(field, model) ?? '')\n input.placeholder = field.desc || ''\n\n input.addEventListener('change', () => {\n this.handleFieldChange(field.name, input.value)\n })\n\n return input\n }\n\n /**\n * 数值输入框\n */\n private createNumberField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const input = document.createElement('input')\n input.type = 'number'\n input.className = 'rulego-property-panel__input'\n input.name = field.name\n input.value = String(this.getModelValue(field, model) ?? 0)\n\n input.addEventListener('change', () => {\n this.handleFieldChange(field.name, Number(input.value))\n })\n\n return input\n }\n\n /**\n * 布尔开关\n */\n private createBoolField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const wrapper = document.createElement('div')\n wrapper.className = 'rulego-property-panel__switch-wrapper'\n\n const checkboxId = `field-${field.name}-${Date.now()}`\n const checkbox = document.createElement('input')\n checkbox.type = 'checkbox'\n checkbox.className = 'rulego-property-panel__checkbox'\n checkbox.id = checkboxId\n checkbox.checked = Boolean(this.getModelValue(field, model))\n\n const toggleLabel = document.createElement('label')\n toggleLabel.className = 'rulego-property-panel__switch'\n toggleLabel.htmlFor = checkboxId\n\n checkbox.addEventListener('change', () => {\n this.handleFieldChange(field.name, checkbox.checked)\n })\n\n wrapper.appendChild(checkbox)\n wrapper.appendChild(toggleLabel)\n\n return wrapper\n }\n\n /**\n * 下拉选择\n */\n private createSelectField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const select = document.createElement('select')\n select.className = 'rulego-property-panel__select'\n select.name = field.name\n\n const currentValue = String(this.getModelValue(field, model) ?? '')\n\n // 渲染选项\n if (field.options && field.options.length > 0) {\n for (const opt of field.options) {\n const optionEl = document.createElement('option')\n optionEl.value = opt.value\n optionEl.textContent = opt.label\n if (opt.value === currentValue) {\n optionEl.selected = true\n }\n select.appendChild(optionEl)\n }\n }\n\n select.addEventListener('change', () => {\n this.handleFieldChange(field.name, select.value)\n })\n\n return select\n }\n\n /**\n * 多行文本 / 代码编辑器\n */\n private createTextareaField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const textarea = document.createElement('textarea')\n textarea.className = 'rulego-property-panel__textarea'\n textarea.name = field.name\n textarea.rows = 6\n textarea.value = String(this.getModelValue(field, model) ?? '')\n textarea.placeholder = field.desc || ''\n\n if (field.type === 'code' || field.type === 'json') {\n textarea.spellcheck = false\n textarea.style.fontFamily = 'monospace'\n }\n\n textarea.addEventListener('change', () => {\n this.handleFieldChange(field.name, textarea.value)\n })\n\n return textarea\n }\n\n // ============================================================\n // 数据处理\n // ============================================================\n\n /**\n * 从模型中获取字段当前值\n * @param field - 字段定义\n * @param model - 节点模型\n * @returns 字段值\n */\n private getModelValue(field: ComponentField, model: Record<string, unknown>): unknown {\n // 先从 configuration 中找, 再从顶层找\n const config = model.configuration as Record<string, unknown> | undefined\n if (config && field.name in config) {\n return config[field.name]\n }\n if (field.name in model) {\n return model[field.name]\n }\n return field.defaultValue\n }\n\n /**\n * 处理字段变更, 通知 EditorCore\n * @param fieldName - 字段名\n * @param value - 新值\n */\n private handleFieldChange(fieldName: string, value: unknown): void {\n this.core.eventBus.emit('node:property-update', {\n id: this.currentNodeId || '',\n properties: { [fieldName]: value },\n })\n\n // 标记数据已修改\n this.core.store.setState({ isDirty: true })\n }\n\n // ============================================================\n // 公共 API\n // ============================================================\n\n /**\n * 获取根 DOM 元素\n */\n getElement(): HTMLElement | null {\n return this.rootEl\n }\n\n /**\n * 手动设置当前编辑的节点 ID\n * @param nodeId - LogicFlow 节点 ID\n */\n setCurrentNodeId(nodeId: string): void {\n this.currentNodeId = nodeId\n }\n\n /**\n * 手动显示面板\n * @param view - 组件定义\n * @param model - 节点模型\n * @param nodeId - 节点 ID\n */\n show(view: ComponentDefinition, model: Record<string, unknown>, nodeId?: string): void {\n if (nodeId) {\n this.currentNodeId = nodeId\n }\n this.showPanel(view, model)\n }\n\n /**\n * 手动隐藏面板\n */\n hide(): void {\n this.clearPanel()\n }\n}\n","/**\n * @file components/ContextMenu.ts\n * @description 右键上下文菜单 — 纯 DOM 实现 (零框架依赖)\n *\n * 功能:\n * - 支持节点右键菜单 (编辑 / 删除 / 复制 等)\n * - 支持画布空白处右键菜单 (粘贴 / 全选 等)\n * - 支持边右键菜单 (删除连线 / 编辑标签 等)\n * - 自动定位到鼠标位置\n * - 点击外部自动关闭\n * - 支持自定义菜单项扩展\n *\n * 使用方式:\n * ```typescript\n * import { ContextMenu } from '@huanban/editor-ui'\n *\n * const menu = new ContextMenu(container, {\n * core: editorCore,\n * items: [...] // 可选: 自定义菜单项\n * })\n * menu.destroy()\n * ```\n */\n\nimport type { EditorCore } from '@huanban/rulego-editor-core'\n\n// ============================================================\n// 类型定义\n// ============================================================\n\n/**\n * 菜单项类型\n */\nexport type MenuItemType = 'node' | 'edge' | 'canvas' | 'all'\n\n/**\n * 菜单项定义\n */\nexport interface MenuItem {\n /** 菜单项唯一标识 */\n id: string\n /** 显示文本 */\n label: string\n /** 图标 (emoji 或 SVG) */\n icon?: string\n /** 适用的目标类型 */\n target: MenuItemType | MenuItemType[]\n /** 是否禁用 (可动态) */\n disabled?: boolean | (() => boolean)\n /** 是否在此项之后添加分隔线 */\n separator?: boolean\n /** 点击回调, 接收目标元素的信息 */\n onClick: (context: MenuContext) => void\n}\n\n/**\n * 菜单上下文 — 右键时附带的目标信息\n */\nexport interface MenuContext {\n /** 目标类型 */\n type: MenuItemType\n /** 目标元素 ID (节点/边 LogicFlow ID) */\n id?: string\n /** 鼠标坐标 */\n position: { x: number; y: number }\n /** 附加数据 */\n data?: Record<string, unknown>\n}\n\n/**\n * ContextMenu 配置选项\n */\nexport interface ContextMenuOptions {\n /** EditorCore 实例 (必须) */\n core: EditorCore\n /** 自定义菜单项 (追加到内置菜单项之后) */\n items?: MenuItem[]\n /** 是否包含内置菜单项, 默认 true */\n includeBuiltin?: boolean\n}\n\n// ============================================================\n// ContextMenu 组件\n// ============================================================\n\n/**\n * 纯 DOM 实现的右键上下文菜单\n *\n * 生命周期:\n * 1. 构造时创建隐藏的菜单 DOM\n * 2. 监听右键事件, 判断目标类型并显示对应菜单\n * 3. 点击菜单项后执行回调并关闭\n * 4. 点击外部区域自动关闭\n */\nexport class ContextMenu {\n /** 画布容器 (用于事件监听范围) */\n private container: HTMLElement\n /** EditorCore 引用 */\n private core: EditorCore\n /** 菜单项列表 */\n private items: MenuItem[]\n /** 菜单根 DOM 元素 */\n private menuEl: HTMLElement | null = null\n /** 当前上下文 */\n private currentContext: MenuContext | null = null\n /** 外部点击关闭的监听函数引用 */\n private outsideClickHandler: ((e: MouseEvent) => void) | null = null\n\n constructor(container: HTMLElement, options: ContextMenuOptions) {\n this.container = container\n this.core = options.core\n\n // 合并菜单项\n const builtinItems = (options.includeBuiltin !== false) ? this.getBuiltinItems() : []\n this.items = [...builtinItems, ...(options.items ?? [])]\n\n this.mount()\n this.bindEvents()\n }\n\n // ============================================================\n // 生命周期\n // ============================================================\n\n /**\n * 创建菜单 DOM (初始隐藏)\n */\n private mount(): void {\n this.menuEl = document.createElement('div')\n this.menuEl.className = 'rulego-context-menu'\n this.menuEl.style.display = 'none'\n document.body.appendChild(this.menuEl)\n }\n\n /**\n * 绑定事件\n */\n private bindEvents(): void {\n // 右键拦截\n this.container.addEventListener('contextmenu', (e: MouseEvent) => {\n e.preventDefault()\n this.handleContextMenu(e)\n })\n\n // 外部点击关闭\n this.outsideClickHandler = (e: MouseEvent) => {\n if (this.menuEl && !this.menuEl.contains(e.target as Node)) {\n this.hide()\n }\n }\n document.addEventListener('click', this.outsideClickHandler)\n\n // ESC 键关闭\n document.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n this.hide()\n }\n })\n }\n\n /**\n * 销毁菜单\n */\n destroy(): void {\n if (this.outsideClickHandler) {\n document.removeEventListener('click', this.outsideClickHandler)\n this.outsideClickHandler = null\n }\n if (this.menuEl && this.menuEl.parentNode) {\n this.menuEl.parentNode.removeChild(this.menuEl)\n }\n this.menuEl = null\n }\n\n // ============================================================\n // 右键处理\n // ============================================================\n\n /**\n * 处理右键事件\n */\n private handleContextMenu(e: MouseEvent): void {\n const target = e.target as HTMLElement\n\n // 判断右键目标类型\n const context = this.resolveContext(target, e)\n this.currentContext = context\n\n // 过滤出适用的菜单项\n const applicableItems = this.items.filter((item) => {\n const targets = Array.isArray(item.target) ? item.target : [item.target]\n return targets.includes(context.type) || targets.includes('all')\n })\n\n if (applicableItems.length === 0) return\n\n // 渲染菜单\n this.renderMenu(applicableItems, e.clientX, e.clientY)\n }\n\n /**\n * 解析右键目标的上下文\n */\n private resolveContext(target: HTMLElement, e: MouseEvent): MenuContext {\n // 检查是否在节点上\n const nodeEl = target.closest('.lf-node') as HTMLElement | null\n if (nodeEl) {\n return {\n type: 'node',\n id: nodeEl.getAttribute('data-node-id') || undefined,\n position: { x: e.clientX, y: e.clientY },\n }\n }\n\n // 检查是否在边上\n const edgeEl = target.closest('.lf-edge') as HTMLElement | null\n if (edgeEl) {\n return {\n type: 'edge',\n id: edgeEl.getAttribute('data-edge-id') || undefined,\n position: { x: e.clientX, y: e.clientY },\n }\n }\n\n // 画布空白处\n return {\n type: 'canvas',\n position: { x: e.clientX, y: e.clientY },\n }\n }\n\n // ============================================================\n // 菜单渲染\n // ============================================================\n\n /**\n * 渲染并显示菜单\n */\n private renderMenu(items: MenuItem[], x: number, y: number): void {\n if (!this.menuEl) return\n\n this.menuEl.innerHTML = ''\n\n for (const item of items) {\n const itemEl = document.createElement('div')\n itemEl.className = 'rulego-context-menu__item'\n\n const isDisabled = typeof item.disabled === 'function' ? item.disabled() : item.disabled\n if (isDisabled) {\n itemEl.classList.add('rulego-context-menu__item--disabled')\n }\n\n // 图标\n if (item.icon) {\n const iconSpan = document.createElement('span')\n iconSpan.className = 'rulego-context-menu__icon'\n iconSpan.innerHTML = item.icon\n itemEl.appendChild(iconSpan)\n }\n\n // 文字\n const labelSpan = document.createElement('span')\n labelSpan.className = 'rulego-context-menu__label'\n labelSpan.textContent = item.label\n itemEl.appendChild(labelSpan)\n\n if (!isDisabled) {\n itemEl.addEventListener('click', () => {\n if (this.currentContext) {\n item.onClick(this.currentContext)\n }\n this.hide()\n })\n }\n\n this.menuEl.appendChild(itemEl)\n\n // 分隔线\n if (item.separator) {\n const sep = document.createElement('div')\n sep.className = 'rulego-context-menu__separator'\n this.menuEl.appendChild(sep)\n }\n }\n\n // 定位\n this.menuEl.style.left = `${x}px`\n this.menuEl.style.top = `${y}px`\n this.menuEl.style.display = 'block'\n\n // 边界检测 — 防止菜单超出视口\n requestAnimationFrame(() => {\n if (!this.menuEl) return\n const rect = this.menuEl.getBoundingClientRect()\n if (rect.right > window.innerWidth) {\n this.menuEl.style.left = `${x - rect.width}px`\n }\n if (rect.bottom > window.innerHeight) {\n this.menuEl.style.top = `${y - rect.height}px`\n }\n })\n }\n\n /**\n * 隐藏菜单\n */\n hide(): void {\n if (this.menuEl) {\n this.menuEl.style.display = 'none'\n }\n this.currentContext = null\n }\n\n // ============================================================\n // 内置菜单项\n // ============================================================\n\n /**\n * 获取内置菜单项\n */\n private getBuiltinItems(): MenuItem[] {\n const i18n = this.core.i18n\n return [\n {\n id: 'edit',\n label: i18n.t('contextMenu.edit') || '编辑',\n icon: '✏️',\n target: 'node',\n onClick: (ctx) => {\n if (ctx.id) {\n this.core.eventBus.emit('editor:show-edit-panel')\n }\n },\n },\n {\n id: 'delete',\n label: i18n.t('contextMenu.delete') || '删除',\n icon: '🗑️',\n target: ['node', 'edge'],\n separator: true,\n onClick: (ctx) => {\n if (ctx.id) {\n this.core.eventBus.emit('node:delete', { id: ctx.id })\n }\n },\n },\n {\n id: 'copy',\n label: i18n.t('contextMenu.copy') || '复制',\n icon: '📋',\n target: 'node',\n onClick: () => {\n // 由 EditorCore 或插件处理剪贴板\n },\n },\n {\n id: 'select-all',\n label: i18n.t('contextMenu.selectAll') || '全选',\n icon: '⬜',\n target: 'canvas',\n onClick: () => {\n // 由 EditorCore 处理全选\n },\n },\n ]\n }\n\n // ============================================================\n // 公共 API\n // ============================================================\n\n /**\n * 动态添加菜单项\n * @param item - 菜单项定义\n */\n addItem(item: MenuItem): void {\n this.items.push(item)\n }\n\n /**\n * 移除菜单项\n * @param id - 菜单项 ID\n */\n removeItem(id: string): void {\n this.items = this.items.filter((item) => item.id !== id)\n }\n\n /**\n * 获取菜单 DOM 元素\n */\n getElement(): HTMLElement | null {\n return this.menuEl\n }\n}\n"],"names":["Sidebar","container","options","state","groups","searchWrapper","searchIcon","searchInput","e","currentGroups","groupsContainer","filteredGroups","emptyEl","group","groupEl","headerEl","isCollapsed","arrowEl","labelEl","countEl","listEl","comp","compEl","groupColor","colorBar","nameEl","Toolbar","buttons","btn","dividerEl","buttonEl","labelSpan","i18n","id","el","btnDef","b","button","PropertyPanel","closeBtn","nodeView","nodeModel","blankClickCleanup","cleanup","view","model","field","fieldEl","section","nameField","wrapper","inputEl","descEl","input","checkboxId","checkbox","toggleLabel","select","currentValue","opt","optionEl","textarea","config","fieldName","value","nodeId","ContextMenu","builtinItems","target","context","applicableItems","item","targets","nodeEl","edgeEl","items","x","y","itemEl","isDisabled","iconSpan","sep","rect","ctx"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.umd.js","sources":["../src/components/Sidebar.ts","../src/components/Toolbar.ts","../src/components/PropertyPanel.ts","../src/components/ContextMenu.ts"],"sourcesContent":["/**\n * @file components/Sidebar.ts\n * @description 组件侧边栏 — 纯 DOM 实现 (零框架依赖)\n *\n * 功能:\n * - 按分类分组展示可用组件\n * - 支持关键字搜索过滤\n * - 支持分组折叠/展开\n * - 拖拽组件到画布 (DnD)\n * - 自动监听 EditorCore 状态变化, 更新组件列表\n *\n * 使用方式:\n * ```typescript\n * import { Sidebar } from '@huanban/editor-ui'\n *\n * const sidebar = new Sidebar(container, { core: editorCore })\n * // 卸载时\n * sidebar.destroy()\n * ```\n *\n * @see ComponentGroup — 组件分组定义\n * @see EditorCore — 编辑器核心引擎\n */\n\nimport type { EditorCore } from '@huanban/rulego-editor-core'\nimport type { ComponentDefinition, ComponentGroup } from '@huanban/rulego-editor-core'\n\n// ============================================================\n// 配置接口\n// ============================================================\n\n/**\n * Sidebar 配置选项\n */\nexport interface SidebarOptions {\n /** EditorCore 实例 (必须) */\n core: EditorCore\n /** 是否显示搜索框, 默认 true */\n searchable?: boolean\n /** 默认折叠状态, 默认 false (全部展开) */\n defaultCollapsed?: boolean\n /** 搜索框占位符文本 */\n searchPlaceholder?: string\n /** 宽度 (CSS值), 默认 '220px' */\n width?: string\n}\n\n// ============================================================\n// Sidebar 组件\n// ============================================================\n\n/**\n * 纯 DOM 实现的组件侧边栏\n *\n * 内部维护:\n * - componentGroups: 当前显示的组件分组\n * - collapsedGroups: 折叠状态 Set\n * - searchKeyword: 搜索关键字\n *\n * 通过 EditorCore.store.subscribe 监听状态更新\n */\nexport class Sidebar {\n /** 外层容器 */\n private container: HTMLElement\n /** EditorCore 引用 */\n private core: EditorCore\n /** 配置选项 */\n private options: Required<SidebarOptions>\n /** 已折叠的分组 Set */\n private collapsedGroups: Set<string> = new Set()\n /** 当前搜索关键字 */\n private searchKeyword = ''\n /** 根 DOM 元素 */\n private rootEl: HTMLElement | null = null\n /** 状态订阅取消函数 */\n private unsubscribe: (() => void) | null = null\n\n constructor(container: HTMLElement, options: SidebarOptions) {\n this.container = container\n this.core = options.core\n\n // 合并默认配置\n this.options = {\n core: options.core,\n searchable: options.searchable ?? true,\n defaultCollapsed: options.defaultCollapsed ?? false,\n searchPlaceholder: options.searchPlaceholder || this.core.i18n.t('sidebar.searchPlaceholder') || '搜索组件...',\n width: options.width ?? '220px',\n }\n\n // 初始渲染\n this.mount()\n\n // 监听组件列表变化\n this.unsubscribe = this.core.store.select(\n (state) => state.componentGroups as ComponentGroup[]\n ).subscribe(\n (groups: ComponentGroup[]) => {\n this.renderGroups(groups)\n }\n )\n }\n\n // ============================================================\n // 生命周期\n // ============================================================\n\n /**\n * 挂载侧边栏 DOM 到容器\n */\n private mount(): void {\n this.rootEl = document.createElement('div')\n this.rootEl.className = 'rulego-sidebar'\n this.rootEl.style.width = this.options.width\n\n // 搜索框\n if (this.options.searchable) {\n const searchWrapper = document.createElement('div')\n searchWrapper.className = 'rulego-sidebar__search-wrapper'\n\n const searchIcon = document.createElement('span')\n searchIcon.className = 'rulego-sidebar__search-icon'\n searchIcon.innerHTML = '🔍'\n\n const searchInput = document.createElement('input')\n searchInput.type = 'text'\n searchInput.className = 'rulego-sidebar__search'\n searchInput.placeholder = this.options.searchPlaceholder\n searchInput.addEventListener('input', (e) => {\n this.searchKeyword = (e.target as HTMLInputElement).value.toLowerCase()\n const currentGroups = this.core.store.getState().componentGroups as ComponentGroup[]\n this.renderGroups(currentGroups)\n })\n\n searchWrapper.appendChild(searchIcon)\n searchWrapper.appendChild(searchInput)\n this.rootEl.appendChild(searchWrapper)\n }\n\n // 分组容器\n const groupsContainer = document.createElement('div')\n groupsContainer.className = 'rulego-sidebar__groups'\n this.rootEl.appendChild(groupsContainer)\n\n this.container.appendChild(this.rootEl)\n\n // 首次渲染\n const groups = this.core.store.getState().componentGroups as ComponentGroup[]\n if (groups && groups.length > 0) {\n this.renderGroups(groups)\n }\n }\n\n /**\n * 销毁侧边栏, 清理所有 DOM 和事件\n */\n destroy(): void {\n if (this.unsubscribe) {\n this.unsubscribe()\n this.unsubscribe = null\n }\n if (this.rootEl && this.rootEl.parentNode) {\n this.rootEl.parentNode.removeChild(this.rootEl)\n }\n this.rootEl = null\n }\n\n // ============================================================\n // 渲染\n // ============================================================\n\n /**\n * 渲染所有组件分组\n * @param groups - 原始组件分组列表\n */\n private renderGroups(groups: ComponentGroup[]): void {\n if (!this.rootEl) return\n\n const groupsContainer = this.rootEl.querySelector('.rulego-sidebar__groups')\n if (!groupsContainer) return\n\n // 清空旧内容\n groupsContainer.innerHTML = ''\n\n // 按搜索关键字过滤\n const filteredGroups = this.filterGroups(groups)\n\n if (filteredGroups.length === 0) {\n const emptyEl = document.createElement('div')\n emptyEl.className = 'rulego-sidebar__empty'\n emptyEl.textContent = this.core.i18n.t('sidebar.noResults') || '无匹配组件'\n groupsContainer.appendChild(emptyEl)\n return\n }\n\n // 渲染每个分组\n for (const group of filteredGroups) {\n const groupEl = this.createGroupElement(group)\n groupsContainer.appendChild(groupEl)\n }\n }\n\n /**\n * 创建单个分组 DOM 元素\n * @param group - 组件分组定义\n * @returns 分组 HTMLElement\n */\n private createGroupElement(group: ComponentGroup): HTMLElement {\n const groupEl = document.createElement('div')\n groupEl.className = 'rulego-sidebar__group'\n\n // 分组标题 (可折叠)\n const headerEl = document.createElement('div')\n headerEl.className = 'rulego-sidebar__group-header'\n headerEl.style.borderLeftColor = group.color || '#ccc'\n\n const isCollapsed = this.collapsedGroups.has(group.category)\n\n // 折叠图标\n const arrowEl = document.createElement('span')\n arrowEl.className = `rulego-sidebar__arrow ${isCollapsed ? 'rulego-sidebar__arrow--collapsed' : ''}`\n arrowEl.textContent = '▼'\n\n // 分组名称\n const labelEl = document.createElement('span')\n labelEl.className = 'rulego-sidebar__group-label'\n labelEl.textContent = group.label\n\n // 组件数量\n const countEl = document.createElement('span')\n countEl.className = 'rulego-sidebar__group-count'\n countEl.textContent = `${group.components.length}`\n\n headerEl.appendChild(arrowEl)\n headerEl.appendChild(labelEl)\n headerEl.appendChild(countEl)\n\n // 点击折叠/展开\n headerEl.addEventListener('click', () => {\n if (this.collapsedGroups.has(group.category)) {\n this.collapsedGroups.delete(group.category)\n } else {\n this.collapsedGroups.add(group.category)\n }\n const currentGroups = this.core.store.getState().componentGroups as ComponentGroup[]\n this.renderGroups(currentGroups)\n })\n\n groupEl.appendChild(headerEl)\n\n // 组件列表 (折叠时不渲染)\n if (!isCollapsed) {\n const listEl = document.createElement('div')\n listEl.className = 'rulego-sidebar__component-list'\n\n for (const comp of group.components) {\n const compEl = this.createComponentElement(comp, group.color)\n listEl.appendChild(compEl)\n }\n\n groupEl.appendChild(listEl)\n }\n\n return groupEl\n }\n\n /**\n * 创建单个组件 DOM 元素 (可拖拽)\n * @param comp - 组件定义\n * @param groupColor - 分组颜色\n * @returns 组件 HTMLElement\n */\n private createComponentElement(comp: ComponentDefinition, groupColor: string): HTMLElement {\n const compEl = document.createElement('div')\n compEl.className = 'rulego-sidebar__component'\n compEl.setAttribute('draggable', 'true')\n compEl.title = comp.desc || comp.label\n\n // 颜色条\n const colorBar = document.createElement('span')\n colorBar.className = 'rulego-sidebar__component-color'\n colorBar.style.backgroundColor = comp.color || groupColor || '#ccc'\n\n // 组件名称\n const nameEl = document.createElement('span')\n nameEl.className = 'rulego-sidebar__component-name'\n nameEl.textContent = comp.label\n\n compEl.appendChild(colorBar)\n compEl.appendChild(nameEl)\n\n // 拖拽开始 — 存储组件类型数据, 供 LogicFlow DnD 消费\n compEl.addEventListener('dragstart', (e: DragEvent) => {\n if (e.dataTransfer) {\n e.dataTransfer.setData('application/rulego-component', JSON.stringify({\n type: comp.type,\n category: comp.category,\n label: comp.label,\n color: comp.color || groupColor,\n }))\n e.dataTransfer.effectAllowed = 'copy'\n }\n compEl.classList.add('rulego-sidebar__component--dragging')\n })\n\n compEl.addEventListener('dragend', () => {\n compEl.classList.remove('rulego-sidebar__component--dragging')\n })\n\n return compEl\n }\n\n // ============================================================\n // 搜索过滤\n // ============================================================\n\n /**\n * 根据搜索关键字过滤组件分组\n * @param groups - 原始分组列表\n * @returns 过滤后的分组列表 (空分组会被移除)\n */\n private filterGroups(groups: ComponentGroup[]): ComponentGroup[] {\n if (!this.searchKeyword) return groups\n\n return groups\n .map((group) => ({\n ...group,\n components: group.components.filter((comp) =>\n comp.label.toLowerCase().includes(this.searchKeyword) ||\n comp.type.toLowerCase().includes(this.searchKeyword) ||\n (comp.desc && comp.desc.toLowerCase().includes(this.searchKeyword))\n ),\n }))\n .filter((group) => group.components.length > 0)\n }\n\n // ============================================================\n // 公共 API\n // ============================================================\n\n /**\n * 手动触发刷新\n */\n refresh(): void {\n const currentGroups = this.core.store.getState().componentGroups as ComponentGroup[]\n this.renderGroups(currentGroups)\n }\n\n /**\n * 展开所有分组\n */\n expandAll(): void {\n this.collapsedGroups.clear()\n this.refresh()\n }\n\n /**\n * 折叠所有分组\n */\n collapseAll(): void {\n const groups = this.core.store.getState().componentGroups as ComponentGroup[]\n for (const group of groups) {\n this.collapsedGroups.add(group.category)\n }\n this.refresh()\n }\n\n /**\n * 获取根 DOM 元素\n */\n getElement(): HTMLElement | null {\n return this.rootEl\n }\n}\n","/**\n * @file components/Toolbar.ts\n * @description 编辑器工具栏 — 纯 DOM 实现 (零框架依赖)\n *\n * 功能:\n * - 撤销 / 重做 按钮\n * - 删除选中元素\n * - 保存 / 全屏切换\n * - 缩放控制 (放大 / 缩小 / 适应画布)\n * - 自定义按钮扩展\n * - 自动监听 EditorCore 状态, 按钮禁用联动\n *\n * 使用方式:\n * ```typescript\n * import { Toolbar } from '@huanban/editor-ui'\n *\n * const toolbar = new Toolbar(container, { core: editorCore })\n * toolbar.destroy()\n * ```\n */\n\nimport type { EditorCore } from '@huanban/rulego-editor-core'\n\n// ============================================================\n// 类型定义\n// ============================================================\n\n/**\n * 工具栏按钮定义\n */\nexport interface ToolbarButton {\n /** 按钮唯一标识 */\n id: string\n /** 按钮显示文本 (可选, 图标模式下可不设) */\n label?: string\n /** 按钮图标 (emoji 或 SVG 字符串) */\n icon: string\n /** 提示文本 (tooltip) */\n title: string\n /** 是否禁用 (可以是函数, 动态判断) */\n disabled?: boolean | (() => boolean)\n /** 分隔线 (在此按钮之前插入分隔线) */\n divider?: boolean\n /** 点击回调 */\n onClick: () => void\n}\n\n/**\n * Toolbar 配置选项\n */\nexport interface ToolbarOptions {\n /** EditorCore 实例 (必须) */\n core: EditorCore\n /** 自定义按钮列表 (追加到内置按钮之后) */\n extraButtons?: ToolbarButton[]\n /** 是否显示缩放控制, 默认 true */\n showZoom?: boolean\n /** 高度 (CSS值), 默认 '40px' */\n height?: string\n}\n\n// ============================================================\n// Toolbar 组件\n// ============================================================\n\n/**\n * 纯 DOM 实现的编辑器工具栏\n *\n * 内置按钮:\n * - ↩ 撤销 | ↪ 重做 | 🗑 删除 | 💾 保存 | ⛶ 全屏\n * - 🔍+ 放大 | 🔍- 缩小 | 🔲 适应画布\n *\n * 通过 EditorCore.store.subscribe 监听 canUndo/canRedo 等状态,\n * 动态切换按钮的 disabled 状态。\n */\nexport class Toolbar {\n /** 外层容器 */\n private container: HTMLElement\n /** EditorCore 引用 */\n private core: EditorCore\n /** 配置选项 */\n private options: Required<ToolbarOptions>\n /** 根 DOM 元素 */\n private rootEl: HTMLElement | null = null\n /** 状态订阅取消函数 */\n private unsubscribe: (() => void) | null = null\n /** 按钮 DOM 映射 (id => element) */\n private buttonElements: Map<string, HTMLButtonElement> = new Map()\n\n constructor(container: HTMLElement, options: ToolbarOptions) {\n this.container = container\n this.core = options.core\n\n this.options = {\n core: options.core,\n extraButtons: options.extraButtons ?? [],\n showZoom: options.showZoom ?? true,\n height: options.height ?? '40px',\n }\n\n this.mount()\n\n // 监听状态变化, 更新按钮禁用状态\n this.unsubscribe = this.core.store.subscribe(() => {\n this.updateButtonStates()\n })\n }\n\n // ============================================================\n // 生命周期\n // ============================================================\n\n /**\n * 挂载工具栏 DOM\n */\n private mount(): void {\n this.rootEl = document.createElement('div')\n this.rootEl.className = 'rulego-toolbar'\n this.rootEl.style.height = this.options.height\n\n // 构建内置按钮\n const buttons = this.getBuiltinButtons()\n\n // 添加自定义按钮\n if (this.options.extraButtons.length > 0) {\n buttons.push({\n id: '__divider_extra',\n icon: '',\n title: '',\n divider: true,\n onClick: () => { /* 分隔占位 */ },\n })\n buttons.push(...this.options.extraButtons)\n }\n\n // 渲染按钮\n for (const btn of buttons) {\n if (btn.divider) {\n const dividerEl = document.createElement('span')\n dividerEl.className = 'rulego-toolbar__divider'\n this.rootEl.appendChild(dividerEl)\n }\n\n if (!btn.icon && btn.divider) continue // 纯分隔线\n\n const buttonEl = document.createElement('button')\n buttonEl.className = 'rulego-toolbar__button'\n buttonEl.title = btn.title\n buttonEl.innerHTML = btn.icon\n if (btn.label) {\n const labelSpan = document.createElement('span')\n labelSpan.className = 'rulego-toolbar__button-label'\n labelSpan.textContent = btn.label\n buttonEl.appendChild(labelSpan)\n }\n\n buttonEl.addEventListener('click', (e) => {\n e.preventDefault()\n e.stopPropagation()\n btn.onClick()\n })\n\n this.buttonElements.set(btn.id, buttonEl)\n this.rootEl.appendChild(buttonEl)\n }\n\n this.container.appendChild(this.rootEl)\n\n // 初始状态更新\n this.updateButtonStates()\n }\n\n /**\n * 销毁工具栏\n */\n destroy(): void {\n if (this.unsubscribe) {\n this.unsubscribe()\n this.unsubscribe = null\n }\n if (this.rootEl && this.rootEl.parentNode) {\n this.rootEl.parentNode.removeChild(this.rootEl)\n }\n this.rootEl = null\n this.buttonElements.clear()\n }\n\n // ============================================================\n // 按钮定义\n // ============================================================\n\n /**\n * 获取内置按钮列表\n * @returns 内置按钮定义数组\n */\n private getBuiltinButtons(): ToolbarButton[] {\n const i18n = this.core.i18n\n const buttons: ToolbarButton[] = [\n {\n id: 'undo',\n icon: '↩',\n title: i18n.t('toolbar.undo') || '撤销',\n disabled: () => !(this.core.store.getState().canUndo as boolean),\n onClick: () => this.core.eventBus.emit('editor:reset'),\n },\n {\n id: 'redo',\n icon: '↪',\n title: i18n.t('toolbar.redo') || '重做',\n disabled: () => !(this.core.store.getState().canRedo as boolean),\n onClick: () => { /* 由 HistoryManager 处理 */ },\n },\n {\n id: 'delete',\n icon: '🗑',\n title: i18n.t('toolbar.delete') || '删除选中',\n divider: true,\n onClick: () => this.core.eventBus.emit('editor:delete-selected'),\n },\n {\n id: 'save',\n icon: '💾',\n title: i18n.t('toolbar.save') || '保存',\n onClick: () => this.core.eventBus.emit('editor:save', {\n data: {} as never,\n success: false,\n }),\n },\n {\n id: 'fullscreen',\n icon: '⛶',\n title: i18n.t('toolbar.fullscreen') || '全屏',\n onClick: () => this.core.eventBus.emit('editor:fullscreen'),\n },\n ]\n\n // 缩放控制\n if (this.options.showZoom) {\n buttons.push(\n {\n id: 'zoom-in',\n icon: '🔍+',\n title: i18n.t('toolbar.zoomIn') || '放大',\n divider: true,\n onClick: () => this.core.eventBus.emit('canvas:zoom', { scale: 1.1 }),\n },\n {\n id: 'zoom-out',\n icon: '🔍-',\n title: i18n.t('toolbar.zoomOut') || '缩小',\n onClick: () => this.core.eventBus.emit('canvas:zoom', { scale: 0.9 }),\n },\n {\n id: 'zoom-fit',\n icon: '🔲',\n title: i18n.t('toolbar.fitView') || '适应画布',\n onClick: () => this.core.eventBus.emit('canvas:zoom', { scale: 1 }),\n },\n )\n }\n\n return buttons\n }\n\n // ============================================================\n // 状态更新\n // ============================================================\n\n /**\n * 根据 EditorCore 的状态更新按钮的 disabled 属性\n */\n private updateButtonStates(): void {\n for (const [id, el] of this.buttonElements) {\n const btnDef = this.getBuiltinButtons().find((b) => b.id === id)\n if (btnDef && typeof btnDef.disabled === 'function') {\n el.disabled = btnDef.disabled()\n el.classList.toggle('rulego-toolbar__button--disabled', el.disabled)\n }\n }\n }\n\n // ============================================================\n // 公共 API\n // ============================================================\n\n /**\n * 获取根 DOM 元素\n */\n getElement(): HTMLElement | null {\n return this.rootEl\n }\n\n /**\n * 动态添加自定义按钮\n * @param button - 按钮定义\n */\n addButton(button: ToolbarButton): void {\n if (!this.rootEl) return\n\n const buttonEl = document.createElement('button')\n buttonEl.className = 'rulego-toolbar__button'\n buttonEl.title = button.title\n buttonEl.innerHTML = button.icon\n buttonEl.addEventListener('click', (e) => {\n e.preventDefault()\n button.onClick()\n })\n\n this.buttonElements.set(button.id, buttonEl)\n this.rootEl.appendChild(buttonEl)\n }\n}\n","/**\n * @file components/PropertyPanel.ts\n * @description 节点/边属性面板 — 纯 DOM 实现 (零框架依赖)\n *\n * 功能:\n * - 监听节点/边选中事件, 自动展示对应的属性表单\n * - 根据组件定义动态生成表单字段\n * - 支持字段类型: string, int, bool, select, code, json\n * - 字段变更实时回传给 EditorCore\n * - 空白处点击时自动关闭面板\n *\n * 使用方式:\n * ```typescript\n * import { PropertyPanel } from '@huanban/editor-ui'\n *\n * const panel = new PropertyPanel(container, { core: editorCore })\n * panel.destroy()\n * ```\n *\n * @see ComponentField — 字段定义类型\n * @see EditorCore — 编辑器核心引擎\n */\n\nimport type { EditorCore } from '@huanban/rulego-editor-core'\nimport type { ComponentField, ComponentDefinition } from '@huanban/rulego-editor-core'\n\n// ============================================================\n// 配置接口\n// ============================================================\n\n/**\n * PropertyPanel 配置选项\n */\nexport interface PropertyPanelOptions {\n /** EditorCore 实例 (必须) */\n core: EditorCore\n /** 面板标题文本 */\n title?: string\n /** 宽度, 默认 '320px' */\n width?: string\n /** 位置, 默认 'right' */\n position?: 'left' | 'right'\n}\n\n// ============================================================\n// PropertyPanel 组件\n// ============================================================\n\n/**\n * 纯 DOM 实现的属性编辑面板\n *\n * 工作流:\n * 1. 监听 core.store 中 currentNodeView / currentNodeModel 变化\n * 2. 根据 ComponentDefinition.fields 动态生成表单\n * 3. 字段变更时通过 core.eventBus 发送 'node:property-update' 事件\n * 4. 画布空白点击时清空面板内容\n */\nexport class PropertyPanel {\n /** 外层容器 */\n private container: HTMLElement\n /** EditorCore 引用 */\n private core: EditorCore\n /** 配置选项 */\n private options: Required<PropertyPanelOptions>\n /** 根 DOM 元素 */\n private rootEl: HTMLElement | null = null\n /** 表单容器 */\n private formContainer: HTMLElement | null = null\n /** 面板标题元素 */\n private titleEl: HTMLElement | null = null\n /** 状态订阅取消函数 */\n private unsubscribe: (() => void) | null = null\n /** 事件取消列表 */\n private eventCleanups: Array<() => void> = []\n /** 当前编辑的节点 LF ID */\n private currentNodeId: string | null = null\n\n constructor(container: HTMLElement, options: PropertyPanelOptions) {\n this.container = container\n this.core = options.core\n\n this.options = {\n core: options.core,\n title: options.title || this.core.i18n.t('panel.title') || '属性编辑',\n width: options.width ?? '320px',\n position: options.position ?? 'right',\n }\n\n this.mount()\n this.bindEvents()\n }\n\n // ============================================================\n // 生命周期\n // ============================================================\n\n /**\n * 创建面板 DOM\n */\n private mount(): void {\n this.rootEl = document.createElement('div')\n this.rootEl.className = `rulego-property-panel rulego-property-panel--${this.options.position}`\n this.rootEl.style.width = this.options.width\n\n // 面板头部\n const headerEl = document.createElement('div')\n headerEl.className = 'rulego-property-panel__header'\n\n this.titleEl = document.createElement('h3')\n this.titleEl.className = 'rulego-property-panel__title'\n this.titleEl.textContent = this.options.title\n\n const closeBtn = document.createElement('button')\n closeBtn.className = 'rulego-property-panel__close'\n closeBtn.textContent = '✕'\n closeBtn.addEventListener('click', () => this.clearPanel())\n\n headerEl.appendChild(this.titleEl)\n headerEl.appendChild(closeBtn)\n this.rootEl.appendChild(headerEl)\n\n // 表单容器\n this.formContainer = document.createElement('div')\n this.formContainer.className = 'rulego-property-panel__form'\n this.rootEl.appendChild(this.formContainer)\n\n // 初始隐藏\n this.rootEl.style.display = 'none'\n this.container.appendChild(this.rootEl)\n }\n\n /**\n * 绑定事件监听\n */\n private bindEvents(): void {\n // 监听节点视图和模型的组合变化\n this.unsubscribe = this.core.store.subscribe((state) => {\n const nodeView = state.currentNodeView as ComponentDefinition | null\n const nodeModel = state.currentNodeModel as Record<string, unknown>\n\n if (nodeView && nodeModel && Object.keys(nodeModel).length > 0) {\n this.showPanel(nodeView, nodeModel)\n }\n })\n\n // 画布空白点击 — 关闭面板\n const blankClickCleanup = this.core.eventBus.on('blank:click', () => {\n this.clearPanel()\n })\n this.eventCleanups.push(blankClickCleanup)\n }\n\n /**\n * 销毁面板\n */\n destroy(): void {\n if (this.unsubscribe) {\n this.unsubscribe()\n this.unsubscribe = null\n }\n for (const cleanup of this.eventCleanups) {\n cleanup()\n }\n this.eventCleanups = []\n if (this.rootEl && this.rootEl.parentNode) {\n this.rootEl.parentNode.removeChild(this.rootEl)\n }\n this.rootEl = null\n this.formContainer = null\n }\n\n // ============================================================\n // 面板展示 / 清空\n // ============================================================\n\n /**\n * 显示面板并渲染表单\n * @param view - 组件定义 (含字段列表)\n * @param model - 当前节点模型数据\n */\n private showPanel(view: ComponentDefinition, model: Record<string, unknown>): void {\n if (!this.rootEl || !this.formContainer || !this.titleEl) return\n\n this.rootEl.style.display = 'flex'\n this.titleEl.textContent = view.label || this.options.title\n\n // 清空旧表单\n this.formContainer.innerHTML = ''\n\n // 渲染基础信息区\n this.renderBasicInfo(model)\n\n // 渲染动态字段\n if (view.fields && view.fields.length > 0) {\n for (const field of view.fields) {\n const fieldEl = this.createFieldElement(field, model)\n this.formContainer.appendChild(fieldEl)\n }\n }\n }\n\n /**\n * 清空面板并隐藏\n */\n private clearPanel(): void {\n if (!this.rootEl || !this.formContainer) return\n this.formContainer.innerHTML = ''\n this.rootEl.style.display = 'none'\n this.currentNodeId = null\n }\n\n // ============================================================\n // 表单渲染\n // ============================================================\n\n /**\n * 渲染基础信息区 (节点名称等)\n * @param model - 节点模型\n */\n private renderBasicInfo(model: Record<string, unknown>): void {\n if (!this.formContainer) return\n\n const section = document.createElement('div')\n section.className = 'rulego-property-panel__section'\n\n // 节点名称\n const nameField = this.createTextField({\n name: 'name',\n type: 'string',\n defaultValue: '',\n label: this.core.i18n.t('panel.nodeName') || '节点名称',\n desc: '',\n validate: '',\n fields: null,\n }, model)\n\n section.appendChild(nameField)\n this.formContainer.appendChild(section)\n }\n\n /**\n * 创建单个表单字段元素\n * @param field - 字段定义\n * @param model - 节点模型数据\n * @returns 字段 wrapper HTMLElement\n */\n private createFieldElement(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const wrapper = document.createElement('div')\n wrapper.className = 'rulego-property-panel__field'\n\n // 字段标签\n const labelEl = document.createElement('label')\n labelEl.className = 'rulego-property-panel__label'\n labelEl.textContent = field.label || field.name\n\n if (field.desc) {\n labelEl.title = field.desc\n }\n\n wrapper.appendChild(labelEl)\n\n // 根据字段类型创建不同的输入控件\n let inputEl: HTMLElement\n switch (field.type) {\n case 'bool':\n inputEl = this.createBoolField(field, model)\n break\n case 'select':\n inputEl = this.createSelectField(field, model)\n break\n case 'code':\n case 'json':\n inputEl = this.createTextareaField(field, model)\n break\n case 'int':\n inputEl = this.createNumberField(field, model)\n break\n default:\n inputEl = this.createTextField(field, model)\n break\n }\n\n wrapper.appendChild(inputEl)\n\n // 描述文本\n if (field.desc) {\n const descEl = document.createElement('span')\n descEl.className = 'rulego-property-panel__desc'\n descEl.textContent = field.desc\n wrapper.appendChild(descEl)\n }\n\n return wrapper\n }\n\n /**\n * 文本输入框\n */\n private createTextField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const input = document.createElement('input')\n input.type = 'text'\n input.className = 'rulego-property-panel__input'\n input.name = field.name\n input.value = String(this.getModelValue(field, model) ?? '')\n input.placeholder = field.desc || ''\n\n input.addEventListener('change', () => {\n this.handleFieldChange(field.name, input.value)\n })\n\n return input\n }\n\n /**\n * 数值输入框\n */\n private createNumberField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const input = document.createElement('input')\n input.type = 'number'\n input.className = 'rulego-property-panel__input'\n input.name = field.name\n input.value = String(this.getModelValue(field, model) ?? 0)\n\n input.addEventListener('change', () => {\n this.handleFieldChange(field.name, Number(input.value))\n })\n\n return input\n }\n\n /**\n * 布尔开关\n */\n private createBoolField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const wrapper = document.createElement('div')\n wrapper.className = 'rulego-property-panel__switch-wrapper'\n\n const checkboxId = `field-${field.name}-${Date.now()}`\n const checkbox = document.createElement('input')\n checkbox.type = 'checkbox'\n checkbox.className = 'rulego-property-panel__checkbox'\n checkbox.id = checkboxId\n checkbox.checked = Boolean(this.getModelValue(field, model))\n\n const toggleLabel = document.createElement('label')\n toggleLabel.className = 'rulego-property-panel__switch'\n toggleLabel.htmlFor = checkboxId\n\n checkbox.addEventListener('change', () => {\n this.handleFieldChange(field.name, checkbox.checked)\n })\n\n wrapper.appendChild(checkbox)\n wrapper.appendChild(toggleLabel)\n\n return wrapper\n }\n\n /**\n * 下拉选择\n */\n private createSelectField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const select = document.createElement('select')\n select.className = 'rulego-property-panel__select'\n select.name = field.name\n\n const currentValue = String(this.getModelValue(field, model) ?? '')\n\n // 渲染选项\n if (field.options && field.options.length > 0) {\n for (const opt of field.options) {\n const optionEl = document.createElement('option')\n optionEl.value = opt.value\n optionEl.textContent = opt.label\n if (opt.value === currentValue) {\n optionEl.selected = true\n }\n select.appendChild(optionEl)\n }\n }\n\n select.addEventListener('change', () => {\n this.handleFieldChange(field.name, select.value)\n })\n\n return select\n }\n\n /**\n * 多行文本 / 代码编辑器\n */\n private createTextareaField(field: ComponentField, model: Record<string, unknown>): HTMLElement {\n const textarea = document.createElement('textarea')\n textarea.className = 'rulego-property-panel__textarea'\n textarea.name = field.name\n textarea.rows = 6\n textarea.value = String(this.getModelValue(field, model) ?? '')\n textarea.placeholder = field.desc || ''\n\n if (field.type === 'code' || field.type === 'json') {\n textarea.spellcheck = false\n textarea.style.fontFamily = 'monospace'\n }\n\n textarea.addEventListener('change', () => {\n this.handleFieldChange(field.name, textarea.value)\n })\n\n return textarea\n }\n\n // ============================================================\n // 数据处理\n // ============================================================\n\n /**\n * 从模型中获取字段当前值\n * @param field - 字段定义\n * @param model - 节点模型\n * @returns 字段值\n */\n private getModelValue(field: ComponentField, model: Record<string, unknown>): unknown {\n // 先从 configuration 中找, 再从顶层找\n const config = model.configuration as Record<string, unknown> | undefined\n if (config && field.name in config) {\n return config[field.name]\n }\n if (field.name in model) {\n return model[field.name]\n }\n return field.defaultValue\n }\n\n /**\n * 处理字段变更, 通知 EditorCore\n * @param fieldName - 字段名\n * @param value - 新值\n */\n private handleFieldChange(fieldName: string, value: unknown): void {\n this.core.eventBus.emit('node:property-update', {\n id: this.currentNodeId || '',\n properties: { [fieldName]: value },\n })\n\n // 标记数据已修改\n this.core.store.setState({ isDirty: true })\n }\n\n // ============================================================\n // 公共 API\n // ============================================================\n\n /**\n * 获取根 DOM 元素\n */\n getElement(): HTMLElement | null {\n return this.rootEl\n }\n\n /**\n * 手动设置当前编辑的节点 ID\n * @param nodeId - LogicFlow 节点 ID\n */\n setCurrentNodeId(nodeId: string): void {\n this.currentNodeId = nodeId\n }\n\n /**\n * 手动显示面板\n * @param view - 组件定义\n * @param model - 节点模型\n * @param nodeId - 节点 ID\n */\n show(view: ComponentDefinition, model: Record<string, unknown>, nodeId?: string): void {\n if (nodeId) {\n this.currentNodeId = nodeId\n }\n this.showPanel(view, model)\n }\n\n /**\n * 手动隐藏面板\n */\n hide(): void {\n this.clearPanel()\n }\n}\n","/**\n * @file components/ContextMenu.ts\n * @description 右键上下文菜单 — 纯 DOM 实现 (零框架依赖)\n *\n * 功能:\n * - 支持节点右键菜单 (编辑 / 删除 / 复制 等)\n * - 支持画布空白处右键菜单 (粘贴 / 全选 等)\n * - 支持边右键菜单 (删除连线 / 编辑标签 等)\n * - 自动定位到鼠标位置\n * - 点击外部自动关闭\n * - 支持自定义菜单项扩展\n *\n * 使用方式:\n * ```typescript\n * import { ContextMenu } from '@huanban/editor-ui'\n *\n * const menu = new ContextMenu(container, {\n * core: editorCore,\n * items: [...] // 可选: 自定义菜单项\n * })\n * menu.destroy()\n * ```\n */\n\nimport type { EditorCore } from '@huanban/rulego-editor-core'\n\n// ============================================================\n// 类型定义\n// ============================================================\n\n/**\n * 菜单项类型\n */\nexport type MenuItemType = 'node' | 'edge' | 'canvas' | 'all'\n\n/**\n * 菜单项定义\n */\nexport interface MenuItem {\n /** 菜单项唯一标识 */\n id: string\n /** 显示文本 */\n label: string\n /** 图标 (emoji 或 SVG) */\n icon?: string\n /** 适用的目标类型 */\n target: MenuItemType | MenuItemType[]\n /** 是否禁用 (可动态) */\n disabled?: boolean | (() => boolean)\n /** 是否在此项之后添加分隔线 */\n separator?: boolean\n /** 点击回调, 接收目标元素的信息 */\n onClick: (context: MenuContext) => void\n}\n\n/**\n * 菜单上下文 — 右键时附带的目标信息\n */\nexport interface MenuContext {\n /** 目标类型 */\n type: MenuItemType\n /** 目标元素 ID (节点/边 LogicFlow ID) */\n id?: string\n /** 鼠标坐标 */\n position: { x: number; y: number }\n /** 附加数据 */\n data?: Record<string, unknown>\n}\n\n/**\n * ContextMenu 配置选项\n */\nexport interface ContextMenuOptions {\n /** EditorCore 实例 (必须) */\n core: EditorCore\n /** 自定义菜单项 (追加到内置菜单项之后) */\n items?: MenuItem[]\n /** 是否包含内置菜单项, 默认 true */\n includeBuiltin?: boolean\n}\n\n// ============================================================\n// ContextMenu 组件\n// ============================================================\n\n/**\n * 纯 DOM 实现的右键上下文菜单\n *\n * 生命周期:\n * 1. 构造时创建隐藏的菜单 DOM\n * 2. 监听右键事件, 判断目标类型并显示对应菜单\n * 3. 点击菜单项后执行回调并关闭\n * 4. 点击外部区域自动关闭\n */\nexport class ContextMenu {\n /** 画布容器 (用于事件监听范围) */\n private container: HTMLElement\n /** EditorCore 引用 */\n private core: EditorCore\n /** 菜单项列表 */\n private items: MenuItem[]\n /** 菜单根 DOM 元素 */\n private menuEl: HTMLElement | null = null\n /** 当前上下文 */\n private currentContext: MenuContext | null = null\n /** 外部点击关闭的监听函数引用 */\n private outsideClickHandler: ((e: MouseEvent) => void) | null = null\n\n constructor(container: HTMLElement, options: ContextMenuOptions) {\n this.container = container\n this.core = options.core\n\n // 合并菜单项\n const builtinItems = (options.includeBuiltin !== false) ? this.getBuiltinItems() : []\n this.items = [...builtinItems, ...(options.items ?? [])]\n\n this.mount()\n this.bindEvents()\n }\n\n // ============================================================\n // 生命周期\n // ============================================================\n\n /**\n * 创建菜单 DOM (初始隐藏)\n */\n private mount(): void {\n this.menuEl = document.createElement('div')\n this.menuEl.className = 'rulego-context-menu'\n this.menuEl.style.display = 'none'\n document.body.appendChild(this.menuEl)\n }\n\n /**\n * 绑定事件\n */\n private bindEvents(): void {\n // 右键拦截\n this.container.addEventListener('contextmenu', (e: MouseEvent) => {\n e.preventDefault()\n this.handleContextMenu(e)\n })\n\n // 外部点击关闭\n this.outsideClickHandler = (e: MouseEvent) => {\n if (this.menuEl && !this.menuEl.contains(e.target as Node)) {\n this.hide()\n }\n }\n document.addEventListener('click', this.outsideClickHandler)\n\n // ESC 键关闭\n document.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n this.hide()\n }\n })\n }\n\n /**\n * 销毁菜单\n */\n destroy(): void {\n if (this.outsideClickHandler) {\n document.removeEventListener('click', this.outsideClickHandler)\n this.outsideClickHandler = null\n }\n if (this.menuEl && this.menuEl.parentNode) {\n this.menuEl.parentNode.removeChild(this.menuEl)\n }\n this.menuEl = null\n }\n\n // ============================================================\n // 右键处理\n // ============================================================\n\n /**\n * 处理右键事件\n */\n private handleContextMenu(e: MouseEvent): void {\n const target = e.target as HTMLElement\n\n // 判断右键目标类型\n const context = this.resolveContext(target, e)\n this.currentContext = context\n\n // 过滤出适用的菜单项\n const applicableItems = this.items.filter((item) => {\n const targets = Array.isArray(item.target) ? item.target : [item.target]\n return targets.includes(context.type) || targets.includes('all')\n })\n\n if (applicableItems.length === 0) return\n\n // 渲染菜单\n this.renderMenu(applicableItems, e.clientX, e.clientY)\n }\n\n /**\n * 解析右键目标的上下文\n */\n private resolveContext(target: HTMLElement, e: MouseEvent): MenuContext {\n // 检查是否在节点上\n const nodeEl = target.closest('.lf-node') as HTMLElement | null\n if (nodeEl) {\n return {\n type: 'node',\n id: nodeEl.getAttribute('data-node-id') || undefined,\n position: { x: e.clientX, y: e.clientY },\n }\n }\n\n // 检查是否在边上\n const edgeEl = target.closest('.lf-edge') as HTMLElement | null\n if (edgeEl) {\n return {\n type: 'edge',\n id: edgeEl.getAttribute('data-edge-id') || undefined,\n position: { x: e.clientX, y: e.clientY },\n }\n }\n\n // 画布空白处\n return {\n type: 'canvas',\n position: { x: e.clientX, y: e.clientY },\n }\n }\n\n // ============================================================\n // 菜单渲染\n // ============================================================\n\n /**\n * 渲染并显示菜单\n */\n private renderMenu(items: MenuItem[], x: number, y: number): void {\n if (!this.menuEl) return\n\n this.menuEl.innerHTML = ''\n\n for (const item of items) {\n const itemEl = document.createElement('div')\n itemEl.className = 'rulego-context-menu__item'\n\n const isDisabled = typeof item.disabled === 'function' ? item.disabled() : item.disabled\n if (isDisabled) {\n itemEl.classList.add('rulego-context-menu__item--disabled')\n }\n\n // 图标\n if (item.icon) {\n const iconSpan = document.createElement('span')\n iconSpan.className = 'rulego-context-menu__icon'\n iconSpan.innerHTML = item.icon\n itemEl.appendChild(iconSpan)\n }\n\n // 文字\n const labelSpan = document.createElement('span')\n labelSpan.className = 'rulego-context-menu__label'\n labelSpan.textContent = item.label\n itemEl.appendChild(labelSpan)\n\n if (!isDisabled) {\n itemEl.addEventListener('click', () => {\n if (this.currentContext) {\n item.onClick(this.currentContext)\n }\n this.hide()\n })\n }\n\n this.menuEl.appendChild(itemEl)\n\n // 分隔线\n if (item.separator) {\n const sep = document.createElement('div')\n sep.className = 'rulego-context-menu__separator'\n this.menuEl.appendChild(sep)\n }\n }\n\n // 定位\n this.menuEl.style.left = `${x}px`\n this.menuEl.style.top = `${y}px`\n this.menuEl.style.display = 'block'\n\n // 边界检测 — 防止菜单超出视口\n requestAnimationFrame(() => {\n if (!this.menuEl) return\n const rect = this.menuEl.getBoundingClientRect()\n if (rect.right > window.innerWidth) {\n this.menuEl.style.left = `${x - rect.width}px`\n }\n if (rect.bottom > window.innerHeight) {\n this.menuEl.style.top = `${y - rect.height}px`\n }\n })\n }\n\n /**\n * 隐藏菜单\n */\n hide(): void {\n if (this.menuEl) {\n this.menuEl.style.display = 'none'\n }\n this.currentContext = null\n }\n\n // ============================================================\n // 内置菜单项\n // ============================================================\n\n /**\n * 获取内置菜单项\n */\n private getBuiltinItems(): MenuItem[] {\n const i18n = this.core.i18n\n return [\n {\n id: 'edit',\n label: i18n.t('contextMenu.edit') || '编辑',\n icon: '✏️',\n target: 'node',\n onClick: (ctx) => {\n if (ctx.id) {\n this.core.eventBus.emit('editor:show-edit-panel')\n }\n },\n },\n {\n id: 'delete',\n label: i18n.t('contextMenu.delete') || '删除',\n icon: '🗑️',\n target: ['node', 'edge'],\n separator: true,\n onClick: (ctx) => {\n if (ctx.id) {\n this.core.eventBus.emit('node:delete', { id: ctx.id })\n }\n },\n },\n {\n id: 'copy',\n label: i18n.t('contextMenu.copy') || '复制',\n icon: '📋',\n target: 'node',\n onClick: () => {\n // 由 EditorCore 或插件处理剪贴板\n },\n },\n {\n id: 'select-all',\n label: i18n.t('contextMenu.selectAll') || '全选',\n icon: '⬜',\n target: 'canvas',\n onClick: () => {\n // 由 EditorCore 处理全选\n },\n },\n ]\n }\n\n // ============================================================\n // 公共 API\n // ============================================================\n\n /**\n * 动态添加菜单项\n * @param item - 菜单项定义\n */\n addItem(item: MenuItem): void {\n this.items.push(item)\n }\n\n /**\n * 移除菜单项\n * @param id - 菜单项 ID\n */\n removeItem(id: string): void {\n this.items = this.items.filter((item) => item.id !== id)\n }\n\n /**\n * 获取菜单 DOM 元素\n */\n getElement(): HTMLElement | null {\n return this.menuEl\n }\n}\n"],"names":["Sidebar","container","options","state","groups","searchWrapper","searchIcon","searchInput","e","currentGroups","groupsContainer","filteredGroups","emptyEl","group","groupEl","headerEl","isCollapsed","arrowEl","labelEl","countEl","listEl","comp","compEl","groupColor","colorBar","nameEl","Toolbar","buttons","btn","dividerEl","buttonEl","labelSpan","i18n","id","el","btnDef","b","button","PropertyPanel","closeBtn","nodeView","nodeModel","blankClickCleanup","cleanup","view","model","field","fieldEl","section","nameField","wrapper","inputEl","descEl","input","checkboxId","checkbox","toggleLabel","select","currentValue","opt","optionEl","textarea","config","fieldName","value","nodeId","ContextMenu","builtinItems","target","context","applicableItems","item","targets","nodeEl","edgeEl","items","x","y","itemEl","isDisabled","iconSpan","sep","rect","ctx"],"mappings":"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"}