Skip to content

fix(app-shell,components,plugin-detail): 对象表单之外五处渲染器把必填状态送进 a11y 树 (#3299) - #3340

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-3299-required-a11y-five-sites
Aug 4, 2026
Merged

fix(app-shell,components,plugin-detail): 对象表单之外五处渲染器把必填状态送进 a11y 树 (#3299)#3340
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-3299-required-a11y-five-sites

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #3299

改动

issue #3290 / PR #3298 修掉了 form.tsx「必填只画星号、控件无必填状态」的缺陷;本 PR 按 #3299 清单把其范围栅栏之外的五处逐一收敛到同一参照形状(EmbeddableForm.tsx:481,489):

  • 控件:aria-required={required || undefined}(可选字段不带属性,而不是写 "false");
  • 星号:aria-hidden="true"(必填状态只经状态通道播报一次,不再折进可访问名被读成 "asterisk")。
位置 控件 处理
packages/app-shell/src/views/ActionParamDialog.tsx 布尔行 + 默认分支的字段 widget host 侧传 aria-required,经 fields 的 toDomProps 白名单(aria-* 前缀放行,#3291/#3313)落到真实控件
packages/app-shell/src/views/CreateViewDialog.tsx 显示名 / 机器名两个 Input + 各视图类型必选字段 select 三处均静态必填 → 字面 aria-required="true"
packages/components/src/custom/action-param-dialog.tsx textarea / number / select / date / text 五个分支 select 的状态落在 Radix trigger(可聚焦的 combobox;root 不渲染元素)
packages/components/src/custom/field.tsx FieldContainer 的 Slot 注入 与既有 id / aria-describedby / aria-invalid 同通道补一个 aria-required,一处覆盖全部使用方;其星号是 CSS 伪元素、本就不进 a11y 树,无需改
packages/plugin-detail/src/InlineCreateRelated.tsx Create 页签的 Input 同型收敛

刻意不用原生 required#3290 裁决):这些宿主各自有校验(requiredError 消息 / isCreateValid 门控 / 提交时校验),原生 required 会让浏览器约束校验气泡与之双轨。issue「明确排除」段照单全收:renderers/form/{input,textarea,select,checkbox}.tsxbasic/text-input.tsx 已设原生 required 且无双轨问题,不动。

不动 spec、不动 widget props 契约(aria-required 已在既有 widget props 类型里,无需新键)。

验证

命令 结果
仓根 pnpm exec vitest run 五个新测试文件 + 既有 ActionParamDialog.test.tsx / ActionParamDialog.uploading.test.tsx 7 files / 58 tests 全绿(16.29s)
turbo run type-check --filter=@object-ui/components --filter=@object-ui/app-shell --filter=@object-ui/plugin-detail 31 tasks successful

破坏验证实录(摘掉 → 红 → 还原 → 绿):

  1. custom/field.tsx 摘掉 Slot 注入的 aria-requiredfield-container-aria-required.test.tsx 5 例中 4 例红sets aria-required="true" on the slotted control / never sets the native required / coexists with the pre-existing Slot injections / keeps the label association intact)→ 还原全绿。这是承重位:一处覆盖 FieldContainer 全部使用方。
  2. app-shell/ActionParamDialog.tsx 摘掉默认分支 Widget 的 aria-requiredActionParamDialog.ariaRequired.test.tsx 5 例中 2 例红 → 还原全绿。该用例走真实字段 widget 全链路(host → widget props → toDomProps → DOM),widget 侧若吞掉 aria-* 同样会红。

changeset:@object-ui/app-shell / @object-ui/components / @object-ui/plugin-detailpatch(fixed group,无 major)。

按 push-first 纪律:scoped 测试全绿 + 破坏验证完成后即推送;三个包的全量回归随后本地补跑,并由 CI 把关。

🤖 Generated with Claude Code

https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa


Generated by Claude Code

… five renderer sites outside the object form (#3299)

Five renderers computed `required`, painted a red asterisk, and put NO
required state on the control — the same defect #3290/#3298 fixed in
form.tsx, at the five sites its scope fence excluded. Four of them were
worse than pre-fix form.tsx: the bare `*` had no `aria-hidden`, so the
only "signal" was a literal asterisk folded into the accessible name
("Title asterisk").

Every site converges on the reference shape (EmbeddableForm.tsx:481,489):

- control: `aria-required={required || undefined}` — absence, not
  "false", for optional fields;
- asterisk: `aria-hidden="true"` — announced once, as a state, never as
  part of the name.

Sites:
- app-shell/ActionParamDialog: both branches (boolean row + default);
  the state rides the widget props and reaches the DOM through the
  fields' `toDomProps` whitelist, which forwards `aria-*` by prefix.
- app-shell/CreateViewDialog: display label, machine name, and each
  type-specific required-field selector (statically required, so a
  literal `aria-required="true"`).
- components/custom/action-param-dialog: all five typed branches; for
  select the state lands on the Radix trigger (the focusable combobox —
  the root renders no element).
- components/custom/field: FieldContainer injects `aria-required` via
  the same Slot that already injects id/aria-describedby/aria-invalid —
  one line covers every consumer. Its asterisk is a CSS pseudo-element,
  which never enters the a11y tree, so no marker change is needed there.
- plugin-detail/InlineCreateRelated: create-tab inputs.

Deliberately NOT native `required` (#3290 ruling): each host runs its
own validation; native required would arm the browser's constraint-
validation bubble beside it. The SDUI controls that already set native
`required` (renderers/form/{input,textarea,select,checkbox}.tsx,
basic/text-input.tsx) are excluded per the issue — no second validator
there, so their channel is already correct.

Tests per site, with mutation verification on the two load-bearing
deliveries (FieldContainer's Slot injection; the app-shell dialog's
widget-chain delivery): attribute removed -> red, restored -> green.

Fixes #3299

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 4, 2026 5:19am

Request Review

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-L1Cm0zM-.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.47KB 3.09KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 477.05KB 104.72KB
core (index.js) 2.25KB 0.80KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 136.23KB 34.75KB
fields (index.js) 224.99KB 55.14KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 26.14KB 6.07KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 38.53KB 10.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.05KB 1.53KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.98KB 12.37KB
plugin-charts (index.js) 60.54KB 17.13KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 112.01KB 28.86KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 230.68KB 56.83KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 111.54KB 26.97KB
plugin-gantt (index.js) 162.25KB 39.55KB
plugin-grid (index.js) 185.08KB 49.04KB
plugin-kanban (index.js) 47.89KB 13.18KB
plugin-list (index.js) 104.94KB 25.32KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.55KB 10.59KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.34KB 2.82KB
plugin-view (index.js) 83.67KB 20.43KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.46KB 1.21KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 4, 2026 06:38
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit 532cf8b Aug 4, 2026
14 of 16 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-3299-required-a11y-five-sites branch August 4, 2026 06:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

对象表单之外的一批渲染器仍把必填状态画成星号而不送进 a11y 树(#3290 的同类缺陷,另外五处)

2 participants