fix(fields): field:select 把 aria-invalid/aria-describedby/aria-required 送达真实控件(Select trigger),并加全注册表守卫 (#3306) - #3323
Merged
Conversation
…he Select trigger Radix Select.Root renders no DOM element and silently drops every prop it does not recognise, so the widget's DOM pass-through spread on Root threw away the form renderer's aria-invalid / aria-describedby / aria-required. A failed required select showed the red message while assistive tech was told nothing (objectui#3306). The spread now lands on SelectTrigger — the focusable button role=combobox the user actually interacts with — with name kept on Root (it feeds the hidden native select that takes part in form submission) and disabled kept single-authority on Root. The widget also computes aria-invalid from the published error slot, the objectui#3222 discipline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa
… failed validation Every registered widget type renders through the real form, drives a real react-hook-form failure (presence-check required + null, cloud#972), proves the message rendered, then asserts aria-invalid="true" exists inside the field row. The 29 types measured not to deliver are pinned in a ratchet ledger (NOT_YET_DELIVERED, tracked in objectui#3318) asserted in the opposite direction, so fixing a widget forces its ledger entry out and the ledger only shrinks. Paradigm of the objectui#3291 leak sweep: static imports through withFieldCarrier, no React.lazy, popovers not opened. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
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 #3306
问题
field:select把 DOM pass-through({...toDomProps(props)})展开在 RadixSelect.Root上。Select.Root自身不渲染任何 DOM 元素,不认识的 prop 一律静默丢弃——表单渲染器送下来的aria-invalid、aria-describedby、aria-required全部消失。实测:一个required的 select 提交失败后,红字错误信息视觉上正常显示,但真实控件(trigger)上没有任何 aria 状态——屏幕阅读器用户完全收不到「该字段处于错误状态」,也拿不到错误信息的关联。这与 #3222(7 个 widget 的
aria-invalid从未被算出)和 #3290(aria-required声明了没送达)同属本仓视为一等缺陷的「declared ≠ delivered」失败类。修复
packages/fields/src/widgets/SelectField.tsx(单选分支):SelectTrigger上——真实可聚焦的<button role="combobox">,用户与辅助技术实际交互的元素。aria-invalid/aria-describedby/aria-required/data-*/id/className/tabIndex/ 焦点事件由此送达真实 DOM。name——Root 唯一真正消费的白名单 key(转发给参与原生表单提交的隐藏<select>);放到 trigger 上只会挂在一个非 submitter 的<button>上;disabled——Root 是单一权威(同时禁用 trigger、选项交互与隐藏 select);再转发给 trigger 会给这个状态第二个 OR 合并的作者。aria-invalid={!!error}(字段 widget 的错误提示键:spec 声明error,objectui 渲染errorMessage(declared ≠ enforced) #3222 纪律:error是 specFieldWidgetPropsSchema的发布槽位;!!undefined必须产出显式的"false"——字段有效时明说有效,而不是沉默)。没有放宽
toDomProps(白名单原样),没有在消费端加任何容错回退。顺带核查(issue 要求的同结构扫描)
逐一核查了全部 46 个注册 widget 的 spread 落点:只有
SelectField把 DOM pass-through 展开到非 DOM 的 Radix Root 上。Boolean/Date/DateTime/Time 等其余toDomProps使用者都落在真实 DOM 控件(Input/ Radix Checkbox·Switch 的真实<button>)上。守卫(issue 要求的「不再逐个复发」)
新增全注册表 sweep
packages/fields/src/__tests__/widget-aria-invalid-registry-e2e.test.tsx(范式同 #3291 的 DOM leak sweep):required+null,cloud#972),先自证 "is required" 消息已渲染再读 aria(无错误的错误变体什么都测不到);aria-invalid="true",且失败前不存在(不许提前报警);NOT_YET_DELIVERED并按反方向断言:修好任何一个,它的账本行变红、强制移出账本——账本只能收缩,新类型默认走正向断言,不能悄悄进账本。29 个缺口已作为独立未认领 issue 记录:29 个注册 field widget 在校验失败后从不携带aria-invalid—— #3306 全注册表守卫实测的账本 #3318;同时把
select补进既有widget-aria-invalid-e2e.test.tsx(#3222 的守卫,当年漏掉 select 的那个),新增 3 条针对 trigger 的断言:aria-invalid仅在失败后为"true"(此前显式"false")、aria-required在位、aria-describedby的 id 链闭合到渲染出的错误信息元素。破坏验证实录
3 failed | 10 skipped)git checkout回修复态13 passed全绿text植入账本field:text now delivers aria-invalid — remove it from NOT_YET_DELIVERED验证
vitest run widget-aria-invalid-e2e.test.tsxvitest run widget-aria-invalid-registry-e2e.test.tsxvitest run widget-dom-leak-e2e.test.tsx(#3313 的 gate,46×5)vitest run packages/fields全包改动仅涉
packages/fields(widget + 测试)与一个 changeset(@object-ui/fieldspatch,fixed group,无 major)。未触碰content/docs/releases/。🤖 Generated with Claude Code
https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa