@coding-script/script-engine 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,8 @@
9
9
  - **动态类型自动补全**:基于 `ScriptMetadata` 提供变量名补全和点号链式访问补全(如 `request.test.name`)
10
10
  - **Groovy 语法提示**:内置 `if`/`for`/`while`/`println`/`return` 等常用 Groovy 语法片段
11
11
  - **代码格式化**:内置 Groovy 格式化器,也可通过 `onFormat` 自定义格式化逻辑
12
- - **属性面板**:右侧侧边栏展示主函数签名、变量、数据类型(字段和方法),支持折叠/展开和拖拽调节宽度
12
+ - **属性面板**:右侧侧边栏展示主函数签名、函数入参、绑定参数、数据类型(字段和方法),支持折叠/展开和拖拽调节宽度
13
+ - **脚本说明**:工具栏"脚本说明"按钮,点击展开/收起脚本描述弹框,支持多行文本和 `key: value` 格式高亮
13
14
  - **主题切换**:暗色/亮色两套主题,编辑器、补全弹窗、属性面板同步切换,编辑器内部管理主题状态
14
15
  - **全屏模式**:支持 CSS 全屏(`position: fixed` 覆盖视口),不影响编辑器内容
15
16
  - **自定义工具栏**:通过 `toolbarExtra` 传入任意 React 节点
@@ -31,6 +32,7 @@ import type { ScriptMetadata } from '@coding-script/script-engine';
31
32
 
