cdp-material-sdk 0.0.8 → 0.0.10

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 (19) hide show
  1. package/dist/hooks/useFieldRegistry.d.ts +36 -0
  2. package/dist/host-react.d.ts +2 -0
  3. package/dist/host-react.js +7 -6
  4. package/dist/index.js +14 -13
  5. package/dist/useFieldRegistry-CwjlA-ja.js +123 -0
  6. package/docs/component-development/README.md +10 -0
  7. 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
  8. package/docs/component-development/recipes//345/243/260/346/230/216props.md +313 -0
  9. package/docs/component-development/recipes//345/243/260/346/230/216/345/270/203/345/261/200/345/256/271/345/231/250/347/273/204/344/273/266.md +38 -14
  10. package/docs/component-development/recipes//345/243/260/346/230/216/346/217/222/346/247/275.md +4 -2
  11. 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 +122 -12
  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
  13. package/docs/component-development/recipes//351/205/215/347/275/256Loading/347/255/226/347/225/245.md +96 -0
  14. package/docs/component-development/reference/Manifest/345/255/227/346/256/265/345/217/202/350/200/203.md +2 -1
  15. package/docs/component-development/reference/SDK/345/257/274/345/205/245/350/276/271/347/225/214.md +5 -4
  16. package/docs/component-development/reference/Traits/350/203/275/345/212/233/346/250/241/345/236/213.md +59 -14
  17. 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
  18. package/package.json +1 -1
  19. 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)
@@ -10,38 +10,62 @@ related:
10
10
  ---
11
11
  # 声明布局容器组件
12
12
 
13
- 布局容器组件用于承载子组件,例如 Card、Grid、Tabs、Collapse、Section 等。
13
+ `LAYOUT_CONTAINER` trait 用于声明组件具备一个**默认 children 区域**——设计器中拖入的子节点会被宿主递归渲染并作为 React `children` 传入组件。
14
14
 
15
- `LAYOUT_CONTAINER` 与 `DATA_CONTAINER` **不互斥**:只要组件同时承载子组件、又管理子字段数据作用域,就应同时声明两个 trait。典型代表是 Form:既是布局容器(拖入字段子组件),又是数据容器(管理表单值)。
15
+ `LAYOUT_CONTAINER` 与 `DATA_CONTAINER` **不互斥**:Form 这类组件既需要默认 children 区域承载字段子组件、又需要管理表单值,应同时声明两个 trait
16
+
17
+ ---
18
+
19
+ ## LAYOUT_CONTAINER 与 slots 是两条独立路径
20
+
21
+ 宿主在运行时**独立判断**这两个开关,互不依赖:
22
+
23
+ | 接子节点的方式 | 是否需要 LAYOUT_CONTAINER | 是否需要 `manifest.slots` 声明 |
24
+ |---|:---:|:---:|
25
+ | 默认 children 区域(用户在画布拖入子节点) | ✅ | ❌ |
26
+ | 命名 / 作用域 / 动态插槽(用户填充具名区域) | ❌ | ✅ |
27
+ | 两者并存(既有匿名主区又有 header / footer 等具名区) | ✅ | ✅ |
28
+ | 两者都没有(按钮、输入框) | ❌ | ❌ |
29
+
30
+ 只通过具名插槽接子内容的组件**不需要** `LAYOUT_CONTAINER` trait,直接走 [声明插槽](./声明插槽.md)。
16
31
 
17
32
  ---
18
33
 
19
34
  ## 适用场景
20
35
 
21
- - 组件提供一个可放置子组件的区域。
22
- - 组件可能限制子组件的类型、最少/最多数量。
23
- - 不论组件是否还参与数据作用域。只要可以拖入子组件,就应声明 `LAYOUT_CONTAINER`。
36
+ - 组件需要一个默认的、匿名的子组件区域,由用户在设计器拖入子节点。
37
+ - 组件需要按 `nesting` 约束限制子组件类型、最少/最多数量。
24
38
 
25
39
  ---
26
40
 
27
41
  ## 可以跳过的情况
28
42
 
29
43
  - 组件只是单纯的展示组件,没有子内容。
30
- - 子内容只以命名/作用域/动态区域出现,不需要默认 children 区域,应改用 [声明插槽](./声明插槽.md)。
44
+ - 子内容只以命名 / 作用域 / 动态插槽出现,没有默认 children 区域 → 走 [声明插槽](./声明插槽.md)。
31
45
  - 组件是数据容器但不接受子组件(例如只接 props 的表格),只需要 [声明数据容器组件](./声明数据容器组件.md)。
32
46
 
33
47
  ---
34
48
 
