@xbeeant/form-engine-designer 0.0.3 → 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.
Files changed (131) hide show
  1. package/dist/es/Canvas.js +42 -47
  2. package/dist/es/Canvas.js.map +1 -1
  3. package/dist/es/Designer.js +121 -98
  4. package/dist/es/Designer.js.map +1 -1
  5. package/dist/es/DesignerContext.js +86 -73
  6. package/dist/es/DesignerContext.js.map +1 -1
  7. package/dist/es/Palette.js +3 -3
  8. package/dist/es/Palette.js.map +1 -1
  9. package/dist/es/PropertyPanel.js +99 -100
  10. package/dist/es/PropertyPanel.js.map +1 -1
  11. package/dist/es/catalog.js +17 -10
  12. package/dist/es/catalog.js.map +1 -1
  13. package/dist/es/designer-context.js +110 -0
  14. package/dist/es/designer-context.js.map +1 -0
  15. package/dist/es/history.js +73 -0
  16. package/dist/es/history.js.map +1 -0
  17. package/dist/es/index.css +1 -1
  18. package/dist/es/index.js +3 -3
  19. package/dist/es/property/basic-property.js +6 -0
  20. package/dist/es/property/basic-property.js.map +1 -1
  21. package/dist/es/property-panel.js +294 -0
  22. package/dist/es/property-panel.js.map +1 -0
  23. package/dist/es/resizable-panel.js +103 -0
  24. package/dist/es/resizable-panel.js.map +1 -0
  25. package/dist/es/schema-utils.js +286 -0
  26. package/dist/es/schema-utils.js.map +1 -0
  27. package/dist/es/widgets/BindEditor.js +60 -49
  28. package/dist/es/widgets/BindEditor.js.map +1 -1
  29. package/dist/es/widgets/CodeEditor.js +41 -49
  30. package/dist/es/widgets/CodeEditor.js.map +1 -1
  31. package/dist/es/widgets/DefaultEditor.js +25 -30
  32. package/dist/es/widgets/DefaultEditor.js.map +1 -1
  33. package/dist/es/widgets/DependenciesEditor.js +21 -21
  34. package/dist/es/widgets/DependenciesEditor.js.map +1 -1
  35. package/dist/es/widgets/EnumEditor.js +25 -31
  36. package/dist/es/widgets/EnumEditor.js.map +1 -1
  37. package/dist/es/widgets/ExpressionSwitch.js +32 -30
  38. package/dist/es/widgets/ExpressionSwitch.js.map +1 -1
  39. package/dist/es/widgets/ReactionsEditor.js +103 -103
  40. package/dist/es/widgets/ReactionsEditor.js.map +1 -1
  41. package/dist/es/widgets/RemoteDataEditor.js +63 -63
  42. package/dist/es/widgets/RemoteDataEditor.js.map +1 -1
  43. package/dist/es/widgets/ValidateEditor.js +49 -49
  44. package/dist/es/widgets/ValidateEditor.js.map +1 -1
  45. package/dist/es/widgets/bind-editor.js +78 -0
  46. package/dist/es/widgets/bind-editor.js.map +1 -0
  47. package/dist/es/widgets/code-editor.js +76 -0
  48. package/dist/es/widgets/code-editor.js.map +1 -0
  49. package/dist/es/widgets/default-editor.js +50 -0
  50. package/dist/es/widgets/default-editor.js.map +1 -0
  51. package/dist/es/widgets/dependencies-editor.js +36 -0
  52. package/dist/es/widgets/dependencies-editor.js.map +1 -0
  53. package/dist/es/widgets/enum-editor.js +43 -0
  54. package/dist/es/widgets/enum-editor.js.map +1 -0
  55. package/dist/es/widgets/expression-builder.js +367 -0
  56. package/dist/es/widgets/expression-builder.js.map +1 -0
  57. package/dist/es/widgets/expression-model.js +340 -0
  58. package/dist/es/widgets/expression-model.js.map +1 -0
  59. package/dist/es/widgets/expression-switch.js +49 -0
  60. package/dist/es/widgets/expression-switch.js.map +1 -0
  61. package/dist/es/widgets/index.js +13 -13
  62. package/dist/es/widgets/index.js.map +1 -1
  63. package/dist/es/widgets/options-editor.js +102 -0
  64. package/dist/es/widgets/options-editor.js.map +1 -0
  65. package/dist/es/widgets/reactions-editor.js +401 -0
  66. package/dist/es/widgets/reactions-editor.js.map +1 -0
  67. package/dist/es/widgets/reactions-model.js +107 -0
  68. package/dist/es/widgets/reactions-model.js.map +1 -0
  69. package/dist/es/widgets/remote-data-editor.js +314 -0
  70. package/dist/es/widgets/remote-data-editor.js.map +1 -0
  71. package/dist/es/widgets/use-form-data-fields.js +16 -0
  72. package/dist/es/widgets/use-form-data-fields.js.map +1 -0
  73. package/dist/es/widgets/validate-editor.js +118 -0
  74. package/dist/es/widgets/validate-editor.js.map +1 -0
  75. package/package.json +4 -5
  76. package/dist/es/react/src/components/FormController.js +0 -160
  77. package/dist/es/react/src/components/FormController.js.map +0 -1
  78. package/dist/es/react/src/components/NexusField.js +0 -98
  79. package/dist/es/react/src/components/NexusField.js.map +0 -1
  80. package/dist/es/react/src/components/NexusForm.js +0 -128
  81. package/dist/es/react/src/components/NexusForm.js.map +0 -1
  82. package/dist/es/react/src/components/NexusFormProvider.js +0 -19
  83. package/dist/es/react/src/components/NexusFormProvider.js.map +0 -1
  84. package/dist/es/react/src/components/NexusLayout.js +0 -49
  85. package/dist/es/react/src/components/NexusLayout.js.map +0 -1
  86. package/dist/es/react/src/components/NexusObject.js +0 -65
  87. package/dist/es/react/src/components/NexusObject.js.map +0 -1
  88. package/dist/es/react/src/contexts/FieldInheritContext.js +0 -6
  89. package/dist/es/react/src/contexts/FieldInheritContext.js.map +0 -1
  90. package/dist/es/react/src/contexts/GridContext.js +0 -6
  91. package/dist/es/react/src/contexts/GridContext.js.map +0 -1
  92. package/dist/es/react/src/contexts/LayoutConfigContext.js +0 -6
  93. package/dist/es/react/src/contexts/LayoutConfigContext.js.map +0 -1
  94. package/dist/es/react/src/contexts/NexusContext.js +0 -13
  95. package/dist/es/react/src/contexts/NexusContext.js.map +0 -1
  96. package/dist/es/react/src/hooks/useForm.js +0 -13
  97. package/dist/es/react/src/hooks/useForm.js.map +0 -1
  98. package/dist/es/react/src/utils/renderTreeNode.js +0 -18
  99. package/dist/es/react/src/utils/renderTreeNode.js.map +0 -1
  100. package/dist/es/react/src/utils/resolveColSpan.js +0 -10
  101. package/dist/es/react/src/utils/resolveColSpan.js.map +0 -1
  102. package/src/Canvas.tsx +0 -1111
  103. package/src/Designer.css +0 -206
  104. package/src/Designer.tsx +0 -254
  105. package/src/DesignerContext.tsx +0 -192
  106. package/src/Palette.tsx +0 -261
  107. package/src/PropertyPanel.tsx +0 -486
  108. package/src/ResizablePanel.tsx +0 -210
  109. package/src/catalog.ts +0 -527
  110. package/src/index.ts +0 -25
  111. package/src/property/basic-property.ts +0 -126
  112. package/src/schemaUtils.test.ts +0 -261
  113. package/src/schemaUtils.ts +0 -627
  114. package/src/types.ts +0 -38
  115. package/src/vite-env.d.ts +0 -1
  116. package/src/widgets/BindEditor.tsx +0 -105
  117. package/src/widgets/CodeEditor.tsx +0 -114
  118. package/src/widgets/DefaultEditor.tsx +0 -73
  119. package/src/widgets/DependenciesEditor.tsx +0 -56
  120. package/src/widgets/EnumEditor.tsx +0 -66
  121. package/src/widgets/ExpressionBuilder.tsx +0 -509
  122. package/src/widgets/ExpressionSwitch.tsx +0 -62
  123. package/src/widgets/OptionsEditor.tsx +0 -141
  124. package/src/widgets/ReactionsEditor.tsx +0 -532
  125. package/src/widgets/RemoteDataEditor.tsx +0 -382
  126. package/src/widgets/ValidateEditor.tsx +0 -162
  127. package/src/widgets/expressionModel.test.ts +0 -262
  128. package/src/widgets/expressionModel.ts +0 -573
  129. package/src/widgets/index.ts +0 -45
  130. package/src/widgets/reactionsModel.ts +0 -190
  131. package/src/widgets/useFormDataFields.ts +0 -21
