在 #4788 (只读替换显示丢整份 host plumbing)的实施中实测发现的相邻一条,与那个 PR 无关也不由它引入 ,按 Prime Directive #10 立单(不认领、不扩围)。
#4788 修的是只读态 :host 在「readonly + 注册 field widget」时包一层带 id / aria-labelledby / aria-describedby / role="group" 的容器。本条是同一族里 readonly 门管不到的那一半 —— 有七个 widget 在可编辑态 也一样丢掉 host 下发的 id,因为它们根本没有可编辑分支 ,或者有分支但不展开 DOM pass-through。
实测
origin/main 1ef236e18,与 #4788 同一套 probe(真 form renderer + 裸注册,字段带 description: 'Some help',一行一个字段),看 editable 列:
formula editable for=DANGLING hostIdEl=NONE consumers=0
summary editable for=DANGLING hostIdEl=NONE consumers=0
auto_number editable for=DANGLING hostIdEl=NONE consumers=0
vector editable for=DANGLING hostIdEl=NONE consumers=0
grid editable for=DANGLING hostIdEl=NONE consumers=0
slider editable for=DANGLING hostIdEl=NONE consumers=0
signature editable for=DANGLING hostIdEl=NONE consumers=0
对照:其余 27 个注册的非 group-labelled 类型在 editable 列全部是 for=RESOLVES-LABELABLE hostIdEl=input/textarea/button consumers=1。
即:可见 label 的 for 指向一个整个文档里没有任何元素带 的 id,只读态之外也一样 —— 这七个字段在无障碍树里没有名字 ,渲染出来的帮助文本零消费者。
为什么 #4788 的修法覆盖不到
#4788 的 host 门是 readonly === true。而在真实的对象表单路径上,计算型字段并不 走 readonly:
packages/plugin-form/src/ObjectForm.tsx:687 把 formula / summary / auto_number 映射成 formField.disabled = true,不是 readonly: true。所以 ObjectForm 渲染出来的公式字段拿不到 #4788 的容器,for 照旧悬空。
另外三个(grid / slider / signature)与 readonly 无关:它们的唯一 渲染分支就不展开 host props。
涉及文件
需要一次方向裁决,不要直接推
至少三种形状,取舍不同,不要 按 #4788 外推:
把 D 组映射成 readonly: true (改 ObjectForm)—— 最小,直接落进 [fields] 非 group-labelled widget 的只读替换显示丢掉 host 下发的整份 plumbing —— label 的 for 悬空、description 零消费者(email / url / phone 实测) #4788 已有的容器机制;但 disabled 与 readonly 在本仓是有意区分 的两件事(form.tsx 的注释写明了),这条改的是语义分类而不只是 a11y;
给 host 一个 display-only 声明 (ComponentMeta 加一维,类似 labelling)—— 契约优先,host 不再靠「registered + readonly」推断;代价是新增一个公开契约键,且漏声明会静默退回今天的坏形状;
widget 侧各自展开 pass-through —— grid / slider / signature 有真控件,这条对它们大概是对的(host id 落到可 label 元素上,for 自然解析);但对 D 组无效,它们渲染的 span 不是 labelable 元素,for 落上去依然惰性([fields] 复合/分组 field widget 丢弃或错投 host 下发的控件 id:address / geolocation 的表单 label for 悬空,checkboxes / radio / rating / file 的 for 落在不可 label 的 div 上 #3961 当初换掉 for 的原因)。
三者可能要分组合用:grid / slider / signature 走 3,D 组走 1 或 2。
参考
在 #4788(只读替换显示丢整份 host plumbing)的实施中实测发现的相邻一条,与那个 PR 无关也不由它引入,按 Prime Directive #10 立单(不认领、不扩围)。
#4788 修的是只读态:host 在「readonly + 注册 field widget」时包一层带
id/aria-labelledby/aria-describedby/role="group"的容器。本条是同一族里 readonly 门管不到的那一半 —— 有七个 widget 在可编辑态也一样丢掉 host 下发的id,因为它们根本没有可编辑分支,或者有分支但不展开 DOM pass-through。实测
origin/main1ef236e18,与 #4788 同一套 probe(真 form renderer + 裸注册,字段带description: 'Some help',一行一个字段),看 editable 列:对照:其余 27 个注册的非 group-labelled 类型在 editable 列全部是
for=RESOLVES-LABELABLE hostIdEl=input/textarea/button consumers=1。即:可见 label 的
for指向一个整个文档里没有任何元素带的 id,只读态之外也一样 —— 这七个字段在无障碍树里没有名字,渲染出来的帮助文本零消费者。为什么 #4788 的修法覆盖不到
#4788 的 host 门是
readonly === true。而在真实的对象表单路径上,计算型字段并不走 readonly:packages/plugin-form/src/ObjectForm.tsx:687把formula/summary/auto_number映射成formField.disabled = true,不是readonly: true。所以 ObjectForm 渲染出来的公式字段拿不到 #4788 的容器,for照旧悬空。另外三个(
grid/slider/signature)与 readonly 无关:它们的唯一渲染分支就不展开 host props。涉及文件
packages/fields/src/widgets/FormulaField.tsx、SummaryField.tsx、AutoNumberField.tsx、VectorField.tsx—— 无可编辑分支,整个 widget 即显示面,签名把 props 收进...props后只读props.className;packages/fields/src/widgets/GridField.tsx、SliderField.tsx、SignatureField.tsx—— 有交互面,但渲染的元素上不展开 DOM pass-through;packages/plugin-form/src/ObjectForm.tsx:687—— 计算型字段映射到disabled而非readonly,是 D 组在真实表单里绕开 [fields] 非 group-labelled widget 的只读替换显示丢掉 host 下发的整份 plumbing —— label 的for悬空、description 零消费者(email / url / phone 实测) #4788 门的原因。需要一次方向裁决,不要直接推
至少三种形状,取舍不同,不要按 #4788 外推:
readonly: true(改 ObjectForm)—— 最小,直接落进 [fields] 非 group-labelled widget 的只读替换显示丢掉 host 下发的整份 plumbing —— label 的for悬空、description 零消费者(email / url / phone 实测) #4788 已有的容器机制;但disabled与readonly在本仓是有意区分的两件事(form.tsx 的注释写明了),这条改的是语义分类而不只是 a11y;ComponentMeta加一维,类似labelling)—— 契约优先,host 不再靠「registered + readonly」推断;代价是新增一个公开契约键,且漏声明会静默退回今天的坏形状;grid/slider/signature有真控件,这条对它们大概是对的(host id 落到可 label 元素上,for自然解析);但对 D 组无效,它们渲染的 span 不是 labelable 元素,for落上去依然惰性([fields] 复合/分组 field widget 丢弃或错投 host 下发的控件 id:address / geolocation 的表单 labelfor悬空,checkboxes / radio / rating / file 的for落在不可 label 的 div 上 #3961 当初换掉for的原因)。三者可能要分组合用:
grid/slider/signature走 3,D 组走 1 或 2。参考
for悬空、description 零消费者(email / url / phone 实测) #4788 / 2026-08-16 维护者裁定(评论 5307576062)—— 只读态的 host 容器机制for悬空,checkboxes / radio / rating / file 的for落在不可 label 的 div 上 #3961 / PR fix(fields,components): 复合/分组 field widget 的组标签改用 IDREF 关联(#3961) #3978、[fields] multiselect 的 host label 也落在不可 label 的包裹 div 上 —— 与 #3961 同一失效类,但不在其审计的六个 widget 内 #3975 / PR fix(fields): name the multiselect chip group by IDREF instead of an inertfor(#3975) #3983、[fields] 只读 / 零选项的 group-labelled 选项 widget 提前 return,groupDomProps没被展开 —— host label 的 IDREF 没有消费者,标签仍然什么都不命名(#3961 的残留,checkboxes 今天的 main 上就这样) #3990 / PR fix(fields): group-labelled widget 的只读/零选项分支也消费 host label 的 IDREF (#3990) #4002、[fields] 只读态的 group-labelled 字段:description 的 IDREF 同样没有消费者(#3990 修的是「名」,这条是「描述」) #4005 —— group-labelled 一族