From d33e5d396f80e661e16bf672706e19fefe4076bd Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Aug 2026 18:36:04 +0000 Subject: [PATCH] =?UTF-8?q?fix(app-shell):=20ActionParamDialog=20=E7=9A=84?= =?UTF-8?q?=20boolean=20=E5=88=86=E6=94=AF=E4=B8=8B=E5=8F=91=E6=8E=A7?= =?UTF-8?q?=E4=BB=B6=20id,=E5=8F=AF=E8=AE=BF=E9=97=AE=E5=90=8D=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E6=98=AF=E4=B8=A4=E4=BB=BD=20label=20=E7=9A=84?= =?UTF-8?q?=E6=8B=BC=E6=8E=A5=20(#3962)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ActionParamDialog` 的 boolean 分支渲染 `Label htmlFor={param.name}` 放在控件 旁边,却不给 widget 传 `id` —— 而同一文件下面几十行的通用分支一直传的是 `id={param.name}`。实测(#3952 / PR #3959 之后、真 dialog 渲染)一个 boolean param 产出两个 label 元素指向同一个控件:widget 自己那份 sr-only 的,和 dialog 的可见那份,文本相同。两个 `for` 都能解析,所以这不是 #3341 / #3952 那种悬空 label —— 点击一直是好的;坏的是名字:按 accname 规范,引用同一控件的多个 label 会拼接成可访问名,屏幕阅读器听到的是 "Confirm This Confirm This"。 两个缺陷,相隔一行: 1. 关联是隐式的。它成立只因为 `BooleanField` 的 id 回退链第二项落在 `config.name`,而 `paramToField` 把它设成了 `param.name` —— 两边靠另一个包的 内部实现恰好撞成同一个字符串。一个渲染了 `htmlFor` 的 host 必须自己发出它所 指的 id;widget 契约(`FieldWidgetDomProps`)声明 `id` 就是为这个。 2. 重复的 sr-only label。#3952 / PR #3959 让 `BooleanField` 在 host 下发 id 时 不再发自己的 label(`emitOwnLabel = !hostId`),理由正是「会下发 id 的 host 就是会渲染 label 的 host」。这个分支收不到 id,那条抑制从来没被触发。 修法是让 boolean 分支像它自己的通用分支那样传 `id={param.name}`:关联从隐式变 显式,widget 的 sr-only 副本被抑制,可访问名回到作者声明的那一份。通用分支未动。 钉子挂在两个既有测试文件上。命名一半在 `ActionParamDialog.ariaRequired.test.tsx` (4 条):boolean 控件 id 恰为 `param.name` 且 `label[for]` 恰好 1 个、活下来的 是可见那份而非 sr-only;可访问名恰为一份;必填 boolean 的名字仍不含 `aria-hidden` 的星号且带 `aria-required="true"`;以及不变方向的通用分支 host id / 单 label / 单名字。行为一半在 `ActionParamDialog.test.tsx`(1 条):点击可见 label 翻转开关并 round-trip 到 `resolve`。 刻意没有用 `getAllByLabelText` 做探针(issue 正文提议过): `@testing-library/dom` 10.4.1 的 `queryAllByLabelText` 最后一步是 `Array.from(new Set(matchingLabelledElements))`,并且对多 label 会把名字拆开 分别匹配,修前修后它都只解析出 1 个控件,当探针是空转的。可测量的那一半是名字 与 label 元素个数。 反向验证按预判成立:删掉 boolean 分支那一行 `id={param.name}` 后恰好 3 条命名钉 翻红,而「通用分支不变」与点击 label 那条保持绿 —— 修前 `for` 目标本来就能解析, 这个缺陷是重复而非悬空。变异未提交。 Co-authored-by: Claude --- .../actionparamdialog-boolean-host-id-3962.md | 30 ++++++++ .../ActionParamDialog.ariaRequired.test.tsx | 73 +++++++++++++++++++ .../src/views/ActionParamDialog.test.tsx | 20 +++++ .../app-shell/src/views/ActionParamDialog.tsx | 15 ++++ 4 files changed, 138 insertions(+) create mode 100644 .changeset/actionparamdialog-boolean-host-id-3962.md diff --git a/.changeset/actionparamdialog-boolean-host-id-3962.md b/.changeset/actionparamdialog-boolean-host-id-3962.md new file mode 100644 index 0000000000..317015767d --- /dev/null +++ b/.changeset/actionparamdialog-boolean-host-id-3962.md @@ -0,0 +1,30 @@ +--- +"@object-ui/app-shell": patch +--- + +`ActionParamDialog` boolean params: the dialog now owns the control id, so the checkbox is named once instead of twice + +The boolean branch rendered `