fix(fields): name the multiselect chip group by IDREF instead of an inert for (#3975) - #3983
Merged
Merged
Conversation
…nert `for` (#3975) The residual of #3961, not a regression of it. That issue's probe audited six widgets; re-running the same probe over the whole widget map afterwards — on the tree that already carried #3961's fix — put `multiselect` on the byte-identical failure shape as `checkboxes`: checkboxes for=(none) ownId=…-group-label byLabelText=1(div[role=group]) multiselect for=…-form-item ownId=(none) byLabelText=0 `MultiSelectField` keeps the host's id on the chip row's wrapper `div`, so the host label's `for` "resolved" to a `div` — where `label for` is inert HTML (`HTMLLabelElement.control` is null: it activates nothing and contributes no accessible name). Visually a "Tags" label; a screen reader heard only "Alpha" / "Beta" and nothing about what the set was for. No new mechanism — #3961's declaration, applied to one more widget: - `FIELD_TYPES_GROUP_LABELLED` gains `multiselect`, so the form renderer publishes its label's `id` and drops the dead `for`; - the chip row answers `role="group"` when (and only when) it was handed an `aria-labelledby`, the same conditional `CheckboxesField` uses. Pins, both directions. The two files fail apart by design and were measured apart: dropping the set entry turns `group-labelling-declaration.test.ts` red (the per-type case + the "exactly the audited set" assertion) while the e2e file stays GREEN — it registers widgets raw with `labelling: 'group'` hardcoded in its own `beforeAll`, so it structurally cannot see the real declaration. Dropping the `role` inverts that: four e2e cases red, the declaration test green. Neither half is covered by the other's test, which is why both exist. Unchanged on purpose: each chip keeps its own accessible name from its text content (an `aria-labelledby` on a chip would OVERRIDE it, the same override this family refuses for address's street box), and standalone rendering — the grid's inline cell editor, a bare SDUI node — emits no role and no IDREF.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Collaborator
Author
|
PM 验收(session_01GTRjn8xBqp75dk7kFupVRt):通过,转 ready 并挂 auto-merge。#3975 落地。 核验记录(head
out-of-scope 两条(#3986 select multiple 路径分叉、#3990 只读/零选项态收口)由 PM 分诊(另评)。 Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3975
这是 #3961 的余账,不是它的回归
#3961 的 probe 覆盖了六个 widget 并修好;在已经带着 #3978 修复的同一棵树上把 probe 扩到整张 widget 表重跑,
multiselect落在与checkboxes逐字相同的失效形态上:MultiSelectField把 host 的id留在 chip 行的包裹div上,于是 host label 的for「解析成功」却指向一个div—— HTML 里 label 的for指向不可 label 的元素是惰性的(HTMLLabelElement.control返回 null:既不产生点击激活,也不贡献可访问名)。视觉上有「Tags」标签,屏幕阅读器只听到 "Alpha" / "Beta",不知道这一组是什么。修法:没有新机制,只是把 #3961 的声明用到第七个 widget
packages/fields/src/index.tsx的FIELD_TYPES_GROUP_LABELLED加multiselect—— 于是 form renderer 发布 label 的id、撤掉那个死掉的for;MultiSelectField的 chip 行在(且仅在)收到aria-labelledby时应答role="group",判据与CheckboxesField同一条件式。aria-*早已在 widget props 契约里声明并由toDomProps转发(aria-required#3290 用的就是这条通道),所以契约没有新增 key。钉子(两个方向,而且两个文件是互补的,不是重叠)
反向验证先预判后跑,两次变异均未提交:
group-labelling-declaration.test.tscomposite-group-label-e2e.test.tsxmultiselectrolee2e 文件在自己的
beforeAll里用labelling: 'group'硬编码裸注册(刻意避开React.lazy,见 AGENTS.md 测试纪律),所以它结构上看不见真实声明集合 —— 这正是那两个文件必须同时存在的原因,也是「撤掉集合项 → 钉翻红」这一朴素预期在此处不成立的地方(它翻红的是声明那一文件)。补充确认:另有一条一次性 probe(未提交)走真实registerAllFields()惰性注册路径,在两次变异下都翻红,合起来说明真实注册路径也确实通了。新增的钉:表单内
getByRole('group', { name: host label })接线前 0 / 后 1(并入 HOSTED 表,连带拿到它已有的四条方向性断言)、chip 各自保名不被 group 名吞掉、以及 standalone 不回退(chip 行仍是无role的div)。刻意不变的部分
aria-labelledby会顶掉它自己的名字,这正是本系列在 address 的 street 输入框上拒绝过的那种覆盖);顺手实测到、但不在本 PR 范围的一条
field:select+multiple: true会委派给MultiSelectField,而select必须保持未声明(单选是可 label 的),所以按字段类型键入的声明碰不到它。实测(本 PR 之后):for=…-form-item解析到div,byRoleGroupNamed=0,byLabelText=0—— 同一失效类的另一条路径。已另立 issue,不夹带。验证
pnpm exec vitest run packages/fields packages/components/src/renderers/form --maxWorkers=2→ 103 files / 1253 tests 全绿turbo run type-check(fields + components)→ 11 successful, 11 totalnode scripts/check-control-bytes.mjsOK;改动文件另做了一次超出该 gate 扫描面的自查Generated by Claude Code