cdp-material-sdk 0.0.5 → 0.0.9
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/dist/hooks/useFieldRegistry.d.ts +36 -0
- package/dist/host-react.d.ts +2 -0
- package/dist/host-react.js +7 -6
- package/dist/index.js +14 -13
- package/dist/useFieldRegistry-CwjlA-ja.js +123 -0
- package/docs/component-development/README.md +10 -0
- package/docs/component-development/recipes//344/275/277/347/224/250Adapter/351/200/202/351/205/215/347/273/204/344/273/266API.md +121 -23
- package/docs/component-development/recipes//345/243/260/346/230/216props.md +313 -0
- package/docs/component-development/recipes//345/243/260/346/230/216/346/225/260/346/215/256/345/255/227/346/256/265/347/273/204/344/273/266.md +115 -12
- package/docs/component-development/recipes//345/243/260/346/230/216/346/225/260/346/215/256/345/256/271/345/231/250/347/273/204/344/273/266.md +10 -4
- package/docs/component-development/recipes//351/205/215/347/275/256Loading/347/255/226/347/225/245.md +96 -0
- package/docs/component-development/reference/Manifest/345/255/227/346/256/265/345/217/202/350/200/203.md +2 -1
- package/docs/component-development/reference/SDK/345/257/274/345/205/245/350/276/271/347/225/214.md +5 -4
- package/docs/component-development/reference/Traits/350/203/275/345/212/233/346/250/241/345/236/213.md +43 -6
- package/docs/component-development/reference//345/274/225/346/223/216/345/237/272/347/241/200/350/203/275/345/212/233/346/250/241/345/236/213.md +11 -9
- package/package.json +1 -1
- package/dist/DataScope-B7ffH5qN.js +0 -106
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: recipe
|
|
3
|
+
capability: props
|
|
4
|
+
related:
|
|
5
|
+
- reference/Manifest字段参考.md
|
|
6
|
+
- reference/Traits能力模型.md
|
|
7
|
+
- reference/validateManifest校验规则.md
|
|
8
|
+
- recipes/使用Adapter适配组件API.md
|
|
9
|
+
- recipes/声明数据字段组件.md
|
|
10
|
+
---
|
|
11
|
+
# 声明 props
|
|
12
|
+
|
|
13
|
+
`props` 是组件向**设计器、表达式、AI 工具**暴露的**可配置项契约**。它使用标准 JSON Schema——具体类型为 `ObjectSchema extends JSONSchema7`,要求 `type: 'object'`,`properties` 内每项是 `ExtendedJSONSchema7`(在标准 JSON Schema 之上加了少量 CDP 设计器扩展字段)。
|
|
14
|
+
|
|
15
|
+
manifest 的 `props` **不是 React 组件 props 接口的镜像**:前者是面向设计器的声明、用 schema 表达;后者是组件运行时接收的参数、用 TS 类型表达。两者会重叠,但语义和职责不同。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 适用场景
|
|
20
|
+
|
|
21
|
+
- 组件有用户/设计器**可配置**的输入项(占位符、列配置、模式枚举、阈值)。
|
|
22
|
+
- 组件需要让表达式或 AI 工具读取、推断、生成配置。
|
|
23
|
+
- 组件需要把第三方 prop 通过设计器暴露给业务用户。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 可以跳过的情况
|
|
28
|
+
|
|
29
|
+
- 组件是纯展示,没有可配置项 → 整个 `props` 字段省略即可。
|
|
30
|
+
- 字段已经由 trait 自动注入(如 `DATA_FIELD` 的 `value` / `readOnly` / `required` / `name` / `label` / `labelStrategy`)→ 不要重复声明。
|
|
31
|
+
- 字段是组件**内部运行时状态** → 走 `state`。
|
|
32
|
+
- 字段是**命令式调用** → 走 `actions`。
|
|
33
|
+
- 字段是**事件**(含回调) → 走 `events` / `customEvents`。
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 作者职责
|
|
38
|
+
|
|
39
|
+
1. 顶层结构是 `{ type: 'object', properties: { ... } }`,遵循 `ObjectSchema`。
|
|
40
|
+
2. 每个字段给出合适的 `type` 和 **`title`**(设计器作为字段标签使用,`validateManifest()` 缺失会 warning)。
|
|
41
|
+
3. 设计器需要选项的字段,用 `oneOf` 配 `{ const, title }`(更友好)或 `enum`(仅值列表)。
|
|
42
|
+
4. 嵌套配置用 `type: 'object' + properties` 或 `type: 'array' + items`。
|
|
43
|
+
5. 默认值写在 `default` 字段,**而不是**依赖 React 组件的参数默认值——后者设计器看不到。
|
|
44
|
+
6. **不要重复声明 trait 自动注入的 props**,除非确有特化需求并能在 PR / 注释中说明原因。
|
|
45
|
+
7. **不要把事件回调、内部状态、宿主注入的运行时数据写进 `props`**。
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 基础写法
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
props: {
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {
|
|
55
|
+
placeholder: { type: 'string', title: '占位提示' },
|
|
56
|
+
maxLength: { type: 'number', title: '最大长度', default: 100 },
|
|
57
|
+
disabled: { type: 'boolean', title: '禁用' },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## title / description / default
|
|
65
|
+
|
|
66
|
+
| 字段 | 用途 |
|
|
67
|
+
|------|------|
|
|
68
|
+
| `title` | 设计器中展示的字段标签;建议每个 prop 都写 |
|
|
69
|
+
| `description` | 鼠标提示 / AI 上下文;复杂或易误用的字段建议写 |
|
|
70
|
+
| `default` | 设计器为字段填入的默认值;写在这里而不是 React 组件的参数默认值上,设计器才能感知 |
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
search: {
|
|
74
|
+
type: 'array',
|
|
75
|
+
title: '搜索栏字段',
|
|
76
|
+
description: '在表格上方渲染搜索条件',
|
|
77
|
+
items: {
|
|
78
|
+
type: 'object',
|
|
79
|
+
properties: {
|
|
80
|
+
width: { type: 'number', title: '宽度(px)', default: 180 },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 枚举:oneOf / enum
|
|
89
|
+
|
|
90
|
+
需要给设计器下拉选项的字段,**用 `oneOf` 配 `{ const, title }`**——`const` 是值,`title` 是显示文本。比裸 `enum` 更友好,因为可以为每个值单独指定中文标签:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
align: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
title: '对齐',
|
|
96
|
+
oneOf: [
|
|
97
|
+
{ const: 'left', title: '左对齐' },
|
|
98
|
+
{ const: 'center', title: '居中' },
|
|
99
|
+
{ const: 'right', title: '右对齐' },
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
只有值列表、不需要差异化标签时,用 `enum`:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
size: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
title: '尺寸',
|
|
110
|
+
enum: ['small', 'medium', 'large'],
|
|
111
|
+
},
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 嵌套 — 对象与数组
|
|
117
|
+
|
|
118
|
+
**对象**:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
sort: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
title: '排序',
|
|
124
|
+
properties: {
|
|
125
|
+
enable: { type: 'boolean', title: '启用排序' },
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**数组(`items` 是对象)**:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
columns: {
|
|
134
|
+
type: 'array',
|
|
135
|
+
title: '列配置',
|
|
136
|
+
items: {
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: {
|
|
139
|
+
title: { type: 'string', title: '列标题' },
|
|
140
|
+
dataIndex: { type: 'string', title: '字段名', format: 'dataField' },
|
|
141
|
+
width: { type: 'number', title: '列宽' },
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
数组与对象可以多层嵌套(参见 Table 组件 `props.columns[*].filter.options[*]` 这类三层结构)。设计器会按嵌套结构折叠/展开。
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## format — 设计器控件提示
|
|
152
|
+
|
|
153
|
+
`format` 是 JSON Schema 标准字段,CDP 设计器消费它来选择更合适的控件。常见值:
|
|
154
|
+
|
|
155
|
+
| `format` | 设计器表现 |
|
|
156
|
+
|----------|------------|
|
|
157
|
+
| `'dataField'` | 字段选择器(可选当前作用域内的数据字段) |
|
|
158
|
+
| `'panelButton'` | 按钮选择器 |
|
|
159
|
+
| `'color'` | 颜色选择器 |
|
|
160
|
+
| `'icon'` | 图标选择器 |
|
|
161
|
+
| `'expression'` | 表达式编辑器 |
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
dataIndex: { type: 'string', title: '字段名', format: 'dataField' },
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
未列出的 `format` 由设计器/物料各自约定,CDP 不强制。
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 与 trait 自动注入 props 的边界
|
|
172
|
+
|
|
173
|
+
trait 会向 manifest 自动注入一组 props,作者**不要在 `props.properties` 里重复声明**——重复声明会**覆盖**引擎注入版本,丢失自动特化(典型是 `DATA_FIELD` 对 `value` 的 `valueSchema` 特化)。详见 [Traits 能力模型](../reference/Traits能力模型.md)。
|
|
174
|
+
|
|
175
|
+
| trait | 自动注入的 props | 写进 `props.properties`? |
|
|
176
|
+
|-------|------------------|--------------------------|
|
|
177
|
+
| `DATA_FIELD` | `value` / `readOnly` / `required` / `name` / `label` / `labelStrategy` | **否**(除非确有特化需求) |
|
|
178
|
+
|
|
179
|
+
`props.properties` 只写**业务专属**字段(如 `placeholder` / `maxLength` / 枚举选项 / 嵌套配置)。
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 与 adapter.propMapping 的协作
|
|
184
|
+
|
|
185
|
+
`props.properties` 的 key 是**面向设计器的 CDP 名**;如果 React 组件实际接收的 prop 名不同(如第三方组件使用 `selectedValue`),通过 `adapter.propMapping` 在引擎层映射,**`props.properties` 不需要跟着改名**:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
props: {
|
|
189
|
+
type: 'object',
|
|
190
|
+
properties: {
|
|
191
|
+
value: { type: 'string', title: '值' },
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
adapter: {
|
|
195
|
+
propMapping: {
|
|
196
|
+
value: 'selectedValue',
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
完整决策见 [使用 Adapter 适配组件 API · wrapper vs adapter 决策框架](./使用Adapter适配组件API.md#wrapper-vs-adapter-决策框架)。
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## ExtendedJSONSchema7 扩展字段
|
|
206
|
+
|
|
207
|
+
CDP 在标准 JSON Schema 之上加了少量 `x-` 字段(`ExtendedJSONSchema7`),用于设计器特定能力:
|
|
208
|
+
|
|
209
|
+
| 字段 | 用途 |
|
|
210
|
+
|------|------|
|
|
211
|
+
| `'x-dynamic-enum'` | 动态枚举来源配置(运行时拉取选项) |
|
|
212
|
+
| `'x-slot'` | 标记该字段对应一个插槽 |
|
|
213
|
+
| `'x-editableSelectOptions'` | 可编辑下拉选项 |
|
|
214
|
+
| `placeholder` | 设计器输入框的占位文本 |
|
|
215
|
+
| `allowedTabs` | 多 tab 设计器面板中允许出现该字段的 tab 列表 |
|
|
216
|
+
|
|
217
|
+
具体语义和触发条件由设计器维护,本文档不展开。
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 完整示例
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import {
|
|
225
|
+
COMPONENT_CATEGORY,
|
|
226
|
+
COMPONENT_TRAIT,
|
|
227
|
+
INJECT_PATH_SLOT_PROPS,
|
|
228
|
+
type ComponentManifest,
|
|
229
|
+
} from 'cdp-material-sdk/portable';
|
|
230
|
+
|
|
231
|
+
export const acmeInputManifest = {
|
|
232
|
+
type: 'acme.Input',
|
|
233
|
+
traits: [COMPONENT_TRAIT.DATA_FIELD],
|
|
234
|
+
meta: {
|
|
235
|
+
title: '输入框',
|
|
236
|
+
category: COMPONENT_CATEGORY.DATA_ENTRY,
|
|
237
|
+
valueSchema: { type: 'string', default: '' },
|
|
238
|
+
},
|
|
239
|
+
engine: {
|
|
240
|
+
render: { injection: { rootPath: INJECT_PATH_SLOT_PROPS } },
|
|
241
|
+
},
|
|
242
|
+
// value / readOnly / required / name / label / labelStrategy 由 DATA_FIELD trait 自动注入,不在此声明
|
|
243
|
+
props: {
|
|
244
|
+
type: 'object',
|
|
245
|
+
properties: {
|
|
246
|
+
placeholder: { type: 'string', title: '占位提示' },
|
|
247
|
+
maxLength: { type: 'number', title: '最大长度' },
|
|
248
|
+
align: {
|
|
249
|
+
type: 'string',
|
|
250
|
+
title: '对齐',
|
|
251
|
+
oneOf: [
|
|
252
|
+
{ const: 'left', title: '左对齐' },
|
|
253
|
+
{ const: 'center', title: '居中' },
|
|
254
|
+
{ const: 'right', title: '右对齐' },
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
} satisfies ComponentManifest;
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## 自检
|
|
265
|
+
|
|
266
|
+
- [ ] 顶层是 `{ type: 'object', properties: { ... } }`。
|
|
267
|
+
- [ ] 每个字段都有 `title`。
|
|
268
|
+
- [ ] 枚举类字段用 `oneOf` 配 `{ const, title }` 或 `enum`。
|
|
269
|
+
- [ ] 嵌套结构正确使用 `type: 'object' + properties` 或 `type: 'array' + items`。
|
|
270
|
+
- [ ] 默认值写 `default`,不依赖 React 组件参数默认值。
|
|
271
|
+
- [ ] **没有**重复声明 trait 自动注入的 props(除非确有特化需求并能说明)。
|
|
272
|
+
- [ ] **没有**把事件回调或运行时状态写进 `props`。
|
|
273
|
+
- [ ] `validateManifest()` 没有 error。
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## 常见错误
|
|
278
|
+
|
|
279
|
+
### 把所有 React props 都搬进 manifest
|
|
280
|
+
|
|
281
|
+
manifest `props` 是**对外可配置项契约**,不是 React props 接口的镜像。运行时回调(`onChange`)、内部状态、宿主注入的运行时数据都不该出现在这里。
|
|
282
|
+
|
|
283
|
+
### 重复声明 trait 自动注入的 props
|
|
284
|
+
|
|
285
|
+
`DATA_FIELD` 已自动注入 `value` / `readOnly` / `required` / `name` / `label` / `labelStrategy`。再写一遍会**覆盖**引擎版本,丢失 `valueSchema` 特化。仅在确有特化需求(收窄类型、补更具体的 `title` / `description`)时显式覆盖,并在 PR / 注释中说明原因。
|
|
286
|
+
|
|
287
|
+
### 默认值写在 React 组件而不是 `default`
|
|
288
|
+
|
|
289
|
+
React 组件参数默认值设计器看不到,物料面板也无法预填。把面向设计器的默认值写在 `props.properties.X.default`。
|
|
290
|
+
|
|
291
|
+
### 把 manifest props key 当成 React prop 名
|
|
292
|
+
|
|
293
|
+
`props.properties` 的 key 是**面向设计器的 CDP 名**。第三方组件实际接收的 prop 名不同时,用 `adapter.propMapping` 在引擎层映射,不要为了贴合第三方而改 manifest key——manifest key 才是表达式、AI、配置 UI 看到的稳定名字。
|
|
294
|
+
|
|
295
|
+
### 缺 `title`
|
|
296
|
+
|
|
297
|
+
`validateManifest()` 会 warning。设计器没有 `title` 时会 fallback 到 key 名,体验差且不利于 i18n。
|
|
298
|
+
|
|
299
|
+
### 用 `enum` 代替 `oneOf` 失去差异化标签
|
|
300
|
+
|
|
301
|
+
`enum` 只有值列表,设计器只能直接显示值字符串。需要每个选项有中文标签时改用 `oneOf` 配 `{ const, title }`。
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## 关联文档
|
|
306
|
+
|
|
307
|
+
本文档只保留本层级职责内容:Recipe 提供任务步骤,Reference 提供稳定模型和规则,validateManifest 文档提供校验级别事实源。
|
|
308
|
+
|
|
309
|
+
- 字段参考:[Manifest 字段参考](../reference/Manifest字段参考.md)
|
|
310
|
+
- 模型参考:[Traits 能力模型](../reference/Traits能力模型.md)
|
|
311
|
+
- 校验规则:[validateManifest 校验规则](../reference/validateManifest校验规则.md)
|
|
312
|
+
- 任务 Recipe:[使用 Adapter 适配组件 API](./使用Adapter适配组件API.md)
|
|
313
|
+
- 任务 Recipe:[声明数据字段组件](./声明数据字段组件.md)
|
|
@@ -5,7 +5,9 @@ related:
|
|
|
5
5
|
- reference/Traits能力模型.md
|
|
6
6
|
- reference/Manifest字段参考.md
|
|
7
7
|
- reference/validateManifest校验规则.md
|
|
8
|
+
- recipes/声明props.md
|
|
8
9
|
- recipes/使用Adapter适配组件API.md
|
|
10
|
+
- reference/Events模型.md
|
|
9
11
|
---
|
|
10
12
|
# 声明数据字段组件
|
|
11
13
|
|
|
@@ -33,9 +35,9 @@ related:
|
|
|
33
35
|
|
|
34
36
|
1. 在 `traits` 中声明 `COMPONENT_TRAIT.DATA_FIELD`。
|
|
35
37
|
2. 在 `meta.valueSchema` 中描述 value 类型。
|
|
36
|
-
3. 组件接收 `value`。
|
|
37
|
-
4.
|
|
38
|
-
5.
|
|
38
|
+
3. 组件接收 `value`,值变化时调用 `onChange(nextValue)`。
|
|
39
|
+
4. 如组件支持只读/必填/标签语义,分别接收并应用 `readOnly` / `required` / `label`;置于容器内时支持 `name`。
|
|
40
|
+
5. 一般不需要自己声明 `value` / `readOnly` / `required` / `name` / `label` / `labelStrategy` props,也不需要重复声明 `getValue` / `setValue` / `getReadOnly` / `setReadOnly` / `toggleReadOnly` / `getRequired` / `setRequired` / `toggleRequired` / `triggerValueChange` actions、`value` / `readOnly` / `required` state 或 `valueChange` event——它们由引擎在 `DATA_FIELD` trait 上自动注入并按 `valueSchema` 特化。重复声明会**覆盖**引擎版本,会丢失自动特化;**仅在确有特化需求**(如定制 setValue 取值规则)时才建议显式覆盖。完整字段规格见 [Traits能力模型 · DATA_FIELD](../reference/Traits能力模型.md#data_field)。
|
|
39
41
|
|
|
40
42
|
---
|
|
41
43
|
|
|
@@ -48,16 +50,25 @@ import type { BaseUIProps } from 'cdp-material-sdk/portable';
|
|
|
48
50
|
export interface AcmeInputProps extends BaseUIProps<HTMLInputElement> {
|
|
49
51
|
value?: string;
|
|
50
52
|
onChange?: (value: string) => void;
|
|
53
|
+
readOnly?: boolean;
|
|
54
|
+
required?: boolean;
|
|
55
|
+
name?: string;
|
|
56
|
+
label?: string;
|
|
57
|
+
placeholder?: string;
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
export const AcmeInput = forwardRef<HTMLInputElement, AcmeInputProps>((props, ref) => {
|
|
54
|
-
const { value = '', onChange, slotProps } = props;
|
|
61
|
+
const { value = '', onChange, readOnly, required, name, slotProps, placeholder } = props;
|
|
55
62
|
|
|
56
63
|
return (
|
|
57
64
|
<input
|
|
58
65
|
{...slotProps?.root}
|
|
59
66
|
ref={ref}
|
|
60
67
|
value={value}
|
|
68
|
+
readOnly={readOnly}
|
|
69
|
+
required={required}
|
|
70
|
+
name={name}
|
|
71
|
+
placeholder={placeholder}
|
|
61
72
|
onChange={(event) => onChange?.(event.target.value)}
|
|
62
73
|
/>
|
|
63
74
|
);
|
|
@@ -95,23 +106,106 @@ export const acmeInputManifest = {
|
|
|
95
106
|
placeholder: { type: 'string', title: '占位提示' },
|
|
96
107
|
},
|
|
97
108
|
},
|
|
109
|
+
// value / readOnly / required / name / label / labelStrategy props
|
|
110
|
+
// getValue / setValue / getReadOnly / setReadOnly / toggleReadOnly
|
|
111
|
+
// getRequired / setRequired / toggleRequired / triggerValueChange actions
|
|
112
|
+
// value / readOnly / required state 与 valueChange event
|
|
113
|
+
// 均由 DATA_FIELD trait 自动注入,默认不需重复声明;如需特化可显式覆盖
|
|
114
|
+
} satisfies ComponentManifest;
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 适配第三方组件的 props / events
|
|
120
|
+
|
|
121
|
+
`DATA_FIELD` trait 默认按 CDP 约定注入:
|
|
122
|
+
|
|
123
|
+
- `value` prop + `onChange(nextValue)` 回写。
|
|
124
|
+
- `readOnly` / `required` / `name` / `label` props。
|
|
125
|
+
- `valueChange` 事件(由 `onChange` / `setValue` 自动触发)。
|
|
126
|
+
|
|
127
|
+
接入第三方组件库时,prop / 事件名 / payload 形状常常不一致(`selectedValue` / `onSelectedValueChange`、`disabled` 替 `readOnly`、`(event, value) => void` 而非 `(value) => void`)。**按层选工具**——事件层与 prop 改名首选 adapter,值变换 / 默认值 / 受控转换 / 结构层走 wrapper。完整决策见 [使用 Adapter 适配组件 API · wrapper vs adapter 决策框架](./使用Adapter适配组件API.md#wrapper-vs-adapter-决策框架)。
|
|
128
|
+
|
|
129
|
+
### 事件层 — 用 adapter(推荐)
|
|
130
|
+
|
|
131
|
+
`valueChange` 由 `DATA_FIELD` trait 自动声明,**不需要重复声明**,直接在 `adapter.events` 中映射 propName 与 payload:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
adapter: {
|
|
98
135
|
events: {
|
|
99
|
-
valueChange
|
|
136
|
+
// 第三方回调签名 (event, value) => void,CDP valueChange payload 是 { value }
|
|
137
|
+
valueChange: {
|
|
138
|
+
propName: 'onSelectedValueChange',
|
|
139
|
+
transform: (event, value) => ({ value }),
|
|
140
|
+
},
|
|
100
141
|
},
|
|
101
|
-
}
|
|
142
|
+
},
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`transform` 的返回类型受 `EngineEventProtocol['valueChange']` 约束,类型化对齐 CDP 协议;不需要在 wrapper 里再构造一份 payload。
|
|
146
|
+
|
|
147
|
+
### Props 层 — 改名用 adapter,值变换用 wrapper
|
|
148
|
+
|
|
149
|
+
仅改名(`value` → `selectedValue`、`readOnly` → `disabled`):
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
adapter: {
|
|
153
|
+
propMapping: {
|
|
154
|
+
value: 'selectedValue',
|
|
155
|
+
readOnly: 'disabled',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
需要值变换 / 默认值 / 受控-非受控调谐时,回到 wrapper:
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
import { forwardRef } from 'react';
|
|
164
|
+
import { ThirdPartySelect } from 'some-ui-lib';
|
|
165
|
+
import type { BaseUIProps } from 'cdp-material-sdk/portable';
|
|
166
|
+
|
|
167
|
+
export interface AcmeSelectProps extends BaseUIProps<HTMLDivElement> {
|
|
168
|
+
value?: string;
|
|
169
|
+
onChange?: (value: string) => void;
|
|
170
|
+
readOnly?: boolean;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export const AcmeSelect = forwardRef<HTMLDivElement, AcmeSelectProps>((props, ref) => {
|
|
174
|
+
const { value = '', onChange, readOnly, slotProps } = props;
|
|
175
|
+
return (
|
|
176
|
+
<ThirdPartySelect
|
|
177
|
+
{...slotProps?.root}
|
|
178
|
+
ref={ref}
|
|
179
|
+
// 第三方组件不接受 undefined value,wrapper 补默认值
|
|
180
|
+
selectedValue={value}
|
|
181
|
+
onSelectedValueChange={onChange}
|
|
182
|
+
disabled={readOnly}
|
|
183
|
+
/>
|
|
184
|
+
);
|
|
185
|
+
});
|
|
102
186
|
```
|
|
103
187
|
|
|
188
|
+
### 混合策略
|
|
189
|
+
|
|
190
|
+
事件层用 adapter、props 值层用 wrapper 是常见组合。**同一适配点不要在两层都做**——`adapter.events.transform` 与 wrapper 内 `(value) => onChange(reshape(value))` 二选一,否则会叠加或冲突。
|
|
191
|
+
|
|
192
|
+
完整规则、自检和常见错误见 [使用 Adapter 适配组件 API](./使用Adapter适配组件API.md)。
|
|
193
|
+
|
|
104
194
|
---
|
|
105
195
|
|
|
106
196
|
## 声明后会得到什么
|
|
107
197
|
|
|
198
|
+
声明 `DATA_FIELD` 后,引擎会自动注入下列能力(完整字段列表见 [Traits能力模型 · DATA_FIELD](../reference/Traits能力模型.md#data_field)):
|
|
199
|
+
|
|
108
200
|
| 能力 | 说明 |
|
|
109
201
|
|------|------|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
202
|
+
| 值注入与回写 | 宿主注入 `value`、`readOnly`、`required`、`name`、`label`、`labelStrategy`;`onChange` 回写值 |
|
|
203
|
+
| 标准 actions | `getValue` / `setValue` / `getReadOnly` / `setReadOnly` / `toggleReadOnly` / `getRequired` / `setRequired` / `toggleRequired` / `triggerValueChange` |
|
|
204
|
+
| 标准 state | `value`(schema 由 `valueSchema` 透传)、`readOnly`、`required` |
|
|
205
|
+
| 标准 event | `valueChange` |
|
|
206
|
+
| valueSchema 特化 | `setValue.params.value`、`setValue.returns.{newValue,oldValue}`、`getValue.returns`、`state.value.schema` 自动按 `meta.valueSchema` 特化 |
|
|
112
207
|
| 类型识别 | 设计器、表达式和 AI 工具可读取 `meta.valueSchema` |
|
|
113
|
-
| 容器协同 |
|
|
114
|
-
| 自动状态补充 | 宿主可基于数据能力补充标准 value 状态 |
|
|
208
|
+
| 容器协同 | 在数据容器内,字段按 `name` 写入容器 value |
|
|
115
209
|
|
|
116
210
|
---
|
|
117
211
|
|
|
@@ -119,8 +213,9 @@ export const acmeInputManifest = {
|
|
|
119
213
|
|
|
120
214
|
- [ ] `traits` 包含 `COMPONENT_TRAIT.DATA_FIELD`。
|
|
121
215
|
- [ ] `meta.valueSchema` 描述了 value 类型。
|
|
122
|
-
- [ ] 组件接收 `value`。
|
|
123
|
-
- [ ]
|
|
216
|
+
- [ ] 组件接收 `value`,值变化时调用 `onChange(nextValue)`。
|
|
217
|
+
- [ ] 组件按需接收并应用 `readOnly` / `required` / `name` / `label` / `labelStrategy`(不必全部支持,但接收的 prop 必须真正生效)。
|
|
218
|
+
- [ ] manifest 中没有不必要地重复声明 DATA_FIELD 自动注入的 props / actions / state / events;确有覆盖的,能明确说出特化原因。
|
|
124
219
|
- [ ] `validateManifest()` 没有 error。
|
|
125
220
|
|
|
126
221
|
---
|
|
@@ -135,6 +230,14 @@ export const acmeInputManifest = {
|
|
|
135
230
|
|
|
136
231
|
如果第三方组件使用 `selectedValue`、`onSelectedValueChange` 等命名,需要在 wrapper 内转换,或阅读 [使用 Adapter 适配组件 API](./使用Adapter适配组件API.md)。
|
|
137
232
|
|
|
233
|
+
### 不必要地重复声明自动注入字段
|
|
234
|
+
|
|
235
|
+
在自己的 manifest 里再写一遍 `value` prop、`getValue` / `setValue` action、`valueChange` event 等,会**覆盖**引擎注入的版本,并**丢失 `valueSchema` 自动特化**。默认只写**业务专属**的 props(如 `placeholder`)和事件;仅在**确有特化需求**时才显式覆盖自动注入的字段,并在代码/PR 中说明原因。
|
|
236
|
+
|
|
237
|
+
### 接收了 `readOnly` / `required` 却没有真正生效
|
|
238
|
+
|
|
239
|
+
引擎会注入 `readOnly` / `required` 并在 state 中持续更新;如果组件渲染时忽略这两个 prop,事件编排里 `setReadOnly` / `toggleRequired` 等 action 看起来生效(state 变了)但 UI 不变,会让使用者困惑。要么接收并应用,要么不接收(说明组件不支持该语义)。
|
|
240
|
+
|
|
138
241
|
---
|
|
139
242
|
|
|
140
243
|
## 关联文档
|
|
@@ -35,6 +35,7 @@ related:
|
|
|
35
35
|
2. 在 `meta.valueSchema` 中描述容器 value 结构。
|
|
36
36
|
3. 在子组件渲染区域用 `DataScope` 建立数据作用域,由它负责字段路径解析与记录注入。
|
|
37
37
|
4. 如需读写容器数据,使用 `cdp-material-sdk/host-react` 中的 `useDataContainer` / `useDataContainerApi`。
|
|
38
|
+
5. 如需聚合子字段状态(hidden / readOnly / required / label),使用 `cdp-material-sdk/host-react` 的 `useFieldRegistry`,把它返回的 `registerField` / `unregisterField` 透传给 `DataScope`。
|
|
38
39
|
|
|
39
40
|
`DataScope` 由 SDK 统一提供,作者不需要传 `componentId`,引擎会自动完成运行时上下文装配。
|
|
40
41
|
|
|
@@ -97,8 +98,11 @@ export const acmeFormManifest = {
|
|
|
97
98
|
|
|
98
99
|
```tsx
|
|
99
100
|
import { useCallback } from 'react';
|
|
100
|
-
import {
|
|
101
|
-
|
|
101
|
+
import {
|
|
102
|
+
DataScope,
|
|
103
|
+
useDataContainerApi,
|
|
104
|
+
useFieldRegistry,
|
|
105
|
+
} from 'cdp-material-sdk/host-react';
|
|
102
106
|
|
|
103
107
|
export function AcmeForm({ children }) {
|
|
104
108
|
const { getContainerData } = useDataContainerApi<Record<string, unknown>>();
|
|
@@ -109,7 +113,8 @@ export function AcmeForm({ children }) {
|
|
|
109
113
|
[getContainerData],
|
|
110
114
|
);
|
|
111
115
|
|
|
112
|
-
// useFieldRegistry 内部用 ref 维护字段表,register/unregister
|
|
116
|
+
// useFieldRegistry 内部用 ref 维护字段表,register/unregister 引用稳定,
|
|
117
|
+
// 还可通过 getAllFieldStates() 拿到当前所有字段状态用于整体校验。
|
|
113
118
|
const { registerField, unregisterField } = useFieldRegistry();
|
|
114
119
|
|
|
115
120
|
return (
|
|
@@ -127,7 +132,8 @@ export function AcmeForm({ children }) {
|
|
|
127
132
|
要点:
|
|
128
133
|
|
|
129
134
|
- `getRecord` 推荐用 `useCallback` 包,避免每次 render 给 `DataScope` 传新函数引用。
|
|
130
|
-
- `registerField` / `unregisterField`
|
|
135
|
+
- `registerField` / `unregisterField` 需保证引用稳定,避免子字段重复注册——直接用 SDK 的 `useFieldRegistry()` 即可,它内部用 ref + `useCallback` 已经保证稳定。
|
|
136
|
+
- 需要在容器内做整体校验(如 Form 提交前检查所有必填字段)时,调 `getAllFieldStates()` 拿到字段状态快照即可。
|
|
131
137
|
- 不需要也不要传 `componentId`。
|
|
132
138
|
|
|
133
139
|
### 示例:Table 这类数组型容器
|
|
@@ -147,6 +147,102 @@ actions: {
|
|
|
147
147
|
|
|
148
148
|
---
|
|
149
149
|
|
|
150
|
+
## 自实现 loading 的 hook
|
|
151
|
+
|
|
152
|
+
`none` 策略下组件得自己维护 loading state。SDK 在 `cdp-material-sdk/portable` 提供两个**纯 React、无宿主耦合**的便利 hook,避免每家组件重复实现引用计数与 dual-loading 合并逻辑。
|
|
153
|
+
|
|
154
|
+
### useConcurrentLoading:并发动作 loading
|
|
155
|
+
|
|
156
|
+
适合按钮上同时挂多个异步动作、或同一组件可能并发触发多次请求的场景。返回的 `startLoading` / `stopLoading` 是**引用计数式**的——后到的 stop 不会误清先到的 start。
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
import { useConcurrentLoading } from 'cdp-material-sdk/portable';
|
|
160
|
+
|
|
161
|
+
export function AcmePanel() {
|
|
162
|
+
const { isLoading, loadingText, startLoading, stopLoading } = useConcurrentLoading();
|
|
163
|
+
|
|
164
|
+
const handleSave = async () => {
|
|
165
|
+
startLoading('保存中…');
|
|
166
|
+
try {
|
|
167
|
+
await saveToServer();
|
|
168
|
+
} finally {
|
|
169
|
+
stopLoading();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<button disabled={isLoading} onClick={handleSave}>
|
|
175
|
+
{isLoading ? loadingText ?? '处理中…' : '保存'}
|
|
176
|
+
</button>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
返回值要点:
|
|
182
|
+
|
|
183
|
+
- `isLoading` 是触发重渲染的状态值,可直接驱动 UI。
|
|
184
|
+
- `getLoading()` 是 ref 读取,适合放进 `useCallback`、事件处理器中按需读取,无需进依赖列表。
|
|
185
|
+
- `loadingText` 由最近一次 `startLoading(text)` 设置;不传 `text` 则保持上次值。
|
|
186
|
+
|
|
187
|
+
### useDualLoading:动作 loading 与数据 loading 分离
|
|
188
|
+
|
|
189
|
+
适合 Table、查询面板这类**同时存在「按钮触发的动作」与「后台数据请求」**的组件。两类 loading 单独建模,但对外仍暴露统一的 `isLoading`。
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import { useEffect } from 'react';
|
|
193
|
+
import { useDualLoading } from 'cdp-material-sdk/portable';
|
|
194
|
+
|
|
195
|
+
export function AcmeTable({ query }) {
|
|
196
|
+
const {
|
|
197
|
+
actionLoading, // 动作触发的 loading(引用计数)
|
|
198
|
+
startActionLoading,
|
|
199
|
+
stopActionLoading,
|
|
200
|
+
dataLoading, // 数据请求 loading(布尔)
|
|
201
|
+
setDataLoading,
|
|
202
|
+
isLoading, // 二者的 OR
|
|
203
|
+
isLoadingRef, // 引用稳定的最新值
|
|
204
|
+
} = useDualLoading();
|
|
205
|
+
|
|
206
|
+
// 数据请求:标记 dataLoading
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
let cancelled = false;
|
|
209
|
+
setDataLoading(true);
|
|
210
|
+
fetchRows(query).finally(() => {
|
|
211
|
+
if (!cancelled) setDataLoading(false);
|
|
212
|
+
});
|
|
213
|
+
return () => { cancelled = true; };
|
|
214
|
+
}, [query, setDataLoading]);
|
|
215
|
+
|
|
216
|
+
// 行内动作:标记 actionLoading
|
|
217
|
+
const handleRowDelete = async (id: string) => {
|
|
218
|
+
startActionLoading('删除中…');
|
|
219
|
+
try {
|
|
220
|
+
await deleteRow(id);
|
|
221
|
+
} finally {
|
|
222
|
+
stopActionLoading();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return <TableUi loading={isLoading} onDelete={handleRowDelete} />;
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
要点:
|
|
231
|
+
|
|
232
|
+
- 用 `dataLoading` 表达「后台拉数据」,用 `actionLoading` 表达「用户动作触发的并发任务」,二者互不污染。
|
|
233
|
+
- 对外通过 `isLoading` 对接 UI 即可,不必在外层组合两个布尔。
|
|
234
|
+
- `isLoadingRef.current` 提供引用稳定的最新值,方便在事件回调里判断「当前是否仍处于 loading」而不必把状态加进依赖列表。
|
|
235
|
+
|
|
236
|
+
### 与 manifest loading 策略的搭配
|
|
237
|
+
|
|
238
|
+
| 场景 | manifest 策略 | hook |
|
|
239
|
+
|------|---------------|------|
|
|
240
|
+
| 完全交给宿主 | `native` 或 `wrapper` | 不需要 |
|
|
241
|
+
| 宿主只接管视觉、组件内部仍想细分 loading | `wrapper` + 这两个 hook | `useConcurrentLoading` 或 `useDualLoading`,组件内部用 hook 状态驱动局部 UI,宿主负责整体遮罩 |
|
|
242
|
+
| 完全自管 | `none` + 自定义 `setLoading` / `getLoading` actions | 推荐 `useConcurrentLoading`;用 hook 的 `getLoading` 实现 action 的 `getLoading`,用 `startLoading` / `stopLoading` 实现 action 的 `setLoading` |
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
150
246
|
## 选择表
|
|
151
247
|
|
|
152
248
|
| 组件情况 | 推荐策略 |
|
|
@@ -22,7 +22,7 @@ related:
|
|
|
22
22
|
| `alias` | `string[]` | 否 | 历史别名或兼容名 |
|
|
23
23
|
| `meta` | `object` | 是 | 基本信息 |
|
|
24
24
|
| `traits` | `ComponentTrait[]` | 否 | 能力声明 |
|
|
25
|
-
| `props` | `ObjectSchema` | 否 | 可配置 props 的 JSON Schema |
|
|
25
|
+
| `props` | `ObjectSchema` | 否 | 可配置 props 的 JSON Schema,详见 [声明 props](../recipes/声明props.md) |
|
|
26
26
|
| `events` | `EventSpec[]` | 否 | 标准事件声明 |
|
|
27
27
|
| `customEvents` | `Record<string, CustomEventSpec>` | 否 | 自定义事件声明 |
|
|
28
28
|
| `actions` | `Record<string, ActionSpec>` | 否 | 组件动作声明 |
|
|
@@ -245,6 +245,7 @@ export const manifest = {
|
|
|
245
245
|
本文档只保留本层级职责内容:Recipe 提供任务步骤,Reference 提供稳定模型和规则,validateManifest 文档提供校验级别事实源。
|
|
246
246
|
|
|
247
247
|
- 关联文档:[03-开发最小可运行组件](../getting-started/03-开发最小可运行组件.md)
|
|
248
|
+
- 任务 Recipe:[声明 props](../recipes/声明props.md)
|
|
248
249
|
- 任务 Recipe:[声明布局容器组件](../recipes/声明布局容器组件.md)
|
|
249
250
|
- 任务 Recipe:[配置设计器元信息](../recipes/配置设计器元信息.md)
|
|
250
251
|
- 校验规则:[validateManifest校验规则](./validateManifest校验规则.md)
|
package/docs/component-development/reference/SDK/345/257/274/345/205/245/350/276/271/347/225/214.md
CHANGED
|
@@ -31,6 +31,7 @@ related:
|
|
|
31
31
|
- `validateManifest()`
|
|
32
32
|
- `diagnoseMissingActionImpls()`
|
|
33
33
|
- `diagnoseMissingStateKeys()`
|
|
34
|
+
- `useConcurrentLoading` / `useDualLoading`(组件自实现 loading 时的开箱即用 hook)
|
|
34
35
|
|
|
35
36
|
示例:
|
|
36
37
|
|
|
@@ -53,10 +54,10 @@ import {
|
|
|
53
54
|
|
|
54
55
|
可用于:
|
|
55
56
|
|
|
56
|
-
- `DataScope`
|
|
57
|
-
- `useDataContainer`
|
|
58
|
-
- `
|
|
59
|
-
- `PageContext`
|
|
57
|
+
- `DataScope` / `DataScopeContext` / 类型 `FieldState` / `LazyScope` / `PathResolver`
|
|
58
|
+
- `useDataContainer` / `useDataContainerApi`
|
|
59
|
+
- `useFieldRegistry`(与 `DataScope` 配套,给数据容器组件聚合子字段状态)
|
|
60
|
+
- `PageContext` / `DataContainerRuntimeContext`
|
|
60
61
|
|
|
61
62
|
如果不能确认共享 runtime,请不要使用该入口。
|
|
62
63
|
|