35
- ## 选择 children、slots 还是 nesting
49
+ ## 选择 children + nesting 还是 slots
50
+
51
+ | 子节点形态 | 推荐方案 | 例子 |
52
+ |---|---|---|
53
+ | 通用匿名子区域 | `LAYOUT_CONTAINER`(无 `nesting`) | Card、Section、Grid |
54
+ | 单一/受限类型的重复子节点(强组合关系) | `LAYOUT_CONTAINER` + `nesting.allowedChildren` / `allowedParents` | Tabs/TabPane、Steps/Step、Form/FormItem、Collapse/Panel |
55
+ | 多个不同语义的具名区域 | `slots`(**不需要** LAYOUT_CONTAINER) | Modal 的 footer、Card 的 header/extra |
56
+ | 数据驱动的多个子区域 | 动态 / 作用域 `slots` | Table 列模板、List 行模板 |
57
+ | 既有匿名主区又有具名扩展区 | `LAYOUT_CONTAINER` + `slots` | Section + header/footer |
58
+
59
+ ### DSL / 低代码场景的归类原则
60
+
61
+ **单一类型重复的子节点关系优先用 children + nesting,不用 slots**:
62
+
63
+ - **children + nesting**:子节点是**真正的 DSL 节点**,在节点树中可独立选中、配置、命名、移动、复制粘贴。
64
+ - **slots**:slot 内容是 slot 项的填充物,节点树中会被嵌套在 slot 名下,结构表达更"封闭"。
36
65
 
37
- | 场景 | 推荐方案 |
38
- |------|----------|
39
- | 一个匿名的子组件区域 | `children` + `LAYOUT_CONTAINER` |
40
- | 多个具名区域(header / footer / actions) | `slots` |
41
- | 子区域带运行时上下文(行数据、列定义) | 作用域 `slots` |
42
- | 同时限制可放入的组件类型或数量 | 加 `nesting` |
66
+ Tabs/TabPane、Steps/Step、Form/FormItem、Collapse/Panel 这类**强组合关系**应走 children + nesting,用 `nesting.allowedChildren` / `allowedParents` 锁定可拖入的子类型与父类型。
43
67
 
44
- `children` 模式与 `slots` 模式可以共存:组件可以同时有默认 `children` 区域和若干命名插槽。
68
+ slots 更适合"多个不同语义的装饰区/扩展区"和"数据驱动的模板区域"。
45
69
 
46
70
  ---
47
71
 