package/src/Designer.css DELETED
@@ -1,206 +0,0 @@
1
- /* ============================================================================
2
- * @xbeeant/form-engine-designer — Tailwind 无法表达的样式
3
- *
4
- * 注意:本包使用 Tailwind 工具类,但不在此导入 Tailwind CSS。
5
- * 消费方需在自己的 CSS 中引入 Tailwind 并通过 @source 扫描本包源码,例如:
6
- * @import "tailwindcss";
7
- * @source "../../designer/src";
8
- * ========================================================================== */
9
-
10
- /* 拖拽插入指示线:使用伪元素,无法用工具类表达 */
11
- .drop-indicator-line::before {
12
- content: "";
13
- position: absolute;
14
- left: 0;
15
- right: 0;
16
- top: 50%;
17
- transform: translateY(-50%);
18
- height: 3px;
19
- background: #1677ff;
20
- border-radius: 2px;
21
- box-shadow: 0 0 8px rgba(22, 119, 255, 0.6);
22
- animation: nexus-drop-pulse 1.2s ease-in-out infinite;
23
- }
24
-
25
- @keyframes nexus-drop-pulse {
26
- 0%,
27
- 100% {
28
- opacity: 1;
29
- }
30
- 50% {
31
- opacity: 0.5;
32
- }
33
- }
34
-
35
- /* 面板拖拽手柄 — 中间 4px 区域常驻极淡分隔线,hover 时整条 5px 通栏高亮 */
36
- .nexus-resize-handle {
37
- position: absolute;
38
- top: 0;
39
- bottom: 0;
40
- width: 5px;
41
- cursor: col-resize;
42
- z-index: 10;
43
- background: transparent;
44
- transition: background 150ms;
45
- }
46
-
47
- .nexus-resize-handle::after {
48
- content: "";
49
- position: absolute;
50
- top: 0;
51
- bottom: 0;
52
- left: 50%;
53
- transform: translateX(-50%);
54
- width: 1px;
55
- background: #e8e8e8;
56
- transition:
57
- background 150ms,
58
- width 150ms;
59
- }
60
-
61
- .nexus-resize-handle:hover,
62
- .nexus-resize-handle.nexus-resize-active {
63
- background: rgba(22, 119, 255, 0.08);
64
- }
65
-
66
- .nexus-resize-handle:hover::after,
67
- .nexus-resize-handle.nexus-resize-active::after {
68
- width: 4px;
69
- background: #1677ff;
70
- border-radius: 2px;
71
- }
72
-
73
- /* ────────────────────────────────────────────────────────────────
74
- * 侧边面板折叠按钮
75
- * ──────────────────────────────────────────────────────────────── */
76
- .nexus-panel-toggle {
77
- display: flex;
78
- align-items: center;
79
- justify-content: center;
80
- width: 22px;
81
- height: 22px;
82
- border: 1px solid #e8e8e8;
83
- border-radius: 999px;
84
- background: #fff;
85
- color: #999;
86
- cursor: pointer;
87
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
88
- transition:
89
- color 150ms,
90
- border-color 150ms,
91
- background 150ms,
92
- box-shadow 150ms;
93
- }
94
-
95
- .nexus-panel-toggle:hover {
96
- color: #1677ff;
97
- border-color: #1677ff;
98
- background: #f0f7ff;
99
- box-shadow: 0 2px 6px rgba(22, 119, 255, 0.15);
100
- }
101
-
102
- /* ────────────────────────────────────────────────────────────────
103
- * 组件面板(Palette)
104
- * ──────────────────────────────────────────────────────────────── */
105
- .nexus-palette-search.ant-input-affix-wrapper {
106
- border-radius: 8px;
107
- background: #fff;
108
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
109
- }
110
-
111
- /* 折叠分组:无边框卡片式 */
112
- .nexus-palette-collapse {
113
- background: transparent;
114
- border: none;
115
- }
116
-
117
- .nexus-palette-collapse > .ant-collapse-item {
118
- margin-bottom: 8px;
119
- overflow: hidden;
120
- border: 1px solid #f0f0f0;
121
- border-radius: 10px;
122
- background: #fff;
123
- }
124
-
125
- .nexus-palette-collapse > .ant-collapse-item:last-child {
126
- margin-bottom: 0;
127
- }
128
-
129
- .nexus-palette-collapse > .ant-collapse-item > .ant-collapse-header {
130
- padding: 9px 10px;
131
- font-size: 13px;
132
- font-weight: 600;
133
- color: #333;
134
- align-items: center;
135
- background: #fff;
136
- transition: background 150ms;
137
- }
138
-
139
- .nexus-palette-collapse > .ant-collapse-item > .ant-collapse-header:hover {
140
- background: #fafbfc;
141
- }
142
-
143
- .nexus-palette-collapse
144
- > .ant-collapse-item
145
- > .ant-collapse-header
146
- .ant-collapse-arrow {
147
- font-size: 10px;
148
- color: #999;
149
- }
150
-
151
- .nexus-palette-collapse
152
- > .ant-collapse-item
153
- > .ant-collapse-content
154
- > .ant-collapse-content-box {
155
- padding: 8px 0 4px;
156
- }
157
-
158
- /* ────────────────────────────────────────────────────────────────
159
- * 属性面板(PropertyPanel)
160
- * ──────────────────────────────────────────────────────────────── */
161
- .nexus-key-input.ant-input {
162
- border-radius: 6px;
163
- }
164
-
165
- /* 属性表单内的 antd Collapse 分区:无边框、紧凑 */
166
- .nexus-property-body .ant-collapse {
167
- background: transparent;
168
- border: none;
169
- }
170
-
171
- .nexus-property-body .ant-collapse > .ant-collapse-item {
172
- margin-bottom: 8px;
173
- overflow: hidden;
174
- border: 1px solid #f0f0f0;
175
- border-radius: 8px;
176
- background: #fff;
177
- }
178
-
179
- .nexus-property-body .ant-collapse > .ant-collapse-item:last-child {
180
- margin-bottom: 0;
181
- }
182
-
183
- .nexus-property-body .ant-collapse > .ant-collapse-item > .ant-collapse-header {
184
- padding: 8px 10px;
185
- font-size: 12px;
186
- font-weight: 600;
187
- color: #333;
188
- align-items: center;
189
- }
190
-
191
- .nexus-property-body
192
- .ant-collapse
193
- > .ant-collapse-item
194
- > .ant-collapse-header
195
- .ant-collapse-arrow {
196
- font-size: 10px;
197
- color: #999;
198
- }
199
-
200
- .nexus-property-body
201
- .ant-collapse
202
- > .ant-collapse-item
203
- > .ant-collapse-content
204
- > .ant-collapse-content-box {
205
- padding: 8px 10px 4px;
206
- }
package/src/Designer.tsx DELETED
@@ -1,254 +0,0 @@
1
- // ============================================================================
2
- // @xbeeant/form-engine-designer — 主组件
3
- // ============================================================================
4
-
5
- import type { NexusEngine, NexusSchema } from '@xbeeant/form-engine';
6
- import type { PropertySchemaMap } from '@xbeeant/form-engine-ui';
7
- import { registerAntdUI, widgetSchemas } from '@xbeeant/form-engine-ui';
8
- import {
9
- Button,
10
- Input,
11
- message,
12
- Popconfirm,
13
- Radio,
14
- Space,
15
- Typography,
16
- } from 'antd';
17
- import { useMemo, useState } from 'react';
18
- import { Canvas } from './Canvas';
19
- import './Designer.css';
20
- import { layoutCatalog, widgetCatalog } from './catalog';
21
- import { DesignerProvider, useDesigner } from './DesignerContext';
22
- import { Palette } from './Palette';
23
- import { PropertyPanel } from './PropertyPanel';
24
- import { ResizablePanel } from './ResizablePanel';
25
- import type { CatalogItem, DesignerMode, FieldDef } from './types';
26
-
27
- const DEFAULT_SCHEMA: NexusSchema = {
28
- type: 'object',
29
- displayType: 'row',
30
- properties: {},
31
- };
32
-
33
- export interface DesignerProps {
34
- schema?: NexusSchema;
35
- /**
36
- * widget 名 → 属性描述符映射,决定属性面板「组件属性」分区展示哪些配置项。
37
- * 默认使用 @xbeeant/form-engine-ui 的 widgetSchemas(antd 组件全覆盖);
38
- * 传入自定义映射可整体替换,也可在默认基础上按 widget 名覆盖(如
39
- * `{ ...widgetSchemas, input: myInputSchema }`)。
40
- */
41
- propertySchemaMap?: PropertySchemaMap;
42
- onSchemaChange?: (schema: NexusSchema) => void;
43
- /**
44
- * UI 注册函数,向引擎注册 widgets 和 layouts。
45
- * 默认使用 @xbeeant/form-engine-ui 的 registerAntdUI;接入其它 UI 库时传入自定义注册函数。
46
- */
47
- registerUI?: (engine: NexusEngine) => void;
48
- /** 外部字段列表,传入后左侧 palette 多一个「字段列表」分组 */
49
- fields?: FieldDef[];
50
- /** 额外的 widget 目录项,与内置 widgetCatalog 合并(同 widget 名时外部覆盖内置) */
51
- widgetCatalog?: CatalogItem[];
52
- /** 额外的 layout 目录项,与内置 layoutCatalog 合并(同 layoutType 时外部覆盖内置) */
53
- layoutCatalog?: CatalogItem[];
54
- }
55
-
56
- export function Designer({
57
- schema,
58
- propertySchemaMap = widgetSchemas,
59
- onSchemaChange,
60
- registerUI = registerAntdUI,
61
- fields,
62
- widgetCatalog: extraWidgetCatalog,
63
- layoutCatalog: extraLayoutCatalog,
64
- }: DesignerProps) {
65
- const initialSchema = useMemo<NexusSchema>(
66
- () => schema ?? DEFAULT_SCHEMA,
67
- [schema],
68
- );
69
-
70
- // 合并内置 catalog 与外部传入的 catalog:外部同名项覆盖内置
71
- const mergedWidgetCatalog = useMemo(
72
- () => mergeCatalog(widgetCatalog, extraWidgetCatalog, 'widget'),
73
- [extraWidgetCatalog],
74
- );
75
- const mergedLayoutCatalog = useMemo(
76
- () => mergeCatalog(layoutCatalog, extraLayoutCatalog, 'layoutType'),
77
- [extraLayoutCatalog],
78
- );
79
-
80
- return (
81
- <DesignerProvider
82
- initialSchema={initialSchema}
83
- onSchemaChange={onSchemaChange}
84
- registerUI={registerUI}
85
- fields={fields}
86
- propertySchemaMap={propertySchemaMap}
87
- widgetCatalog={mergedWidgetCatalog}
88
- layoutCatalog={mergedLayoutCatalog}
89
- >
90
- <DesignerForm />
91
- </DesignerProvider>
92
- );
93
- }
94
-
95
- // 合并内置与外部 catalog:外部项按 key 覆盖内置同名项,其余追加到末尾
96
- function mergeCatalog(
97
- builtIn: CatalogItem[],
98
- extra: CatalogItem[] | undefined,
99
- keyField: 'widget' | 'layoutType',
100
- ): CatalogItem[] {
101
- if (!extra || extra.length === 0) {
102
- return builtIn;
103
- }
104
- const extraKeys = new Set(extra.map((c) => c[keyField]).filter(Boolean));
105
- const preserved = builtIn.filter((c) => !extraKeys.has(c[keyField]));
106
- return [...preserved, ...extra];
107
- }
108
-
109
- function DesignerForm() {
110
- const { mode, setMode, schema, setSchema, selectNode } = useDesigner();
111
- const [schemaText, setSchemaText] = useState('');
112
- const [schemaActive, setSchemaActive] = useState(false);
113
-
114
- // 清空画布:重置为空 schema 并取消选中(必须新建对象,保证触发重渲染)
115
- const handleClearSchema = () => {
116
- setSchema(DEFAULT_SCHEMA);
117
- selectNode(null);
118
- if (mode === 'schema') {
119
- setSchemaText(JSON.stringify(DEFAULT_SCHEMA, null, 2));
120
- }
121
- };
122
-
123
- // 进入 schema 模式时同步当前 schema 到编辑框
124
- const handleEnterSchema = () => {
125
- setSchemaText(JSON.stringify(schema, null, 2));
126
- setSchemaActive(true);
127
- };
128
-
129
- // 离开 schema 模式时尝试解析并写回
130
- const handleLeaveSchema = () => {
131
- if (!schemaActive) {
132
- return;
133
- }
134
- try {
135
- const parsed = JSON.parse(schemaText) as NexusSchema;
136
- if (parsed && parsed.type === 'object' && parsed.properties) {
137
- setSchema(parsed);
138
- } else {
139
- message.warning('Schema 必须包含 type: "object" 和 properties');
140
- }
141
- } catch {
142
- message.error('JSON 格式错误,无法解析');
143
- }
144
- setSchemaActive(false);
145
- };
146
-
147
- // Radio.Group 切换
148
- const handleModeChange = (value: DesignerMode) => {
149
- if (value === 'schema') {
150
- handleEnterSchema();
151
- } else {
152
- handleLeaveSchema();
153
- }
154
- setMode(value);
155
- };
156
-
157
- const handleExport = () => {
158
- const json = JSON.stringify(schema, null, 2);
159
- const blob = new Blob([json], { type: 'application/json' });
160
- const url = URL.createObjectURL(blob);
161
- const a = document.createElement('a');
162
- a.href = url;
163
- a.download = 'schema.json';
164
- a.click();
165
- URL.revokeObjectURL(url);
166
- };
167
-
168
- const handleImport = () => {
169
- const input = document.createElement('input');
170
- input.type = 'file';
171
- input.accept = 'application/json';
172
- input.onchange = () => {
173
- const file = input.files?.[0];
174
- if (!file) {
175
- return;
176
- }
177
- const reader = new FileReader();
178
- reader.onload = () => {
179
- try {
180
- const parsed = JSON.parse(reader.result as string) as NexusSchema;
181
- if (parsed && parsed.type === 'object' && parsed.properties) {
182
- setSchema(parsed);
183
- }
184
- } catch {
185
- // 忽略非法 JSON
186
- }
187
- };
188
- reader.readAsText(file);
189
- };
190
- input.click();
191
- };
192
-
193
- return (
194
- <div className='flex flex-col h-full bg-white'>
195
- <div className='flex items-center justify-between px-4 py-2 border-b border-[#f0f0f0] bg-[#fafafa]'>
196
- <Typography.Text strong>Nexus 表单设计器</Typography.Text>
197
- <Space>
198
- <Radio.Group
199
- value={mode}
200
- onChange={(e) => handleModeChange(e.target.value as DesignerMode)}
201
- >
202
- <Radio.Button value='design'>设计</Radio.Button>
203
- <Radio.Button value='preview'>预览</Radio.Button>
204
- <Radio.Button value='schema'>Schema</Radio.Button>
205
- </Radio.Group>
206
- <Button onClick={handleImport}>导入</Button>
207
- <Button onClick={handleExport}>导出</Button>
208
- <Popconfirm
209
- title='清空画布'
210
- description='将移除画布上所有组件,此操作不可撤销'
211
- okText='清空'
212
- cancelText='取消'
213
- okButtonProps={{ danger: true }}
214
- onConfirm={handleClearSchema}
215
- >
216
- <Button danger>清空</Button>
217
- </Popconfirm>
218
- </Space>
219
- </div>
220
- {mode === 'schema' ? (
221
- <div className='flex-1 overflow-auto p-4 bg-[#1e1e1e]'>
222
- <Input.TextArea
223
- value={schemaText}
224
- onChange={(e) => setSchemaText(e.target.value)}
225
- autoSize={{ minRows: 20 }}
226
- className='font-mono! text-[13px]! leading-[1.6]! bg-[#1e1e1e]! text-[#d4d4d4]! border-[#3e3e42]!'
227
- />
228
- </div>
229
- ) : (
230
- <div className='flex flex-1 overflow-hidden'>
231
- <ResizablePanel
232
- side='left'
233
- defaultWidth={240}
234
- minWidth={160}
235
- maxWidth={420}
236
- collapsedTitle='组件面板'
237
- >
238
- <Palette />
239
- </ResizablePanel>
240
- <Canvas />
241
- <ResizablePanel
242
- side='right'
243
- defaultWidth={320}
244
- minWidth={240}
245
- maxWidth={560}
246
- collapsedTitle='属性面板'
247
- >
248
- <PropertyPanel />
249
- </ResizablePanel>
250
- </div>
251
- )}
252
- </div>
253
- );
254
- }
@@ -1,192 +0,0 @@
1
- // ============================================================================
2
- // @xbeeant/form-engine-designer — 设计器状态上下文
3
- // ============================================================================
4
-
5
- import type {
6
- NexusEngine,
7
- NexusSchema,
8
- SchemaNode,
9
- } from '@xbeeant/form-engine';
10
- import type { PropertySchemaMap } from '@xbeeant/form-engine-ui';
11
- import type { ReactNode } from 'react';
12
- import {
13
- createContext,
14
- useCallback,
15
- useContext,
16
- useMemo,
17
- useState,
18
- } from 'react';
19
- import {
20
- addChildToSchema,
21
- moveNodeInSchema,
22
- removeNodeFromSchema,
23
- renameNodeInSchema,
24
- updateNodeWithNesting,
25
- } from './schemaUtils';
26
- import type { CatalogItem, DesignerMode, FieldDef } from './types';
27
-
28
- /** 设计器内部状态 */
29
- export interface DesignerState {
30
- schema: NexusSchema;
31
- selectedPath: string[] | null;
32
- mode: DesignerMode;
33
- }
34
-
35
- export interface DesignerContextValue {
36
- schema: NexusSchema;
37
- propertySchemaMap: PropertySchemaMap;
38
- selectedPath: string[] | null;
39
- mode: DesignerMode;
40
- /** UI 注册函数,向引擎注册 widgets/layouts */
41
- registerUI?: (engine: NexusEngine) => void;
42
- /** 外部字段列表 */
43
- fields?: FieldDef[];
44
- /** 合并后的 widget 目录(内置 + 外部传入) */
45
- widgetCatalog: CatalogItem[];
46
- /** 合并后的 layout 目录(内置 + 外部传入) */
47
- layoutCatalog: CatalogItem[];
48
- setSchema: (schema: NexusSchema) => void;
49
- selectNode: (path: string[] | null) => void;
50
- setMode: (mode: DesignerMode) => void;
51
- addNode: (parentPath: string[], key: string, node: SchemaNode) => void;
52
- removeNode: (path: string[]) => void;
53
- updateNode: (path: string[], patch: Record<string, unknown>) => void;
54
- moveNode: (fromPath: string[], toParentPath: string[]) => void;
55
- /** 重命名节点 key(schema 结构级操作,保留节点在父级中的位置) */
56
- renameNode: (path: string[], newKey: string) => void;
57
- }
58
-
59
- interface DesignerProviderProps {
60
- initialSchema: NexusSchema;
61
- propertySchemaMap: PropertySchemaMap;
62
- onSchemaChange?: (schema: NexusSchema) => void;
63
- registerUI?: (engine: NexusEngine) => void;
64
- fields?: FieldDef[];
65
- widgetCatalog: CatalogItem[];
66
- layoutCatalog: CatalogItem[];
67
- children: ReactNode;
68
- }
69
-
70
- const DesignerContext = createContext<DesignerContextValue | null>(null);
71
-
72
- export function DesignerProvider({
73
- initialSchema,
74
- propertySchemaMap,
75
- onSchemaChange,
76
- registerUI,
77
- fields,
78
- widgetCatalog,
79
- layoutCatalog,
80
- children,
81
- }: DesignerProviderProps) {
82
- const [schema, setSchemaState] = useState<NexusSchema>(initialSchema);
83
- const [selectedPath, setSelectedPath] = useState<string[] | null>(null);
84
- const [mode, setMode] = useState<DesignerMode>('design');
85
-
86
- // 写入 schema 并同步通知外部
87
- const emit = useCallback(
88
- (next: NexusSchema) => {
89
- setSchemaState(next);
90
- onSchemaChange?.(next);
91
- },
92
- [onSchemaChange],
93
- );
94
-
95
- const setSchema = useCallback((next: NexusSchema) => emit(next), [emit]);
96
-
97
- const selectNode = useCallback((path: string[] | null) => {
98
- setSelectedPath(path);
99
- }, []);
100
-
101
- // setMode 直接复用 useState 的 setter(稳定引用,无需额外包裹)
102
-
103
- const addNode = useCallback(
104
- (parentPath: string[], key: string, node: SchemaNode) => {
105
- emit(addChildToSchema(schema, parentPath, key, node));
106
- },
107
- [schema, emit],
108
- );
109
-
110
- const removeNode = useCallback(
111
- (path: string[]) => {
112
- emit(removeNodeFromSchema(schema, path));
113
- setSelectedPath(null);
114
- },
115
- [schema, emit],
116
- );
117
-
118
- const updateNode = useCallback(
119
- (path: string[], patch: Record<string, unknown>) => {
120
- emit(updateNodeWithNesting(schema, path, patch));
121
- },
122
- [schema, emit],
123
- );
124
-
125
- const moveNode = useCallback(
126
- (fromPath: string[], toParentPath: string[]) => {
127
- emit(moveNodeInSchema(schema, fromPath, toParentPath));
128
- },
129
- [schema, emit],
130
- );
131
-
132
- const renameNode = useCallback(
133
- (path: string[], newKey: string) => {
134
- const result = renameNodeInSchema(schema, path, newKey);
135
- emit(result.schema);
136
- setSelectedPath(result.newPath);
137
- },
138
- [schema, emit],
139
- );
140
-
141
- const value = useMemo<DesignerContextValue>(
142
- () => ({
143
- schema,
144
- selectedPath,
145
- mode,
146
- registerUI,
147
- fields,
148
- widgetCatalog,
149
- layoutCatalog,
150
- propertySchemaMap,
151
- setSchema,
152
- selectNode,
153
- setMode,
154
- addNode,
155
- removeNode,
156
- updateNode,
157
- moveNode,
158
- renameNode,
159
- }),
160
- [
161
- schema,
162
- propertySchemaMap,
163
- selectedPath,
164
- mode,
165
- registerUI,
166
- fields,
167
- widgetCatalog,
168
- layoutCatalog,
169
- setSchema,
170
- selectNode,
171
- addNode,
172
- removeNode,
173
- updateNode,
174
- moveNode,
175
- renameNode,
176
- ],
177
- );
178
-
179
- return (
180
- <DesignerContext.Provider value={value}>
181
- {children}
182
- </DesignerContext.Provider>
183
- );
184
- }
185
-
186
- export function useDesigner(): DesignerContextValue {
187
- const ctx = useContext(DesignerContext);
188
- if (!ctx) {
189
- throw new Error('useDesigner 必须在 <DesignerProvider> 内使用');
190
- }
191
- return ctx;
192
- }