Skip to content

内建 form 分支的长文本字段完全没有字数计数,同一个 maxLength 在两条渲染路径上给出两种体验 #3439

Description

@yinlianghui

#3417 时按「规则的消费半径」扫分叉扫到的,不在该 PR 范围内,独立记录。

这是 observation-class(建议打 finding,不要 pm:queue) —— 我没能证明今天有用户实际撞上它,而且它有相当概率是有意的分层(内建分支本就是精简 fallback)。按纪律不压着不报,严重程度交 PM 的 triage 定。我因为 GitHub API rate limit 打不上标签,烦请代打。

观察到的现象

同一个 maxLength(或旧拼法 max_length),在两条渲染路径上产出两种东西:

注册路径(field:textareapackages/fields/src/widgets/TextAreaField.tsx)

  • 原生 maxLength 硬截断;
  • 可见的 {n}/{max} 数字(aria-hidden);
  • aria-describedby 挂到输入框上的描述节点(聚焦时读一次上限);
  • 阈值门控 + debounce 的 aria-live="polite" 近上限提示。

(前三项 #3406/#3408 建起来,#3417 刚把它们同时铺到内联与全屏两个面。)

内建路径(packages/components/src/renderers/form/form.tsx,case 'textarea' 在 2097 行起)

  • 整个文件里没有任何字数计数代码maxLength 在这个文件里只出现两处,都与计数无关:
    • form.tsx:151 —— 'validation.maxLength': '{{field}} must be at most {{max}} characters'(校验文案);
    • form.tsx:1332 —— if (rules.maxLength) rules.maxLength = localizeRule(...)(把 react-hook-form 的校验规则本地化)。
  • 也就是说这条路上的上限是事后校验报错,而不是「边打边看得见的计数 + 聚焦时读得到的上限」。

结果:读屏用户在内建路径上拿不到任何上限信息(明眼用户也看不到计数),在注册路径上两者都有。

为什么值得记一笔

这正是本仓反复修过的那一类「一份声明、两条路径、两种行为」:#3272#3400/PR #3401#3402#3404 都是同一族,而且每次的裁定口径都是「一个表单级设定必须在两条路径上产出同一种行为」。所以看到同族的第四第五个实例,倾向于记下来而不是默认它没事。

没有核实的部分(请勿据此直接开工)

  • 原生截断到底到没到位:内建分支把 stripRendererOnlyProps(fieldProps) 展开到元素上,maxLength 是 textarea 的合法 DOM 属性,有可能顺着 rest 落到元素上拿到硬截断。我没有追 ObjectForm → FormField → fieldProps 这一段映射,确认不了 maxLength 是否真的在 fieldProps 里。若在,则差异只剩「有没有计数/可访问上限」;若不在,差异还包括「有没有硬截断」。
  • 实际命中面有多大:注册路径带 skipFallback: true,调了 registerAllFields() 的宿主走的是注册路径。内建分支主要服务于没注册 fields 的 embedded/standalone 宿主(本仓确实照顾这类宿主 —— useFieldTranslation 存在的理由就是「standalone/embedded 宿主不挂 I18nProvider」)。这类部署占比我判断不了。
  • 是否有意为之:内建分支可能就是刻意不做到功能对等的精简 fallback。若是,建议把这个取舍写进 form.tsx 的注释,让下一个扫到这里的人不用再查一遍。

起点

检索记录

按 objectstack#4949 的要求先搜后立:关键词 + 文件路径三轮检索(character count maxLength textarea built-in form renderer"form.tsx" maxLength counterbuilt-in branch parity registered widget textarea)在 open issue 里均无命中,故新立而非跟评。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions