fix(components): FilterBuilder 切换 field 时按新列类型重整 value —— 可转则转,不可转清空 (#4781) - #4876
Conversation
…4781) 切换一行的 field 时,PR #4779 只在算子家族变化时重整 value 的形状;标量到 标量没有形状问题,值被原样带过去。但字段的**类型**也换了,而值输入框正是 按新类型重绘的:`<input type="number">` 对非数字 value 一律显示为空。于是 text 列的 `equals "acme"` 指到 number 列后,输入框空白,行却仍带着 `"acme"` —— `foldFilterGroupToSpecRules` 原样持久化,实时网格拿 `amount equals "acme"` 去查。与 #4768 同一形状的「看不见的值」,只是发生在 value 而不是 operator 上。 按维护者 2026-08-16 裁定(方案 B:能转则转、不能转才清): - convertibility 一处定义(`convertScalarToFamily`),与 `reshapeFilterValue` 的 carry-if-possible 语义并列;判定为**干净、无歧义**的读数,比 `parseFloat` 严格 —— 后者会把 `"acme"` 读成 `0`,写出用户从未写过的筛选; - number:`"42"` → `42`,`"42abc"` / `"1,000"` / `Infinity` 清空; - boolean:只认 `"true"` / `"false"`(两端可往返),`1` / `"yes"` 是约定不是 读数,清空; - date/datetime/time:只收目标输入框本身能渲染的值,外加唯一一处不丢信息的 截断(无时区时间戳 → 其日期)。裸日期不会被补上午夜去凑 datetime 列: `equals 2024-03-05T00:00` 是一条看起来已填、实则几乎匹配不到东西的规则; - 新列装得下的值原样保留(两个 text 列之间、两个数值列之间),空行仍是空行。 `getInputType` 改为读同一张 family 表,使「值被转换成的类型」与「值被编辑的 输入框」不可能各说各话 —— 二者不一致正是本单的病灶。 Co-authored-by: Claude <noreply@anthropic.com>
`FILTER_INPUT_TYPE_BY_FAMILY` 上一版标了「@internal exported for tests」, 但测试并不导入它 —— 输入框类型是从渲染出来的 DOM 上钉的,那本来就是更硬的 钉法。`packages/components/src/custom/index.ts` 是 `export *`,于是它白白进了 包的公共 API(连 `dist/*.d.ts` 都带上了)。没有消费方要求的东西不进契约。 Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
|
【PM 验收 · ACCEPT】objectui 分片 PM(session_01GTRjn8xBqp75dk7kFupVRt)对 #4781 裁定(方案 B,评论 5306092839)的实施验收: 实物核验:4 文件对账相符(filter-builder.tsx +269、新钉 38 条 + 旧文件 2 fixture 再分诊、changeset);边界零越包、模型标识 0、releases 0。裁定逐条兑现:convertibility 一处定义( 反向验证:15 红 48 绿逐条命中;更可贵的是第一轮揪出自己的假钉(jsdom 对原地翻转 input type 不重跑值净化,原断言不可能为其被写下的理由失败)—— 测量写进注释、换成 inputShowsExactly 并补两向皆绿的症状钉。conversion matrix 全族覆盖(含 list/pair 形状与可达性如实标注)。CI 亲读:19 项全 completed(17 success + 2 skipped),零失败。 三个新 finding(#4873 假值当空、#4874 值域外显示空需裁定、#4875 三处 parseFloat||0 口径并存)记账合格,留分诊。 处置:undraft + auto-merge(SQUASH)。 Generated by Claude Code |
Fixes #4781
按维护者 2026-08-16 裁定(评论 5306092839,方案 B:能转则转、不能转才清)实施。
前提复核
按 origin/main
65e88e6逐条核对,issue 正文的读数全部成立,PR #4779 落地后此处代码未再动过:changeField在 operator 未变时走{ ...c, field: nextField },value 原样带过去;getInputType按新字段类型重绘,type="number"的输入框对非数字 value 显示空白;filter-builder-field-switch-operator.test.tsx里那条「a scalar value is carried through untouched」正是把这个行为钉住的(text 的"acme"切到 number 列后仍是"acme")—— 缺陷有现成的钉子,实测通过。改了什么
切换 field 现在是一次编辑,同时结算三件事:operator(#4768 已做)、value 的形状(
reshapeFilterValue,#4768 已做)、value 的类型(retypeFilterValue,本单新增)。convertibility 判定收在一处
convertScalarToFamily,与reshapeFilterValue并列,判定标准是干净、无歧义的读数:"42"→42、" 42 "→42、"-3.5"、"1e3""acme"、"42abc"、"1,000"、"Infinity"、true"true"/"False"→ 布尔(两端可往返)1、"yes"、"acme""2024-03-05"原样;"2024-03-05T14:30"→"2024-03-05""2024-02-31"、"05/03/2024"、带时区的...Z/...+08:00"2024-03-05T14:30[:ss]"原样"...T25:00"、带时区"14:30[:ss]"原样"29:71"、时间戳String()(42→"42"、true→"true")几处刻意的取舍,都写进了代码注释:
parseFloat严。parseFloat会把"acme"读成0、"42abc"读成42,那是用户从未写过的筛选,正是裁定要挡的。文件里另外三处键入路径仍是宽松写法,今天被数值输入框挡着碰不到 —— 单独立了观察类 issue FilterBuilder 三处数值输入用parseFloat(raw) || 0—— 半截读数与 NaN 都落成 0(今天被 number 输入框挡着) #4875,不在本 PR 扩围。equals 2024-03-05T00:00是一条看起来已填、实则几乎匹配不到东西的规则,比空输入框更坏。formatValue早就按T切开显示了)。带时区的一律清 —— 手工截断正是裁定说要拒绝的歧义。''不动:没填的行本来就是该族的空形,「清空」它是把无操作装成决定。顺带把
getInputType改为读同一张 family 表,而不是对同一批类型列表再写一遍分支 —— 「值被转成的类型」与「值被编辑的输入框」是同一个问题,答两遍正是二者能各说各话的原因,而不一致就是本单的病灶。行为不变,按类型逐条钉住。测试
新增
filter-builder-field-switch-value.test.tsx(38 条):UI 驱动真实下拉的转换/清空/不动三类,加上retypeFilterValue的逐族矩阵与逐形状(scalar / list / pair)矩阵。list 分支老实标注了可达性 —— 今天只有 select / lookup 两个桶提供in/notIn,二者同属文本族,所以没有任何字段切换能把 list 驱到数值列,该分支按「算子落到哪一族」写并直接钉在 helper 上。既有 fixture 的两处分诊(
filter-builder-field-switch-operator.test.tsx):Amount改为Title。它钉的是 pair → scalar 的塌缩,text 列同样触发算子重置(其桶里没有between)且装得下塌缩出来的日期串;留在数值列上会让这条钉子被一个不属于它的值判定盖住。Amount改为Stage。它原本钉的就是本单裁定要推翻的行为("acme"活着落到 number 列);钉子本身的命题(形状重置不该抹掉值)不变,故换到一个仍装得下该值的目标,number 目标的相反判定进了新测试文件。命令与结果:
反向验证
先提交,再把
changeField里的retypeFilterValue调用断开(退回value: reshaped)。预判先写死:15 条转红 —— 复现两条、it.each里四个目标装不下的切换、5 条转换钉、4 条清空钉;而 6 条「新列装得下就别动」的爆炸半径守卫、10 条 helper 矩阵、11 条输入框类型钉、以及整个 operator 测试文件保持绿。实跑15 failed | 48 passed,逐条对上。第一轮反向验证还揪出我自己测试里的一条假钉子:原先写的「输入框显示的与行存的一致」是
expect(input.value).toBe(String(row.value)),断开后它仍然绿 —— jsdom 对「新建即为数值输入框」会做值净化(显示空),但对原地翻转 type(字段切换正是这个动作)不会重跑净化,于是它把切换前那个浏览器本该抹掉的字符串原样交回来。这条钉子不可能因它被写下的理由而失败,等于没钉。已替换为直接断言「行里存的值是该输入框能原样显示的值」,并把这段测量写进注释;同时补了一条「数值列对非数字值显示空」的症状钉(两个方向都绿,作用是:值控件哪天不再是数值输入框,清空规则就该重新想,而不是静默守着空气)。变更集
.changeset/filter-builder-field-switch-retypes-value.md(@object-ui/components: patch)。顺带发现(均已立单,未在本 PR 修)
false与数字0显示为空,行却带着该值(实测确认,用户可直接触达)parseFloat(raw) || 0—— 半截读数与 NaN 都落成 0(今天被 number 输入框挡着) #4875:三处parseFloat(raw) || 0宽松读数(观察类,今天被数值输入框挡着)Generated by Claude Code