@xbeeant/form-engine-designer 0.0.1

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 (117) hide show
  1. package/README.md +69 -0
  2. package/dist/es/Canvas.js +636 -0
  3. package/dist/es/Canvas.js.map +1 -0
  4. package/dist/es/Designer.js +162 -0
  5. package/dist/es/Designer.js.map +1 -0
  6. package/dist/es/DesignerContext.js +97 -0
  7. package/dist/es/DesignerContext.js.map +1 -0
  8. package/dist/es/Palette.js +148 -0
  9. package/dist/es/Palette.js.map +1 -0
  10. package/dist/es/PropertyPanel.js +294 -0
  11. package/dist/es/PropertyPanel.js.map +1 -0
  12. package/dist/es/ResizablePanel.js +103 -0
  13. package/dist/es/ResizablePanel.js.map +1 -0
  14. package/dist/es/catalog.js +514 -0
  15. package/dist/es/catalog.js.map +1 -0
  16. package/dist/es/form-engine-designer.css +1 -0
  17. package/dist/es/index.js +20 -0
  18. package/dist/es/index.js.map +1 -0
  19. package/dist/es/property/basic-property.js +115 -0
  20. package/dist/es/property/basic-property.js.map +1 -0
  21. package/dist/es/react/src/components/FormController.js +154 -0
  22. package/dist/es/react/src/components/FormController.js.map +1 -0
  23. package/dist/es/react/src/components/NexusField.js +99 -0
  24. package/dist/es/react/src/components/NexusField.js.map +1 -0
  25. package/dist/es/react/src/components/NexusForm.js +121 -0
  26. package/dist/es/react/src/components/NexusForm.js.map +1 -0
  27. package/dist/es/react/src/components/NexusFormProvider.js +19 -0
  28. package/dist/es/react/src/components/NexusFormProvider.js.map +1 -0
  29. package/dist/es/react/src/components/NexusLayout.js +49 -0
  30. package/dist/es/react/src/components/NexusLayout.js.map +1 -0
  31. package/dist/es/react/src/components/NexusObject.js +65 -0
  32. package/dist/es/react/src/components/NexusObject.js.map +1 -0
  33. package/dist/es/react/src/contexts/FieldInheritContext.js +6 -0
  34. package/dist/es/react/src/contexts/FieldInheritContext.js.map +1 -0
  35. package/dist/es/react/src/contexts/GridContext.js +6 -0
  36. package/dist/es/react/src/contexts/GridContext.js.map +1 -0
  37. package/dist/es/react/src/contexts/LayoutConfigContext.js +6 -0
  38. package/dist/es/react/src/contexts/LayoutConfigContext.js.map +1 -0
  39. package/dist/es/react/src/contexts/NexusContext.js +13 -0
  40. package/dist/es/react/src/contexts/NexusContext.js.map +1 -0
  41. package/dist/es/react/src/hooks/useForm.js +10 -0
  42. package/dist/es/react/src/hooks/useForm.js.map +1 -0
  43. package/dist/es/react/src/utils/renderTreeNode.js +18 -0
  44. package/dist/es/react/src/utils/renderTreeNode.js.map +1 -0
  45. package/dist/es/react/src/utils/resolveColSpan.js +10 -0
  46. package/dist/es/react/src/utils/resolveColSpan.js.map +1 -0
  47. package/dist/es/schemaUtils.js +286 -0
  48. package/dist/es/schemaUtils.js.map +1 -0
  49. package/dist/es/schemaUtils.test.js +188 -0
  50. package/dist/es/schemaUtils.test.js.map +1 -0
  51. package/dist/es/types.js +2 -0
  52. package/dist/es/types.js.map +1 -0
  53. package/dist/es/vite-env.d.js +2 -0
  54. package/dist/es/vite-env.d.js.map +1 -0
  55. package/dist/es/widgets/BindEditor.js +67 -0
  56. package/dist/es/widgets/BindEditor.js.map +1 -0
  57. package/dist/es/widgets/CodeEditor.js +84 -0
  58. package/dist/es/widgets/CodeEditor.js.map +1 -0
  59. package/dist/es/widgets/DefaultEditor.js +55 -0
  60. package/dist/es/widgets/DefaultEditor.js.map +1 -0
  61. package/dist/es/widgets/DependenciesEditor.js +36 -0
  62. package/dist/es/widgets/DependenciesEditor.js.map +1 -0
  63. package/dist/es/widgets/EnumEditor.js +49 -0
  64. package/dist/es/widgets/EnumEditor.js.map +1 -0
  65. package/dist/es/widgets/ExpressionBuilder.js +367 -0
  66. package/dist/es/widgets/ExpressionBuilder.js.map +1 -0
  67. package/dist/es/widgets/ExpressionSwitch.js +47 -0
  68. package/dist/es/widgets/ExpressionSwitch.js.map +1 -0
  69. package/dist/es/widgets/OptionsEditor.js +102 -0
  70. package/dist/es/widgets/OptionsEditor.js.map +1 -0
  71. package/dist/es/widgets/ReactionsEditor.js +401 -0
  72. package/dist/es/widgets/ReactionsEditor.js.map +1 -0
  73. package/dist/es/widgets/RemoteDataEditor.js +314 -0
  74. package/dist/es/widgets/RemoteDataEditor.js.map +1 -0
  75. package/dist/es/widgets/ValidateEditor.js +118 -0
  76. package/dist/es/widgets/ValidateEditor.js.map +1 -0
  77. package/dist/es/widgets/expressionModel.js +340 -0
  78. package/dist/es/widgets/expressionModel.js.map +1 -0
  79. package/dist/es/widgets/expressionModel.test.js +193 -0
  80. package/dist/es/widgets/expressionModel.test.js.map +1 -0
  81. package/dist/es/widgets/index.js +57 -0
  82. package/dist/es/widgets/index.js.map +1 -0
  83. package/dist/es/widgets/reactionsModel.js +107 -0
  84. package/dist/es/widgets/reactionsModel.js.map +1 -0
  85. package/dist/es/widgets/useFormDataFields.js +16 -0
  86. package/dist/es/widgets/useFormDataFields.js.map +1 -0
  87. package/package.json +62 -0
  88. package/src/Canvas.tsx +1111 -0
  89. package/src/Designer.css +206 -0
  90. package/src/Designer.tsx +254 -0
  91. package/src/DesignerContext.tsx +192 -0
  92. package/src/Palette.tsx +261 -0
  93. package/src/PropertyPanel.tsx +486 -0
  94. package/src/ResizablePanel.tsx +210 -0
  95. package/src/catalog.ts +526 -0
  96. package/src/index.ts +25 -0
  97. package/src/property/basic-property.ts +126 -0
  98. package/src/schemaUtils.test.ts +261 -0
  99. package/src/schemaUtils.ts +627 -0
  100. package/src/types.ts +38 -0
  101. package/src/vite-env.d.ts +1 -0
  102. package/src/widgets/BindEditor.tsx +105 -0
  103. package/src/widgets/CodeEditor.tsx +114 -0
  104. package/src/widgets/DefaultEditor.tsx +73 -0
  105. package/src/widgets/DependenciesEditor.tsx +56 -0
  106. package/src/widgets/EnumEditor.tsx +66 -0
  107. package/src/widgets/ExpressionBuilder.tsx +509 -0
  108. package/src/widgets/ExpressionSwitch.tsx +62 -0
  109. package/src/widgets/OptionsEditor.tsx +141 -0
  110. package/src/widgets/ReactionsEditor.tsx +532 -0
  111. package/src/widgets/RemoteDataEditor.tsx +382 -0
  112. package/src/widgets/ValidateEditor.tsx +162 -0
  113. package/src/widgets/expressionModel.test.ts +262 -0
  114. package/src/widgets/expressionModel.ts +573 -0
  115. package/src/widgets/index.ts +45 -0
  116. package/src/widgets/reactionsModel.ts +190 -0
  117. package/src/widgets/useFormDataFields.ts +21 -0
@@ -0,0 +1,262 @@
1
+ import type { NexusSchema } from '@xbeeant/form-engine';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { collectDataFieldPaths } from '../schemaUtils';
4
+ import {
5
+ buildExpression,
6
+ type ConditionOperator,
7
+ parseExpression,
8
+ parseVariable,
9
+ stripBraces,
10
+ variableToString,
11
+ } from './expressionModel';
12
+
13
+ describe('buildExpression', () => {
14
+ it('构建基本比较表达式(文本 / 数字 / 布尔 / 变量右值)', () => {
15
+ expect(
16
+ buildExpression([
17
+ {
18
+ id: '1',
19
+ logic: 'and',
20
+ variable: { source: 'formData', path: 'usePromo' },
21
+ operator: 'eq',
22
+ rightMode: 'boolean',
23
+ right: 'true',
24
+ },
25
+ ]),
26
+ ).toBe('{{ formData.usePromo === true }}');
27
+
28
+ expect(
29
+ buildExpression([
30
+ {
31
+ id: '1',
32
+ logic: 'and',
33
+ variable: { source: 'formData', path: 'amount' },
34
+ operator: 'gte',
35
+ rightMode: 'number',
36
+ right: '100',
37
+ },
38
+ ]),
39
+ ).toBe('{{ formData.amount >= 100 }}');
40
+
41
+ expect(
42
+ buildExpression([
43
+ {
44
+ id: '1',
45
+ logic: 'and',
46
+ variable: { source: 'self', path: '' },
47
+ operator: 'neq',
48
+ rightMode: 'string',
49
+ right: 'special',
50
+ },
51
+ ]),
52
+ ).toBe("{{ $self.value !== 'special' }}");
53
+
54
+ expect(
55
+ buildExpression([
56
+ {
57
+ id: '1',
58
+ logic: 'and',
59
+ variable: { source: 'formData', path: 'amount' },
60
+ operator: 'eq',
61
+ rightMode: 'variable',
62
+ right: 'formData.limit',
63
+ },
64
+ ]),
65
+ ).toBe('{{ formData.amount === formData.limit }}');
66
+ });
67
+
68
+ it('空右值 / 未选变量视为不完整条件,返回空模板', () => {
69
+ expect(
70
+ buildExpression([
71
+ {
72
+ id: '1',
73
+ logic: 'and',
74
+ variable: { source: 'formData', path: '' },
75
+ operator: 'eq',
76
+ rightMode: 'string',
77
+ right: 'x',
78
+ },
79
+ ]),
80
+ ).toBe('{{ }}');
81
+
82
+ expect(
83
+ buildExpression([
84
+ {
85
+ id: '1',
86
+ logic: 'and',
87
+ variable: { source: 'formData', path: 'a' },
88
+ operator: 'eq',
89
+ rightMode: 'string',
90
+ right: '',
91
+ },
92
+ ]),
93
+ ).toBe('{{ }}');
94
+ });
95
+
96
+ it('无条件时返回空模板', () => {
97
+ expect(buildExpression([])).toBe('{{ }}');
98
+ });
99
+
100
+ it('按 and/or 连接多条条件', () => {
101
+ const cond = (op: 'eq' | 'neq', right: string) => ({
102
+ id: Math.random().toString(),
103
+ logic: 'and' as const,
104
+ variable: { source: 'formData' as const, path: 'a' },
105
+ operator: op,
106
+ rightMode: 'string' as const,
107
+ right,
108
+ });
109
+ const c1 = { ...cond('eq', 'x'), id: '1' };
110
+ const c2 = { ...cond('neq', 'y'), id: '2', logic: 'or' as const };
111
+ expect(buildExpression([c1, c2])).toBe(
112
+ "{{ formData.a === 'x' || formData.a !== 'y' }}",
113
+ );
114
+ });
115
+
116
+ it('contains / startsWith / empty / truthy 等操作符', () => {
117
+ const base = (operator: ConditionOperator, right = '') => ({
118
+ id: '1',
119
+ logic: 'and' as const,
120
+ variable: { source: 'formData' as const, path: 'code' },
121
+ operator,
122
+ rightMode: 'string' as const,
123
+ right,
124
+ });
125
+ expect(buildExpression([base('contains', 'CN')])).toBe(
126
+ "{{ String(formData.code).includes('CN') }}",
127
+ );
128
+ expect(buildExpression([base('notEmpty')])).toBe(
129
+ "{{ (formData.code != null && String(formData.code).trim() !== '') }}",
130
+ );
131
+ expect(buildExpression([base('truthy')])).toBe('{{ !!(formData.code) }}');
132
+ });
133
+ });
134
+
135
+ describe('parseExpression', () => {
136
+ it('解析基本表达式为行模型', () => {
137
+ const conds = parseExpression('{{ formData.usePromo === true }}');
138
+ expect(conds).not.toBeNull();
139
+ expect(conds![0]).toMatchObject({
140
+ variable: { source: 'formData', path: 'usePromo' },
141
+ operator: 'eq',
142
+ rightMode: 'boolean',
143
+ right: 'true',
144
+ });
145
+ });
146
+
147
+ it('解析 $deps / $self / 文本字面量', () => {
148
+ const conds = parseExpression('{{ $deps[0] === true }}');
149
+ expect(conds![0].variable).toEqual({ source: 'deps', path: '0' });
150
+
151
+ const self = parseExpression("{{ $self.value === 'x' }}");
152
+ expect(self![0].variable).toEqual({ source: 'self', path: '' });
153
+ expect(self![0].rightMode).toBe('string');
154
+ expect(self![0].right).toBe('x');
155
+ });
156
+
157
+ it('解析 and/or 连接', () => {
158
+ const conds = parseExpression(
159
+ "{{ formData.a === 'x' && formData.b > 3 || formData.c == 'y' }}",
160
+ );
161
+ expect(conds!.length).toBe(3);
162
+ expect(conds![0].logic).toBe('and');
163
+ expect(conds![1].logic).toBe('and');
164
+ expect(conds![2].logic).toBe('or');
165
+ });
166
+
167
+ it('解析空模板 / 空串为 []', () => {
168
+ expect(parseExpression('{{ }}')).toEqual([]);
169
+ expect(parseExpression('')).toEqual([]);
170
+ expect(parseExpression(undefined)).toEqual([]);
171
+ });
172
+
173
+ it('无法解析的表达式返回 null(回退高级模式)', () => {
174
+ expect(parseExpression('{{ formData.a.toUpperCase() }}')).toBeNull();
175
+ expect(parseExpression('{{ $deps[0] + 1 > 2 }}')).toBeNull();
176
+ });
177
+
178
+ it('round-trip:build → parse → build 保持一致', () => {
179
+ const src = "{{ formData.a === 'x' || formData.b >= 3 }}";
180
+ const conds = parseExpression(src)!;
181
+ expect(conds.length).toBe(2);
182
+ const rebuilt = buildExpression(conds);
183
+ expect(rebuilt).toBe(src);
184
+ });
185
+ });
186
+
187
+ describe('变量工具', () => {
188
+ it('stripBraces 去除包裹', () => {
189
+ expect(stripBraces('{{ a === 1 }}')).toBe('a === 1');
190
+ expect(stripBraces('a === 1')).toBe('a === 1');
191
+ });
192
+
193
+ it('variableToString 各来源', () => {
194
+ expect(variableToString({ source: 'formData', path: 'user.name' })).toBe(
195
+ 'formData.user.name',
196
+ );
197
+ expect(variableToString({ source: 'deps', path: '2' })).toBe('$deps[2]');
198
+ expect(variableToString({ source: 'self', path: '' })).toBe('$self.value');
199
+ expect(variableToString({ source: 'index', path: '' })).toBe('$index');
200
+ expect(variableToString({ source: 'root', path: '' })).toBe('rootValue');
201
+ });
202
+
203
+ it('parseVariable 非标识符字段用括号访问', () => {
204
+ expect(parseVariable("formData['a-b']")).toEqual({
205
+ source: 'formData',
206
+ path: 'a-b',
207
+ });
208
+ expect(variableToString(parseVariable("formData['a-b']"))).toBe(
209
+ "formData['a-b']",
210
+ );
211
+ });
212
+ });
213
+
214
+ describe('collectDataFieldPaths', () => {
215
+ it('布局节点 Key 不进路径,对象 / 数组透传', () => {
216
+ const schema = {
217
+ type: 'object',
218
+ properties: {
219
+ card: {
220
+ type: 'card',
221
+ title: '卡片',
222
+ properties: {
223
+ name: { type: 'string', widget: 'input', title: '姓名' },
224
+ address: {
225
+ type: 'object',
226
+ properties: {
227
+ city: { type: 'string', widget: 'input' },
228
+ },
229
+ },
230
+ tags: {
231
+ type: 'array',
232
+ widget: 'list',
233
+ items: {
234
+ type: 'object',
235
+ properties: {
236
+ label: { type: 'string', widget: 'input' },
237
+ },
238
+ },
239
+ },
240
+ },
241
+ },
242
+ },
243
+ } as unknown as NexusSchema;
244
+
245
+ expect(collectDataFieldPaths(schema)).toEqual([
246
+ 'name',
247
+ 'address.city',
248
+ 'tags',
249
+ 'tags[0].label',
250
+ ]);
251
+ });
252
+
253
+ it('子表单(带 widget 的 object)视为叶子字段', () => {
254
+ const schema = {
255
+ type: 'object',
256
+ properties: {
257
+ sub: { type: 'object', widget: 'subForm', properties: {} },
258
+ },
259
+ } as unknown as NexusSchema;
260
+ expect(collectDataFieldPaths(schema)).toEqual(['sub']);
261
+ });
262
+ });