发现于 #4010 的实施过程(该单主体是 ColorVariantPicker 的三个调用点,PR 只修了 picker 侧,宿主侧刻意未动)。未认领,交 PM triage。
机制 MetadataField(packages/app-shell/src/views/metadata-admin/SchemaForm.tsx)对每个字段渲染
Label htmlFor={id} // id = `mdf-${name}`,确定性拼接,非 useId
并把同一个 id 传给 FieldControl → 注册 widget。约定与别处一致:被包控件带同一个 id 。
但 widgets.tsx 的 WIDGETS 注册表里,17 个 *Widget 函数中只有 5 个 在解构里收 id(RefComponentWidget :263、FieldRefWidget :948、ViewRefWidget :1011、FieldRefMultiWidget :1064、ActionMultiWidget :1611)。其余的把 id 丢掉,于是那句 htmlFor 指向一个没有任何元素持有的 id —— 悬空 IDREF ,与 #3961 / #3990 / #4788 / #4857 同一失效类,只是宿主换成了 metadata-admin 自己的这套表单。
两档严重度,取决于 widget 渲染的是什么:
可 label 的控件 (Input / Textarea 之类):for 悬空,可见 label 无归属,但控件本身至少还能被点选;不可 label 的容器 :for 连修都修不了(容器不是 labelable 元素),而且组本身没有 aria-label / aria-labelledby —— 完全匿名。已确认两处:
实测(#4010 实施时,objectui origin/main @ 65e88e6c2,真 SchemaForm 渲染) colorVariant(type: string,带 enum,title: "Color Variant"):
label[for]: [{ text: "Color Variant", for: "mdf-colorVariant", resolves: FALSE }]
radiogroup: aria-labelledby=null aria-label=null accName=""
resolves: false 即文档里没有任何元素持有 mdf-colorVariant。#4010 的 PR 之后 accName 变成 "Color Variant"(picker 自带),resolves 仍然是 false —— 那一半正是本单。
为什么宿主侧才修得了(#4010 没有顺手做的原因) 分组面的正确命名通道是 IDREF:label 发布自己的 id、组用 aria-labelledby 应答,并且把 for 去掉 (packages/components/src/renderers/form/form.tsx 在 labelling: 'group' 分支正是这么写的,并注明「for 落在 div 上是惰性 HTML,留着就是一个 label 两条通道、其中一条是坏的」)。
宿主要发出这个 IDREF,就必须在渲染 label 之前 知道该字段最终渲染的是分组面还是可 label 控件。color-picker 恰好是反例:同一个注册项,有 enum 时渲染 radiogroup、没有时渲染可 label 的 input type="color",而这个分支是 widget 自己按 schema/fieldSpec 决定的 —— 宿主写 label 时还不知道。
所以这需要一个逐 widget 的 labelling 声明 (packages/components 的 form renderer 已有的那个形状:ComponentMeta.labelling: 'control' | 'group',#3961 ),而 widgets.tsx 的 WIDGETS 是一张裸 Record,没有 meta 层。对 color-picker 这种条件分组 的,还要决定是拆成两个注册项、还是让宿主把两条通道都递下去由 widget 自选 —— 属于契约层要先定的一步,不该在 #4010 里猜。
边界与去重 不是 [app-shell] DashboardWidgetInspector 的 Field(id="widget-color") 的 for 悬空 —— ColorVariantPicker 不收 id,颜色 radiogroup 完全无可访问名 #4010 :那单是 ColorVariantPicker 组件本身与它的三个调用点;其 PR 让 picker 强制带名(类型层二选一),并在 SchemaForm.colorPicker.labelling.test.tsx 里把「宿主的 for 仍解析不到」作为已知残留 钉住,注释直接指向本单。不是 [fields] 七个 widget 在**可编辑态**也丢掉 host 下发的 id —— label 的 for 悬空、description 零消费者(formula / summary / auto_number / vector / grid / slider / signature 实测) #4857 / [fields] 非 group-labelled widget 的只读替换显示丢掉 host 下发的整份 plumbing —— label 的 for 悬空、description 零消费者(email / url / phone 实测) #4788 / [fields] 复合/分组 field widget 丢弃或错投 host 下发的控件 id:address / geolocation 的表单 label for 悬空,checkboxes / radio / rating / file 的 for 落在不可 label 的 div 上 #3961 / [fields] 只读 / 零选项的 group-labelled 选项 widget 提前 return,groupDomProps 没被展开 —— host label 的 IDREF 没有消费者,标签仍然什么都不命名(#3961 的残留,checkboxes 今天的 main 上就这样) #3990 :那四条都在 packages/fields + packages/components 的 SDUI form renderer 宿主,widget 注册表、host 下发的 plumbing、toDomProps / toHostGroupProps 全是另一套。本单在 packages/app-shell/src/views/metadata-admin 的设计器自有表单,与那套无共享实现 —— 同一失效类、不同宿主。不是 [components] 内建 select 的空选项/依赖门态分支把 host id 落在一个 div 上 —— label 的 for 指向不可 label 元素,仍是不关联的 #3991 :那条在 packages/components 内建 select 的空选项分支。检索过 open issues(SchemaForm htmlFor widget id、metadata-admin 无可访问名 悬空 for 分组、widgets.tsx registry drops id),无同题单。 建议的验收面 先把 17 个注册 widget 逐个测出「host 的 for 是否解析得到 / 渲染面是否可 label / 组是否有名」三列读数(#4788 与 #4857 都是这么摆的账本),再据此定 labelling 声明的形状 —— 逐个改 widget 而不先定声明,会把同一个决定散到十几处。
发现于 #4010 的实施过程(该单主体是
ColorVariantPicker的三个调用点,PR 只修了 picker 侧,宿主侧刻意未动)。未认领,交 PM triage。机制
MetadataField(packages/app-shell/src/views/metadata-admin/SchemaForm.tsx)对每个字段渲染并把同一个
id传给FieldControl→ 注册 widget。约定与别处一致:被包控件带同一个 id。但
widgets.tsx的WIDGETS注册表里,17 个*Widget函数中只有 5 个在解构里收id(RefComponentWidget:263、FieldRefWidget:948、ViewRefWidget:1011、FieldRefMultiWidget:1064、ActionMultiWidget:1611)。其余的把id丢掉,于是那句htmlFor指向一个没有任何元素持有的 id —— 悬空 IDREF,与 #3961 / #3990 / #4788 / #4857 同一失效类,只是宿主换成了 metadata-admin 自己的这套表单。两档严重度,取决于 widget 渲染的是什么:
Input/Textarea之类):for悬空,可见 label 无归属,但控件本身至少还能被点选;for连修都修不了(容器不是 labelable 元素),而且组本身没有aria-label/aria-labelledby—— 完全匿名。已确认两处:ColorPickerWidget的 enum 分支(div role="radiogroup")—— [app-shell] DashboardWidgetInspector 的 Field(id="widget-color") 的 for 悬空 —— ColorVariantPicker 不收 id,颜色 radiogroup 完全无可访问名 #4010 已在 picker 侧自带aria-label兜住命名,宿主那句for依旧解析不到;MultiSelectWidget(:899,div role="group")—— 解构{ value, onChange, readOnly, schema, fieldSpec },不收 id;组上既无aria-label也无aria-labelledby,今天就是匿名的。实测(#4010 实施时,objectui
origin/main@65e88e6c2,真SchemaForm渲染)colorVariant(type: string,带enum,title: "Color Variant"):resolves: false即文档里没有任何元素持有mdf-colorVariant。#4010 的 PR 之后accName变成"Color Variant"(picker 自带),resolves仍然是false—— 那一半正是本单。为什么宿主侧才修得了(#4010 没有顺手做的原因)
分组面的正确命名通道是 IDREF:label 发布自己的
id、组用aria-labelledby应答,并且把for去掉(packages/components/src/renderers/form/form.tsx在labelling: 'group'分支正是这么写的,并注明「for落在 div 上是惰性 HTML,留着就是一个 label 两条通道、其中一条是坏的」)。宿主要发出这个 IDREF,就必须在渲染 label 之前知道该字段最终渲染的是分组面还是可 label 控件。
color-picker恰好是反例:同一个注册项,有enum时渲染radiogroup、没有时渲染可 label 的input type="color",而这个分支是 widget 自己按schema/fieldSpec决定的 —— 宿主写 label 时还不知道。所以这需要一个逐 widget 的
labelling声明(packages/components的 form renderer 已有的那个形状:ComponentMeta.labelling: 'control' | 'group',#3961),而widgets.tsx的WIDGETS是一张裸Record,没有 meta 层。对color-picker这种条件分组的,还要决定是拆成两个注册项、还是让宿主把两条通道都递下去由 widget 自选 —— 属于契约层要先定的一步,不该在 #4010 里猜。边界与去重
ColorVariantPicker组件本身与它的三个调用点;其 PR 让 picker 强制带名(类型层二选一),并在SchemaForm.colorPicker.labelling.test.tsx里把「宿主的for仍解析不到」作为已知残留钉住,注释直接指向本单。for悬空、description 零消费者(formula / summary / auto_number / vector / grid / slider / signature 实测) #4857 / [fields] 非 group-labelled widget 的只读替换显示丢掉 host 下发的整份 plumbing —— label 的for悬空、description 零消费者(email / url / phone 实测) #4788 / [fields] 复合/分组 field widget 丢弃或错投 host 下发的控件 id:address / geolocation 的表单 labelfor悬空,checkboxes / radio / rating / file 的for落在不可 label 的 div 上 #3961 / [fields] 只读 / 零选项的 group-labelled 选项 widget 提前 return,groupDomProps没被展开 —— host label 的 IDREF 没有消费者,标签仍然什么都不命名(#3961 的残留,checkboxes 今天的 main 上就这样) #3990:那四条都在packages/fields+packages/components的 SDUI form renderer 宿主,widget 注册表、host 下发的 plumbing、toDomProps/toHostGroupProps全是另一套。本单在packages/app-shell/src/views/metadata-admin的设计器自有表单,与那套无共享实现 —— 同一失效类、不同宿主。packages/components内建 select 的空选项分支。SchemaForm htmlFor widget id、metadata-admin 无可访问名 悬空 for 分组、widgets.tsx registry drops id),无同题单。建议的验收面
先把 17 个注册 widget 逐个测出「host 的
for是否解析得到 / 渲染面是否可 label / 组是否有名」三列读数(#4788 与 #4857 都是这么摆的账本),再据此定labelling声明的形状 —— 逐个改 widget 而不先定声明,会把同一个决定散到十几处。