fix(fields): 选项 widget 渲染宿主计算的 emptyHint,兜底文案走 i18n (#3231) - #3262
Merged
Conversation
…lated (#3231) `emptyHint` was declared on `FieldWidgetComponentProps`, computed by the form renderer for a dependency-gated option list (#2284) and transported — then lost three times over, so no registered widget could ever render it: 1. `isOptionField` compared the raw resolved type against `'select'` / `'radio'` / `'multiselect'` / `'checkboxes'`. Object-derived forms emit `mapFieldTypeToFormType`'s prefixed ids (`field:select`), which matched none of them, so the whole cascade block was skipped and no hint was computed at all for the normal console path. It now normalizes the `field:` prefix — the same normalization `stripRegisteredFieldProps` already applied below it. 2. `stripRegisteredFieldProps` removed the `emptyHint` key from what was left. It is now forwarded to the four cascade option types alongside `dependentValues` — an allow-list, because every other registered widget spreads leftover props onto a DOM node. 3. `SelectField` / `MultiSelectField` / `RadioField` / `CheckboxesField` each destructured it into `_emptyHint`, dropped it, and rendered a hardcoded English literal instead. The four inline copies of the empty state converge on one `OptionsEmptyState`: host hint when supplied, otherwise a TRANSLATED fallback (`fields.options.empty` / `fields.options.selectFirst`, added to all ten locale packs). The gate sentence is one i18n key shared with the renderer, so the two sides cannot word it differently. The built-in (unregistered) `select` branch, which already consumed `emptyHint`, is untouched. Co-Authored-By: Claude Opus 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 #3231
按 PM 在认领评论里的裁定走 A:widget 优先使用宿主传来的
emptyHint,自有文案退化为已翻译的兜底。复核时发现:断链不止一处
原单认为「宿主算出的提示被 widget 丢弃」。实测下来,
emptyHint在到达 widget 之前就已经断了两次 —— 只修 widget 那一处,线上用户看不到任何变化。三处全部修掉:isOptionField用未归一化的类型做比较(form.tsx)。对象派生的表单走
mapFieldTypeToFormType,发出的是带前缀的 id(field:select),一个都匹配不上 —— 于是整块 cascade 逻辑被跳过,console 里所有来自对象 schema 的选项字段压根没算过 hint。现改为CASCADE_OPTION_FIELD_TYPES.has(normalizeFieldType(resolvedType)),与下方stripRegisteredFieldProps已在用的归一化保持一致 —— 同一个文件里两处对「什么是 select」的判定必须一致。stripRegisteredFieldProps把emptyHint键剥掉了。所以即便是那些确实算出了 hint 的裸类型表单(type: 'radio'),送到已注册 widget 手里的也是空。现在与dependentValues一样,forward 给四个 cascade 选项类型。这里保持白名单而非「干脆别剥」:其余已注册 widget 会把剩余 props 铺到 DOM 节点上,未知的
emptyHint属性是一条 React warning —— 这也正是当初剥它的原因,剥本身是正当的。四个 widget 解构后丢弃。解构是对的(不解构会随
...props上 DOM),丢弃不是。i18n 不是附带项
四份内联的空状态收敛到一个
OptionsEmptyState:宿主给了就用宿主的,没给才用自己的翻译文案(fields.options.empty/fields.options.selectFirst,十个语言包全部补齐)。门控那句话与表单渲染器共用同一个 i18n key —— 渲染器插入字段 label,独立 widget 插入原始字段名,但句式只有一份,两边在同一 locale 下不可能说出两种话。用户可见的变化
I18nProvider时(独立/嵌入用法)仍回落到createSafeTranslation的英文默认值,不会退化成裸 key。未改动
form.tsx:1824的内建(未注册)select分支 —— 它本来就在消费emptyHint,是另一条独立活路径,按 scope fence 不做统一。测试
四个 widget 全部覆盖(它们是四份独立复制品,只测一个证明不了另外三个):
packages/fields/src/widgets/OptionsEmptyState.test.tsx—— 宿主给了 hint ⇒ 渲染宿主的(门控/未配置两种空态,以及 zh locale 下宿主仍然优先);没给 ⇒ 渲染已翻译的兜底(zh:请先选择country/暂无可选项),并断言旧的英文字面量不再出现。packages/fields/src/widgets/OptionsEmptyState.no-provider.test.tsx—— 无 provider 时的英文兜底。单独一个文件是必需的:挂载I18nProvider会通过initReactI18next把该实例装成 react-i18next 的全局默认,同一模块图里一旦挂过就再也观察不到「无 provider」状态。packages/components/src/renderers/form/__tests__/form-empty-hint-delivery.test.tsx—— 生产者侧:field:select/field:radio/field:multiselect/field:checkboxes(console 实际走的前缀 id)都收得到;裸radio/multiselect/checkboxes也收得到;门控解除后 hint 撤回;非选项类 widget 仍然收不到这个键(白名单方向的反向断言)。变异验证(逐条回退实现,证明测试确实会红):
emptyHint+ 恢复英文字面量stripRegisteredFieldProps不再 forwardisOptionField回到未归一化比较field:前缀用例 + 撤回用例)回归面:
packages/i18n+packages/fields674 passed;packages/components505 passed;packages/plugin-form+packages/plugin-grid733 passed;packages/app-shell2203 passed / 1 skipped。type-check三包全过,lint 0 error。🤖 Generated with Claude Code
https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa
Generated by Claude Code