@@ -126,9 +126,11 @@ slots: {
126
126
 
127
127
  宿主无法生成稳定 slot 名称,`validateManifest()` 会报错。
128
128
 
129
- ### 普通容器只声明 LAYOUT_CONTAINER,不声明 slots
129
+ ### 把多个固定区域都塞进默认 children
130
130
 
131
- 如果组件有明确可填充区域,建议声明 slots。`LAYOUT_CONTAINER` 只说明组件具备布局容器能力,slots 才说明具体区域。
131
+ `LAYOUT_CONTAINER` 只控制**默认 children 区域**的开关,与 `slots` 是两条独立机制。如果组件有 header / footer / extra 等多个固定区域,应声明 `slots` 让宿主分别注入到 `_slots[name]`,不要把它们都挤进 `children`,否则设计器和 AI 工具会失去结构信息。
132
+
133
+ 只用 slots 接子内容的组件**不需要**声明 `LAYOUT_CONTAINER` trait。
132
134
 
133
135
  ---
134
136
 
@@ -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,10 @@ related:
33
35
 
34
36
  1. 在 `traits` 中声明 `COMPONENT_TRAIT.DATA_FIELD`。
35
37
  2. 在 `meta.valueSchema` 中描述 value 类型。
36
- 3. 组件接收 `value`。
37
- 4. 值变化时调用 `onChange(nextValue)`。
38
- 5. 如果组件在容器内使用,建议支持 `name` prop。
38
+ 3. 组件接收 `value`,值变化时调用 `onChange(nextValue)`。
39
+ 4. 如组件支持只读/必填/标签语义,分别接收并应用 `readOnly` / `required` / `label`;置于容器内时支持 `name`。
40
+ 5. 如组件支持 `placeholder`,**仅在可编辑态显示**;`readOnly` / `disabled` 等非编辑态默认隐藏占位提示,避免与已锁定的真实值混淆。
41
+ 6. 一般不需要自己声明 `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
42
 
40
43
  ---
41
44
 
@@ -48,16 +51,26 @@ import type { BaseUIProps } from 'cdp-material-sdk/portable';
48
51
  export interface AcmeInputProps extends BaseUIProps<HTMLInputElement> {
49
52
  value?: string;
50
53
  onChange?: (value: string) => void;
54
+ readOnly?: boolean;
55
+ required?: boolean;
56
+ name?: string;
57
+ label?: string;
58
+ placeholder?: string;
51
59
  }
52
60
 
53
61
  export const AcmeInput = forwardRef<HTMLInputElement, AcmeInputProps>((props, ref) => {
54
- const { value = '', onChange, slotProps } = props;
62
+ const { value = '', onChange, readOnly, required, name, slotProps, placeholder } = props;
55
63
 
56
64
  return (
57
65
  <input
58
66
  {...slotProps?.root}
59
67
  ref={ref}
60
68
  value={value}
69
+ readOnly={readOnly}
70
+ required={required}
71
+ name={name}
72
+ // placeholder 仅在可编辑态显示;只读态隐藏
73
+ placeholder={readOnly ? undefined : placeholder}
61
74
  onChange={(event) => onChange?.(event.target.value)}
62
75
  />
63
76
  );
@@ -95,23 +108,106 @@ export const acmeInputManifest = {
95
108
  placeholder: { type: 'string', title: '占位提示' },
96
109
  },
97
110
  },
111
+ // value / readOnly / required / name / label / labelStrategy props
112
+ // getValue / setValue / getReadOnly / setReadOnly / toggleReadOnly
113
+ // getRequired / setRequired / toggleRequired / triggerValueChange actions
114
+ // value / readOnly / required state 与 valueChange event
115
+ // 均由 DATA_FIELD trait 自动注入,默认不需重复声明;如需特化可显式覆盖
116
+ } satisfies ComponentManifest;
117
+ ```
118
+
119
+ ---
120
+
121
+ ## 适配第三方组件的 props / events
122
+
123
+ `DATA_FIELD` trait 默认按 CDP 约定注入:
124
+
125
+ - `value` prop + `onChange(nextValue)` 回写。
126
+ - `readOnly` / `required` / `name` / `label` props。
127
+ - `valueChange` 事件(由 `onChange` / `setValue` 自动触发)。
128
+
129
+ 接入第三方组件库时,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-决策框架)。
130
+
131
+ ### 事件层 — 用 adapter(推荐)
132
+
133
+ `valueChange` 由 `DATA_FIELD` trait 自动声明,**不需要重复声明**,直接在 `adapter.events` 中映射 propName 与 payload:
134
+
135
+ ```ts
136
+ adapter: {
98
137
  events: {
99
- valueChange: { title: '值变化' },
138
+ // 第三方回调签名 (event, value) => void,CDP valueChange payload 是 { value }
139
+ valueChange: {
140
+ propName: 'onSelectedValueChange',
141
+ transform: (event, value) => ({ value }),
142
+ },
100
143
  },
101
- } satisfies ComponentManifest;
144
+ },
145
+ ```
146
+
147
+ `transform` 的返回类型受 `EngineEventProtocol['valueChange']` 约束,类型化对齐 CDP 协议;不需要在 wrapper 里再构造一份 payload。
148
+
149
+ ### Props 层 — 改名用 adapter,值变换用 wrapper
150
+
151
+ 仅改名(`value` → `selectedValue`、`readOnly` → `disabled`):
152
+
153
+ ```ts
154
+ adapter: {
155
+ propMapping: {
156
+ value: 'selectedValue',
157
+ readOnly: 'disabled',
158
+ },
159
+ },
160
+ ```
161
+
162
+ 需要值变换 / 默认值 / 受控-非受控调谐时,回到 wrapper:
163
+
164
+ ```tsx
165
+ import { forwardRef } from 'react';
166
+ import { ThirdPartySelect } from 'some-ui-lib';
167
+ import type { BaseUIProps } from 'cdp-material-sdk/portable';
168
+
169
+ export interface AcmeSelectProps extends BaseUIProps<HTMLDivElement> {
170
+ value?: string;
171
+ onChange?: (value: string) => void;
172
+ readOnly?: boolean;
173
+ }
174
+
175
+ export const AcmeSelect = forwardRef<HTMLDivElement, AcmeSelectProps>((props, ref) => {
176
+ const { value = '', onChange, readOnly, slotProps } = props;
177
+ return (
178
+ <ThirdPartySelect
179
+ {...slotProps?.root}
180
+ ref={ref}
181
+ // 第三方组件不接受 undefined value,wrapper 补默认值
182
+ selectedValue={value}
183
+ onSelectedValueChange={onChange}
184
+ disabled={readOnly}
185
+ />
186
+ );
187
+ });
102
188
  ```
103
189
 
190
+ ### 混合策略
191
+
192
+ 事件层用 adapter、props 值层用 wrapper 是常见组合。**同一适配点不要在两层都做**——`adapter.events.transform` 与 wrapper 内 `(value) => onChange(reshape(value))` 二选一,否则会叠加或冲突。
193
+
194
+ 完整规则、自检和常见错误见 [使用 Adapter 适配组件 API](./使用Adapter适配组件API.md)。
195
+
104
196
  ---