32
33
  const metadata: ScriptMetadata = {
33
34
  mainMethod: 'run',
35
+ description: '脚本主入口,接收请求参数并返回执行结果状态码',
34
36
  returnType: 'Integer',
35
37
  binds: [
36
38
  { dataType: 'GroovyBindObject', name: '$request' },
@@ -126,7 +128,44 @@ function App() {
126
128
 
127
129
  | 属性 | 类型 | 默认值 | 说明 |
128
130
  |---|---|---|---|
129
- | `toolbarExtra` | `React.ReactNode` | `undefined` | 工具栏额外内容,渲染在内置按钮之后,可传入任意 JSX |
131
+ | `toolbar` | `ToolbarItem[]` | `undefined` | 工具栏自定义按钮列表,渲染在内置按钮之后、`toolbarExtra` 之前 |
132
+ | `toolbarExtra` | `React.ReactNode` | `undefined` | 工具栏额外内容,渲染在最后,可传入任意 JSX |
133
+
134
+ `ToolbarItem` 类型:
135
+
136
+ ```typescript
137
+ interface ToolbarItem {
138
+ key: string; // 唯一标识
139
+ label: ReactNode; // 按钮内容
140
+ title: string; // 鼠标悬停提示
141
+ backgroundColor: string;
142
+ hoverBackgroundColor: string;
143
+ textColor: string;
144
+ borderColor: string;
145
+ onClick: () => void;
146
+ }
147
+ ```
148
+
149
+ **使用按钮列表:**
150
+
151
+ ```tsx
152
+ <ScriptCodeEditor
153
+ toolbar={[
154
+ {
155
+ key: 'save',
156
+ label: '💾 保存',
157
+ title: '保存脚本',
158
+ backgroundColor: '#007bff',
159
+ hoverBackgroundColor: '#0056b3',
160
+ textColor: '#fff',
161
+ borderColor: '#007bff',
162
+ onClick: () => saveCode(),
163
+ },
164
+ ]}
165
+ />
166
+ ```
167
+
168
+ **使用自定义内容:**
130
169
 
131
170
  ```tsx
132
171
  <ScriptCodeEditor
@@ -151,8 +190,10 @@ function App() {
151
190
 
152
191
  ```typescript
153
192
  interface ScriptMetadata {
154
- /** 主函数名称 */
155
- mainMethod: string;
193
+ /** 主函数名称(可选) */
194
+ mainMethod?: string;
195
+ /** 脚本说明(可选,提供后在工具栏显示"脚本说明"按钮,点击展开/收起描述弹框) */
196
+ description?: string;
156
197
  /** 注入变量(如 $request,name 含 $ 前缀) */
157
198
  binds: ScriptBindInfo[];
158
199
  /** 主函数参数 */
@@ -241,4 +282,4 @@ pnpm run dev:app-pc
241
282
 
242
283
  ## License
243
284
 
244
- MIT
285
+ Apache-2.0 license
@@ -1,7 +1,7 @@
1
1
  export { themes, SCROLL_CLASS, ensureScrollbarStyle } from './theme-colors';
2
2
  export type { ThemeColors } from './theme-colors';
3
3
  export { ToolbarButton } from './toolbar-button';
4
- export type { ToolbarButtonProps } from './toolbar-button';
4
+ export type { ToolbarButtonProps } from '../types';
5
5
  export { Toolbar } from './toolbar';
6
6
  export type { ToolbarProps } from './toolbar';
7
7
  export { ExpandSidebarButton } from './expand-sidebar-button';
@@ -3,5 +3,7 @@ import type { ThemeColors } from './theme-colors';
3
3
  export interface SectionHeaderProps {
4
4
  colors: ThemeColors;
5
5
  label: string;
6
+ /** 标题右侧的额外操作区域(如按钮、图标) */
7
+ extra?: React.ReactNode;
6
8
  }
7
9
  export declare const SectionHeader: React.FC<SectionHeaderProps>;
@@ -1,6 +1,9 @@
1
1
  import react from "react";
2
- const SectionHeader = ({ colors, label })=>/*#__PURE__*/ react.createElement("div", {
2
+ const SectionHeader = ({ colors, label, extra })=>/*#__PURE__*/ react.createElement("div", {
3
3
  style: {
4
+ display: 'flex',
5
+ alignItems: 'center',
6
+ justifyContent: 'space-between',
4
7
  padding: '6px 12px 4px',
5
8
  fontSize: 11,
6
9
  textTransform: 'uppercase',
@@ -8,5 +11,10 @@ const SectionHeader = ({ colors, label })=>/*#__PURE__*/ react.createElement("di
8
11
  color: colors.textSecondary,
9
12
  fontWeight: 600
10
13
  }
11
- }, label);
14
+ }, /*#__PURE__*/ react.createElement("span", null, label), extra && /*#__PURE__*/ react.createElement("div", {
15
+ style: {
16
+ display: 'flex',
17
+ alignItems: 'center'
18
+ }
19
+ }, extra));
12
20
  export { SectionHeader };
@@ -1,11 +1,3 @@
1
1
  import React from 'react';
2
- export interface ToolbarButtonProps {
3
- label: React.ReactNode;
4
- title: string;
5
- bg: string;
6
- hoverBg: string;
7
- color: string;
8
- border: string;
9
- onClick: () => void;
10
- }
2
+ import type { ToolbarButtonProps } from '../types';
11
3
  export declare const ToolbarButton: React.FC<ToolbarButtonProps>;
@@ -1,5 +1,5 @@
1
1
  import react, { useState } from "react";
2
- const ToolbarButton = ({ label, title, bg, hoverBg, color, border, onClick })=>{
2
+ const ToolbarButton = ({ label, title, backgroundColor, hoverBackgroundColor, textColor, borderColor, onClick })=>{
3
3
  const [hovered, setHovered] = useState(false);
4
4
  return /*#__PURE__*/ react.createElement("button", {
5
5
  onClick: onClick,
@@ -9,9 +9,9 @@ const ToolbarButton = ({ label, title, bg, hoverBg, color, border, onClick })=>{
9
9
  display: 'inline-flex',
10
10
  alignItems: 'center',
11
11
  gap: 4,
12
- background: hovered ? hoverBg : bg,
13
- border: `1px solid ${border}`,
14
- color,
12
+ background: hovered ? hoverBackgroundColor : backgroundColor,
13
+ border: `1px solid ${borderColor}`,
14
+ color: textColor,
15
15
  cursor: 'pointer',
16
16
  fontSize: 12,
17
17
  padding: '4px 10px',
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import type { ToolbarItem } from '../types';
2
3
  export interface ToolbarProps {
3
4
  title?: string;
4
5
  theme: 'dark' | 'light';
@@ -11,6 +12,9 @@ export interface ToolbarProps {
11
12
  enableFullscreen?: boolean;
12
13
  isFullscreen?: boolean;
13
14
  onToggleFullscreen?: () => void;
15
+ toolbar?: ToolbarItem[];
14
16
  toolbarExtra?: React.ReactNode;
17
+ /** 脚本说明内容(有值时显示"脚本说明"按钮) */
18
+ description?: string;
15
19
  }
16
20
  export declare const Toolbar: React.FC<ToolbarProps>;
@@ -1,7 +1,8 @@
1
- import react from "react";
1
+ import react, { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { ToolbarButton } from "./toolbar-button.js";
3
3
  import { FormatIcon } from "./format-icon.js";
4
4
  import { MaximizeIcon, MinimizeIcon } from "./fullscreen-icon.js";
5
+ import { themes } from "./theme-colors.js";
5
6
  const HammerIcon = ({ color })=>/*#__PURE__*/ react.createElement("svg", {
6
7
  width: "14",
7
8
  height: "14",
@@ -28,13 +29,103 @@ const HammerIcon = ({ color })=>/*#__PURE__*/ react.createElement("svg", {
28
29
  opacity: 0.8,
29
30
  transform: "rotate(-25 12.25 14.5)"
30
31
  }));
31
- const Toolbar = ({ title, theme, onThemeChange, enableThemeToggle, enableFormat, onFormat, enableCompile, onCompile, enableFullscreen, isFullscreen, onToggleFullscreen, toolbarExtra })=>{
32
+ const TIP_MARGIN = 6;
33
+ const TIP_MAX_WIDTH = 380;
34
+ function calcTooltipPos(anchorRect, el) {
35
+ const vw = window.innerWidth;
36
+ const vh = window.innerHeight;
37
+ const tipH = el?.scrollHeight ?? 200;
38
+ const spaceBelow = vh - anchorRect.bottom - TIP_MARGIN;
39
+ const spaceAbove = anchorRect.top - TIP_MARGIN;
40
+ const placeBelow = spaceBelow >= Math.min(tipH, 260) || spaceBelow >= spaceAbove;
41
+ const top = placeBelow ? anchorRect.bottom + TIP_MARGIN : Math.max(TIP_MARGIN, anchorRect.top - tipH - TIP_MARGIN);
42
+ let left = anchorRect.left;
43
+ const maxRight = vw - TIP_MARGIN;
44
+ if (left + TIP_MAX_WIDTH > maxRight) left = Math.max(TIP_MARGIN, maxRight - TIP_MAX_WIDTH);
45
+ const maxWidth = Math.min(TIP_MAX_WIDTH, maxRight - left);
46
+ const maxHeight = placeBelow ? spaceBelow - TIP_MARGIN : spaceAbove - TIP_MARGIN;
47
+ return {
48
+ top,
49
+ left,
50
+ maxWidth,
51
+ maxHeight: Math.max(maxHeight, 80)
52
+ };
53
+ }
54
+ const KV_RE = /^([^:]+):\s*([\s\S]+)$/;
55
+ function renderDescLine(line, accent) {
56
+ const m = line.match(KV_RE);
57
+ if (m) return /*#__PURE__*/ react.createElement("span", null, /*#__PURE__*/ react.createElement("span", {
58
+ style: {
59
+ color: accent,
60
+ fontWeight: 600
61
+ }
62
+ }, m[1]), /*#__PURE__*/ react.createElement("span", null, ": ", m[2]));
63
+ return line;
64
+ }
65
+ const QuestionIcon = ({ color })=>/*#__PURE__*/ react.createElement("svg", {
66
+ width: "13",
67
+ height: "13",
68
+ viewBox: "0 0 24 24",
69
+ fill: "none",
70
+ stroke: color,
71
+ strokeWidth: 2.2,
72
+ strokeLinecap: "round",
73
+ strokeLinejoin: "round",
74
+ style: {
75
+ flexShrink: 0
76
+ }
77
+ }, /*#__PURE__*/ react.createElement("circle", {
78
+ cx: "12",
79
+ cy: "12",
80
+ r: "10"
81
+ }), /*#__PURE__*/ react.createElement("path", {
82
+ d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"
83
+ }), /*#__PURE__*/ react.createElement("line", {
84
+ x1: "12",
85
+ y1: "17",
86
+ x2: "12.01",
87
+ y2: "17"
88
+ }));
89
+ const Toolbar = ({ title, theme, onThemeChange, enableThemeToggle, enableFormat, onFormat, enableCompile, onCompile, enableFullscreen, isFullscreen, onToggleFullscreen, toolbar, toolbarExtra, description })=>{
32
90
  const isDark = 'dark' === theme;
91
+ const colors = themes[theme];
33
92
  const borderColor = isDark ? '#434343' : '#d9d9d9';
34
93
  const toolbarBg = isDark ? '#282c34' : '#fafafa';
35
94
  const toolbarText = isDark ? '#abb2bf' : '#333';
36
95
  const btnBg = isDark ? '#2c313a' : '#f0f0f0';
37
96
  const btnHoverBg = isDark ? '#3e4451' : '#e0e0e0';
97
+ const [showDesc, setShowDesc] = useState(false);
98
+ const [tipPos, setTipPos] = useState(null);
99
+ const descBtnRef = useRef(null);
100
+ const tipRef = useRef(null);
101
+ const updateTipPos = useCallback(()=>{
102
+ if (!descBtnRef.current) return;
103
+ setTipPos(calcTooltipPos(descBtnRef.current.getBoundingClientRect(), tipRef.current));
104
+ }, []);
105
+ useEffect(()=>{
106
+ if (!showDesc) return;
107
+ updateTipPos();
108
+ window.addEventListener('scroll', updateTipPos, true);
109
+ window.addEventListener('resize', updateTipPos);
110
+ return ()=>{
111
+ window.removeEventListener('scroll', updateTipPos, true);
112
+ window.removeEventListener('resize', updateTipPos);
113
+ };
114
+ }, [
115
+ showDesc,
116
+ updateTipPos
117
+ ]);
118
+ useEffect(()=>{
119
+ if (!showDesc) setTipPos(null);
120
+ }, [
121
+ showDesc
122
+ ]);
123
+ useEffect(()=>{
124
+ if (!description) setShowDesc(false);
125
+ }, [
126
+ description
127
+ ]);
128
+ const descLines = description ? description.replace(/\\n/g, '\n').split('\n').filter((l)=>l.trim()) : [];
38
129
  const handleThemeToggle = ()=>{
39
130
  const next = isDark ? 'light' : 'dark';
40
131
  onThemeChange?.(next);
@@ -64,33 +155,54 @@ const Toolbar = ({ title, theme, onThemeChange, enableThemeToggle, enableFormat,
64
155
  style: {
65
156
  marginRight: 'auto'
66
157
  }
67
- }), enableThemeToggle && /*#__PURE__*/ react.createElement(ToolbarButton, {
158
+ }), descLines.length > 0 && /*#__PURE__*/ react.createElement("button", {
159
+ ref: descBtnRef,
160
+ onClick: ()=>setShowDesc((v)=>!v),
161
+ style: {
162
+ display: 'inline-flex',
163
+ alignItems: 'center',
164
+ gap: 4,
165
+ padding: '4px 10px',
166
+ borderRadius: 4,
167
+ border: `1px solid ${isDark ? '#2d5a27' : '#b7eb8f'}`,
168
+ background: showDesc ? isDark ? '#3a7033' : '#d4ecd3' : isDark ? '#2d5a27' : '#e6f4e5',
169
+ color: isDark ? '#98c379' : '#389e0d',
170
+ cursor: 'pointer',
171
+ fontSize: 12,
172
+ lineHeight: 1.4,
173
+ whiteSpace: 'nowrap',
174
+ transition: 'background 0.15s'
175
+ },
176
+ title: showDesc ? '隐藏脚本说明' : '查看脚本说明'
177
+ }, /*#__PURE__*/ react.createElement(QuestionIcon, {
178
+ color: "currentColor"
179
+ }), "脚本说明"), enableThemeToggle && /*#__PURE__*/ react.createElement(ToolbarButton, {
68
180
  label: isDark ? '🌞 浅色模式' : '🌙 深色模式',
69
181
  title: isDark ? '切换到浅色主题' : '切换到深色主题',
70
- bg: btnBg,
71
- hoverBg: btnHoverBg,
72
- color: toolbarText,
73
- border: borderColor,
182
+ backgroundColor: btnBg,
183
+ hoverBackgroundColor: btnHoverBg,
184
+ textColor: toolbarText,
185
+ borderColor: borderColor,
74
186
  onClick: handleThemeToggle
75
187
  }), enableFormat && onFormat && /*#__PURE__*/ react.createElement(ToolbarButton, {
76
188
  label: /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(FormatIcon, {
77
189
  color: isDark ? '#61afef' : '#1677ff'
78
190
  }), "格式化"),
79
191
  title: "格式化代码",
80
- bg: isDark ? '#1e3a5f' : '#e6f4ff',
81
- hoverBg: isDark ? '#264a73' : '#bae0ff',
82
- color: isDark ? '#61afef' : '#1677ff',
83
- border: isDark ? '#1e3a5f' : '#91caff',
192
+ backgroundColor: isDark ? '#1e3a5f' : '#e6f4ff',
193
+ hoverBackgroundColor: isDark ? '#264a73' : '#bae0ff',
194
+ textColor: isDark ? '#61afef' : '#1677ff',
195
+ borderColor: isDark ? '#1e3a5f' : '#91caff',
84
196
  onClick: onFormat
85
197
  }), enableCompile && onCompile && /*#__PURE__*/ react.createElement(ToolbarButton, {
86
198
  label: /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(HammerIcon, {
87
199
  color: isDark ? '#98c379' : '#389e0d'
88
200
  }), "编译验证"),
89
201
  title: "编译并验证脚本",
90
- bg: isDark ? '#2d5a27' : '#e6f4e5',
91
- hoverBg: isDark ? '#3a7033' : '#d4ecd3',
92
- color: isDark ? '#98c379' : '#389e0d',
93
- border: isDark ? '#2d5a27' : '#b7eb8f',
202
+ backgroundColor: isDark ? '#2d5a27' : '#e6f4e5',
203
+ hoverBackgroundColor: isDark ? '#3a7033' : '#d4ecd3',
204
+ textColor: isDark ? '#98c379' : '#389e0d',
205
+ borderColor: isDark ? '#2d5a27' : '#b7eb8f',
94
206
  onClick: onCompile
95
207
  }), enableFullscreen && /*#__PURE__*/ react.createElement(ToolbarButton, {
96
208
  label: /*#__PURE__*/ react.createElement(react.Fragment, null, isFullscreen ? /*#__PURE__*/ react.createElement(MinimizeIcon, {
@@ -99,11 +211,46 @@ const Toolbar = ({ title, theme, onThemeChange, enableThemeToggle, enableFormat,
99
211
  color: isDark ? '#c678dd' : '#722ed1'
100
212
  }), isFullscreen ? '退出全屏' : '全屏模式'),
101
213
  title: isFullscreen ? '退出全屏(ESC)' : '全屏显示',
102
- bg: isDark ? '#3a2d5a' : '#f3e8ff',
103
- hoverBg: isDark ? '#4a3d6a' : '#e9d5ff',
104
- color: isDark ? '#c678dd' : '#722ed1',
105
- border: isDark ? '#3a2d5a' : '#d3adf7',
214
+ backgroundColor: isDark ? '#3a2d5a' : '#f3e8ff',
215
+ hoverBackgroundColor: isDark ? '#4a3d6a' : '#e9d5ff',
216
+ textColor: isDark ? '#c678dd' : '#722ed1',
217
+ borderColor: isDark ? '#3a2d5a' : '#d3adf7',
106
218
  onClick: ()=>onToggleFullscreen?.()
107
- }), toolbarExtra);
219
+ }), toolbar?.map((item)=>/*#__PURE__*/ react.createElement(ToolbarButton, {
220
+ key: item.key,
221
+ label: item.label,
222
+ title: item.title,
223
+ backgroundColor: item.backgroundColor,
224
+ hoverBackgroundColor: item.hoverBackgroundColor,
225
+ textColor: item.textColor,
226
+ borderColor: item.borderColor,
227
+ onClick: item.onClick
228
+ })), toolbarExtra, showDesc && tipPos && descLines.length > 0 && /*#__PURE__*/ react.createElement("div", {
229
+ ref: tipRef,
230
+ style: {
231
+ position: 'fixed',
232
+ top: tipPos.top,
233
+ left: tipPos.left,
234
+ maxWidth: tipPos.maxWidth,
235
+ maxHeight: tipPos.maxHeight,
236
+ overflowY: 'auto',
237
+ padding: '10px 14px',
238
+ background: colors.headerBg,
239
+ border: `1px solid ${colors.border}`,
240
+ borderRadius: 6,
241
+ color: colors.text,
242
+ fontSize: 12,
243
+ lineHeight: 1.65,
244
+ wordBreak: 'break-word',
245
+ zIndex: 9999,
246
+ boxShadow: '0 4px 16px rgba(0,0,0,0.3)',
247
+ pointerEvents: 'auto'
248
+ }
249
+ }, descLines.map((line, i)=>/*#__PURE__*/ react.createElement("div", {
250
+ key: i,
251
+ style: {
252
+ marginBottom: i < descLines.length - 1 ? 4 : 0
253
+ }
254
+ }, renderDescLine(line, colors.accent)))));
108
255
  };
109
256
  export { Toolbar };
@@ -3,21 +3,24 @@ import { SectionHeader } from "./section-header.js";
3
3
  import { VariableRow } from "./variable-row.js";
4
4
  const VariablesSection = ({ sortedBinds, sortedRequests, colors })=>{
5
5
  if (0 === sortedBinds.length && 0 === sortedRequests.length) return null;
6
- return /*#__PURE__*/ react.createElement("div", null, /*#__PURE__*/ react.createElement(SectionHeader, {
6
+ return /*#__PURE__*/ react.createElement("div", null, sortedRequests.length > 0 && /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(SectionHeader, {
7
7
  colors: colors,
8
- label: "变量"
8
+ label: "函数入参"
9
+ }), sortedRequests.map((req)=>/*#__PURE__*/ react.createElement(VariableRow, {
10
+ key: req.name,
11
+ name: req.name,
12
+ dataType: req.dataType,
13
+ description: req.description,
14
+ colors: colors
15
+ }))), sortedBinds.length > 0 && /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(SectionHeader, {
16
+ colors: colors,
17
+ label: "绑定参数"
9
18
  }), sortedBinds.map((bind)=>/*#__PURE__*/ react.createElement(VariableRow, {
10
19
  key: bind.name,
11
20
  name: bind.name,
12
21
  dataType: bind.dataType,
13
22
  description: bind.description,
14
23
  colors: colors
15
- })), sortedRequests.map((req)=>/*#__PURE__*/ react.createElement(VariableRow, {
16
- key: req.name,
17
- name: req.name,
18
- dataType: req.dataType,
19
- description: req.description,
20
- colors: colors
21
- })));
24
+ }))));
22
25
  };
23
26
  export { VariablesSection };
@@ -181,7 +181,7 @@ function buildLayoutExtensions(fontSize, minHeight, maxHeight, isFullscreen) {
181
181
  });
182
182
  }
183
183
  const ScriptCodeEditor = (props)=>{
184
- const { value, readonly = false, onChange, onCompile, onFormat, onThemeChange, placeholder = '请输入 Groovy 脚本...', defaultTheme, title, metadata, defaultSidebarOpen, enableThemeToggle, enableFormat, enableCompile, enableFullscreen, toolbarExtra, options = {} } = props;
184
+ const { value, readonly = false, onChange, onCompile, onFormat, onThemeChange, placeholder = '请输入 Groovy 脚本...', defaultTheme, title, metadata, defaultSidebarOpen, enableThemeToggle, enableFormat, enableCompile, enableFullscreen, toolbar, toolbarExtra, options = {} } = props;
185
185
  const { fontSize = 14, minHeight = 300, maxHeight = 300 } = options;
186
186
  const [internalTheme, setInternalTheme] = useState(defaultTheme ?? 'dark');
187
187
  useEffect(()=>{
@@ -368,7 +368,9 @@ const ScriptCodeEditor = (props)=>{
368
368
  enableFullscreen: enableFullscreen,
369
369
  isFullscreen: isFullscreen,
370
370
  onToggleFullscreen: ()=>setIsFullscreen((prev)=>!prev),
371
- toolbarExtra: toolbarExtra
371
+ toolbar: toolbar,
372
+ toolbarExtra: toolbarExtra,
373
+ description: metadata?.description
372
374
  }), /*#__PURE__*/ react.createElement("div", {
373
375
  style: {
374
376
  display: 'flex',
@@ -1,4 +1,26 @@
1
1
  import type React from 'react';
2
+ /** 工具栏按钮属性 */
3
+ export interface ToolbarButtonProps {
4
+ /** 按钮内容(支持 ReactNode,可包含图标 + 文字) */
5
+ label: React.ReactNode;
6
+ /** 鼠标悬停提示文字(原生 title 属性) */
7
+ title: string;
8
+ /** 按钮背景色 */
9
+ backgroundColor: string;
10
+ /** 鼠标悬停时的背景色 */
11
+ hoverBackgroundColor: string;
12
+ /** 文字颜色 */
13
+ textColor: string;
14
+ /** 边框颜色 */
15
+ borderColor: string;
16
+ /** 点击回调 */
17
+ onClick: () => void;
18
+ }
19
+ /** 工具栏自定义按钮项(用于 toolbar 数组) */
20
+ export interface ToolbarItem extends ToolbarButtonProps {
21
+ /** 唯一标识(React key) */
22
+ key: string;
23
+ }
2
24
  /** 函数参数信息 */
3
25
  export interface ScriptParameterInfo {
4
26
  dataType: string;
@@ -41,7 +63,8 @@ export interface ScriptRequestInfo {
41
63
  export interface ScriptMetadata {
42
64
  binds: ScriptBindInfo[];
43
65
  requests: ScriptRequestInfo[];
44
- mainMethod: string;
66
+ description?: string;
67
+ mainMethod?: string;
45
68
  returnType?: string;
46
69
  types: Record<string, ScriptTypeInfo>;
47
70
  }
@@ -76,6 +99,8 @@ export interface ScriptCodeEditorProps {
76
99
  onFormat?: () => void;
77
100
  /** 编译/测试脚本回调(enableCompile 时需提供) */
78
101
  onCompile?: (code: string) => void;
102
+ /** 工具栏自定义按钮列表(渲染在内置按钮之后) */
103
+ toolbar?: ToolbarItem[];
79
104
  /** 工具栏额外内容(渲染在内置按钮之后,任意 React 节点) */
80
105
  toolbarExtra?: React.ReactNode;
81
106
  /** 其他选项 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coding-script/script-engine",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "script-engine components",
5
5
  "keywords": [
6
6
  "coding-script",