105
197
 
106
198
  ## 声明后会得到什么
107
199
 
200
+ 声明 `DATA_FIELD` 后,引擎会自动注入下列能力(完整字段列表见 [Traits能力模型 · DATA_FIELD](../reference/Traits能力模型.md#data_field)):
201
+
108
202
  | 能力 | 说明 |
109
203
  |------|------|
110
- | 值注入 | 宿主向组件注入当前 `value` |
111
- | 值回写 | 组件调用 `onChange` 后,宿主更新值 |
204
+ | 值注入与回写 | 宿主注入 `value`、`readOnly`、`required`、`name`、`label`、`labelStrategy`;`onChange` 回写值 |
205
+ | 标准 actions | `getValue` / `setValue` / `getReadOnly` / `setReadOnly` / `toggleReadOnly` / `getRequired` / `setRequired` / `toggleRequired` / `triggerValueChange` |
206
+ | 标准 state | `value`(schema 由 `valueSchema` 透传)、`readOnly`、`required` |
207
+ | 标准 event | `valueChange` |
208
+ | valueSchema 特化 | `setValue.params.value`、`setValue.returns.{newValue,oldValue}`、`getValue.returns`、`state.value.schema` 自动按 `meta.valueSchema` 特化 |
112
209
  | 类型识别 | 设计器、表达式和 AI 工具可读取 `meta.valueSchema` |
113
- | 容器协同 | 在数据容器内,字段可按 `name` 写入容器 value |
114
- | 自动状态补充 | 宿主可基于数据能力补充标准 value 状态 |
210
+ | 容器协同 | 在数据容器内,字段按 `name` 写入容器 value |
115
211
 
116
212
  ---
117
213
 
@@ -119,8 +215,10 @@ export const acmeInputManifest = {
119
215
 
120
216
  - [ ] `traits` 包含 `COMPONENT_TRAIT.DATA_FIELD`。
121
217
  - [ ] `meta.valueSchema` 描述了 value 类型。
122
- - [ ] 组件接收 `value`。
123
- - [ ] 值变化时调用 `onChange(nextValue)`。
218
+ - [ ] 组件接收 `value`,值变化时调用 `onChange(nextValue)`。
219
+ - [ ] 组件按需接收并应用 `readOnly` / `required` / `name` / `label` / `labelStrategy`(不必全部支持,但接收的 prop 必须真正生效)。
220
+ - [ ] 若支持 `placeholder`,已确保仅在可编辑态显示,`readOnly` / `disabled` 时隐藏。
221
+ - [ ] manifest 中没有不必要地重复声明 DATA_FIELD 自动注入的 props / actions / state / events;确有覆盖的,能明确说出特化原因。
124
222
  - [ ] `validateManifest()` 没有 error。
125
223
 
126
224
  ---
@@ -135,6 +233,18 @@ export const acmeInputManifest = {
135
233
 
136
234
  如果第三方组件使用 `selectedValue`、`onSelectedValueChange` 等命名,需要在 wrapper 内转换,或阅读 [使用 Adapter 适配组件 API](./使用Adapter适配组件API.md)。
137
235
 
236
+ ### 不必要地重复声明自动注入字段
237
+
238
+ 在自己的 manifest 里再写一遍 `value` prop、`getValue` / `setValue` action、`valueChange` event 等,会**覆盖**引擎注入的版本,并**丢失 `valueSchema` 自动特化**。默认只写**业务专属**的 props(如 `placeholder`)和事件;仅在**确有特化需求**时才显式覆盖自动注入的字段,并在代码/PR 中说明原因。
239
+
240
+ ### `placeholder` 在只读/禁用态仍然显示
241
+
242
+ 占位提示与已锁定的真实值同时出现,会让使用者误解控件状态。约定是**只在可编辑态显示** `placeholder`,进入 `readOnly` / `disabled` 时清空或不传该 prop(如 `placeholder={readOnly ? undefined : placeholder}`)。
243
+
244
+ ### 接收了 `readOnly` / `required` 却没有真正生效
245
+
246
+ 引擎会注入 `readOnly` / `required` 并在 state 中持续更新;如果组件渲染时忽略这两个 prop,事件编排里 `setReadOnly` / `toggleRequired` 等 action 看起来生效(state 变了)但 UI 不变,会让使用者困惑。要么接收并应用,要么不接收(说明组件不支持该语义)。
247
+
138
248
  ---
139
249
 
140
250
  ## 关联文档
@@ -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 { DataScope, useDataContainerApi } from 'cdp-material-sdk/host-react';
101
- import { useFieldRegistry } from './useFieldRegistry';
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 这类数组型容器