范围:本导览只管
03-components/06-form-editor这一类八个目录(form、form-list、upload、mentions、inline-edit、inplace、key-filter、sortable-list)之内的选型与踩坑路由。基座机制不在这里论证:注入链归A05,浮层与层级归A07与I02与I10,校验适配层归I12,响应式标准件归I13,输入基座归I04,图标归I15;单个控件的受控实现归C04与C05(编辑区里放xy-select一族的行为见C05-01)。
这一类的八个目录问的是同一个问题:一次用户输入之后,真值在谁手里、谁能否决、别人怎么知道。表单编排线把答案写成「model 由业务方持有,Form 层零状态」,失效模式因此集中在「字段没进表」(C06-00 §三)。
编排线的状态流向只有一条回路:FormItem 从不写值,它按 namePath 去读用户的 model,写入由控件自己完成,完成后敲一个通知口(C06-00 论点 3)。字段表是普通对象而不是响应式,登记的 key 是与名字无关的自增串、寻址靠线性过滤,所以「新挂载的字段立刻进入下一次校验」和「字段表无法被观察」是同一件事的两面(C06-00 论点 1、C06-01 论点 3)。这条回路把「接进表单」变成了每个控件各自的义务:通知口只有 onFieldChange() 一个,本类里 Upload 把它当成与 Form 的全部耦合面(C06-03 §四),Mentions 额外吃一条 id 回退与一次状态仲裁(C06-04 §五),KeyFilter 则公开声明自己与校验链路零接触——它是输入期的字符约束,不是提交期的值校验(C06-07 §一)。
编排线还有第二层:行有多少与行的先后。FormList 管前者,它拿一份本地数组、挂载即整只覆盖 model 上那个键,全文没有任何 watch,净语义是「组件永远赢」(C06-02 论点 1);SortableList 管后者,排序结果只落在数组下标上、回写责任由 update 载荷单方交给调用方(C06-08 论点 1),却用一条深 watch 维持第二个真相源,于是父级任何一次字段写入都可能把界面拉回旧顺序(C06-08 论点 2)。两个「列表」的容错方向恰好相反,这是本类最容易选错的一处。
「行内编辑」这个动作是编排线里最贴近使用者的切片。InlineEdit 与 Inplace 是同一台状态机的两份拷贝,逐行比对量化到 48 行源码相同,唯一被有意改掉的是谁持有值(C06-06 论点 1);所以「是不是两套」的准确答案是:两套实现、一套设计,且没有哪一份取代另一份——值交给宿主插槽才装得下整块表单,代价是同时放弃草稿、回滚与否决式提交(C06-06 §六、C06-06 坑 8)。InlineEdit 一侧明确不向 Form 登记字段,与 C04 那四个「能填不能验」的控件同族(C06-05 §一、C04-00 论点 5);Inplace 的登记情况本类没有成篇取证,若要按「同样不参与校验」来选型,得先确认它有没有出现在字段表里。
取材说明一句:本类里 C06-08 的第六、七两段还没成文,它对排序内核的可证内容都落在论点 1 到论点 8。下面的表格与索引只用它的论点号;要「坑 N」级别的条目,得等那两段落盘。
| 要解决的问题 | 选它 | 而不是另一个 | 判据 |
|---|---|---|---|
| 校验要能脱离组件树跑(命令式调用、片段复用) | useForm 路线 | xy-form 加 xy-form-item | 同一目录里躺着两套同名不同实现的校验编排,Form 从不消费 useForm,两者连失败形状都不一致;useForm 的规则键是字符串点路径,与 Form 的数组路径不兼容,且不吃全局 validateMessages(C06-01 论点 1、C06-01 论点 2、C06-00 坑 7) |
| 需要 label、栅格、错误回显这些排版契约 | xy-form 加 xy-form-item | useForm 路线 | 回显状态走「reactive 源加二次 reactive 合并」的双跳下发给控件,批量操作面也挂在字段表上,useForm 这条路上两者都没有(C06-00 论点 5、C06-01 论点 1) |
| 一行文本、就地换成编辑器、退出即落值 | xy-inline-edit | xy-inplace | 它自己渲染输入控件并持有草稿,进入编辑的初值恒取 modelValue;两份实现的状态机逐字相同,分野只在值归谁(C06-05 论点 3、C06-06 论点 1) |
| 编辑区要放多控件或整块表单 | xy-inplace 加 #content 插槽 | xy-inline-edit | 值所有权交给宿主才装得下自由插槽,代价是没有草稿、没有回滚,进入前的否决也只认同步返回值(C06-06 坑 8、C06-06 坑 5) |
| 多行数据的增行与删行 | xy-form-list | xy-sortable-list | 行的 key 与行内 namePath 是两套索引、前缀拼接完全外置给调用方;它的 move 只换数据与 key、不重注册,已经会让错误序与视觉行序分叉,更没有指针拖拽(C06-02 论点 3、C06-02 坑 8) |
| 只改顺序,顺序要交回调用方持久化 | xy-sortable-list | xy-form-list | 排序只落在数组下标、由 update 事件单方交出载荷,且它靠深 watch 维持第二真相源,与 FormList「组件永远赢」的单向语义方向相反(C06-08 论点 1、C06-08 论点 2) |
| 输入期限制能敲进去的字符 | useKeyFilter hook,或输入框自带的 preset 通道 | xy-key-filter 组件壳 | 组件壳只做一件事:把插槽里第一个输入元素找出来再绑六个 handler,与控件内置通道命中同一节点时两侧都会绑上;字符串 pattern 也只有 hook 侧类型合法(C06-07 论点 5、C06-07 坑 1、C06-07 坑 7) |
| 提交期判定整个值合不合法 | FormItem 的 rules | KeyFilter 与任何输入期拦截 | 过滤在校验链路里完全隐形,值合法性只能由规则自证;校验走「一条规则一个实例」的接缝,trigger 过滤发生在它之前(C06-07 §一、C06-00 论点 4) |
| 上传触发区要不要接受拖拽与目录 | xy-upload-dragger 或 type="drag" | 给 xy-upload 传 height 想要高度 | height 只在 Dragger 一侧被读取;拖拽分支不看 multiple 也没有拒绝回调,禁用态还照给 hover 高亮(C06-03 §五、C06-03 坑 4、C06-03 坑 10) |
读表提醒:前两行是「要不要组件树」,第 3、4 行是「值归谁」,第 5、6 行是「行数还是顺序」,第 7、8 行是「约束发生在哪一期」,第 9 行是「触发区形态」。同一行里的两个候选是本类内可互换的东西;跨类替换(例如拿 C04 的控件顶替行内编辑)不在本表范围。
一条跨类硬约束(先于任何选型):本类的
C06-08SortableList 依赖第三方运行时包@dnd-kit/vue@0.5.0,其 peer 是vue: ^3.5.0(两种方法一致:pnpm-lock.yaml:781与装到盘上的node_modules/@dnd-kit/vue/package.json:72),主库的 peer 下限已在 2026-09-29 修复轮同步抬到vue: >=3.5.0(packages/xiaoye-ui/package.json:44),冲突不再发生——判「这个组件能不能用」看的是这条下限:整库要求 Vue 3.5+,AGENTS.md的技术栈行已同步。
A. 配了没反应——静默失效族(本类最大的一族,四篇各证一层,族谱见 §四)
disabled 打不进「长得像控件但没接上下文」的东西 → C06-05 坑 2(行内编辑的三重断裂)、C06-06 坑 3(断链叠加默认值中毒)、C06-08 论点 5(排序列表的双重断链);控件侧同族判据见共则 §3 判据 ①。C06-06 坑 4(样式函数内部把用户值再覆盖一次)、C06-02 坑 6(列表整条样式链空转)。getPopupContainer 对本组件无效 → C06-04 坑 6,基座口径见 A07 与 I10。size 版本见 C11-01 坑 2。C06-01 坑 6(规则浅 watch,原地改不触发)、C06-01 坑 5(嵌套文案层不能全局一套加局部补一条)。class、id、原生事件不落地 → C06-02 坑 6。B. 值与态错帧——编辑中途被冲掉、退出不了或回不去
C06-05 坑 1;原地编辑侧同样没有失焦收敛路径 → C06-06 坑 8。C06-05 坑 3;editable 与 disabled 同时为真时外部绑定与内部态永久错帧 → C06-05 坑 5。C06-06 坑 6,缺标准件这条口径见 I13。C06-05 坑 4、C06-06 坑 5。initial-value 毫无反应 → C06-02 坑 1、C06-02 坑 2。update,之后任何一次字段写入都把顺序回滚回去 → C06-08 论点 2。C. 字段表看不见你——校验不跑、重置不落
rules 不写 name 的字段永远不校验,且早退返回的是 undefined 而不是 Promise → C06-00 坑 2。name 路径写错不报路径错,而是静默按空值校验,重置时还会在 model 上新建一个拼错的键 → C06-00 坑 1。C06-01 坑 1。C06-00 坑 3、C06-02 坑 7。C04-00 坑 8;本类里明确不接这条链的边界声明见 C06-05 §一、C06-07 §一。C06-01 坑 2(2026-09-30 已修:实测 @finish 1 次 + @finishFailed 1 次,payload 带 outOfDate: true;修法是在 handleSubmit 的 .catch 吞掉被取代的那一轮,回归 form/__tests__/form.test.tsx:187)。同一坑初稿另写有「会抛 TypeError/未处理 rejection」,探针实跑 0 条,已就地撤回,只在 :206 留下一处潜在的无守卫读取。D. 顺序、身份与全局事件抢占
scrollToFirstError 滚到的不是最上面那个错误行 → C06-02 坑 8;批量结果的保序机制归 C06-00 论点 6。v-for 用下标做 key,删中间行后输入内容与红字留在旧槽位 → C06-02 坑 3;两个列表的行拼进同一个 v-for 会撞 list_0 → C06-02 坑 9。name 的表单产生完全相同的字段 id,滚动与读屏都命中错的那个 → C06-01 坑 3。C06-06 坑 2。C05-01 一族),点选项就把自己判成「点击外部」而退出编辑 → C06-06 坑 1。C06-08 论点 8。C06-03 的 emit('drop') + callEvent(props.onDrop) 与 C06-04 的五处 props.onX?.(...) 直调(focus/blur/select/change/pressenter),2026-09-29 已全改成只留 emit:修前拖放一次 @drop、聚焦一次 @focus 各 2 次,修后各 1 次,回归见 packages/xiaoye-ui/src/_util/__tests__/event-dispatch-count.test.tsx。两处别误判:upload/UploadList/index.tsx 里的 callEvent(props.onPreview/onDownload/onRemove) 不是双投(该组件没有 emits 声明,props 通道是唯一出口,实测点一次文件名只 1 次);mentions 的 blur 在厂商层被 setTimeout(…, 100) 延后,计数用例必须先等约 160ms 再断言,否则会误判成 0 次。E. 输入期、上传受理与测试面
C06-04 坑 12;粘贴前缀不弹面板 → C06-04 坑 2;过滤却在合成期照拦 → C06-07 坑 3;@pressenter 从不触发 → C06-04 坑 5,基座侧事实见 I04。maxCount 只裁列表不裁请求 → C06-03 坑 3;裸扩展名写法等于不过滤 → C06-03 坑 5;自定义请求缺 abort 时删除只是假装成功 → C06-03 坑 6;失败项的百分比恒为零 → C06-03 坑 7。C06-03 坑 1;列表那条声称「全局唯一」的用例只挂了一个列表 → C06-02 坑 9;原地编辑有一条断言与用例名无关 → C06-06 坑 9。C06-01 论点 3(字段表是普通对象,不可观察)与 C06-01 §六(类型面上留着一整套 store 协议、运行时无人消费),再看 C06-02 论点 4(注册用实例身份、收集用当前位置),否则会撞上「加了状态但批量操作看不见」。C06-00 论点 3 给出读值链与写入责任的划分,C04-00 论点 5 给出通知口的实际接入统计,C06-03 §四 是本类里唯一一条完整接线样本。C06-00 论点 6(保序与判过期)、C06-01 论点 4(三种 reject 形状叠加全局 DOM id)、C06-02 论点 5(三层索引没有一层以行号为键)。这三篇要一起读,缺一个就会把「顺序」问题误判成「排序」问题。C06-06 论点 1(相同行的量化与唯一分歧)、C06-05 论点 2(失焦不是退出路径)、C06-06 论点 6(受控与非受控各做了一半),并顺手对齐 C06-06 坑 2 里那两处已量化的漂移。C06-06 坑 1、C06-04 坑 6,浮层基座口径见 A07 与 I02。C06-02 论点 1 与 C06-02 坑 4(行数据与 key 的三处手工 splice),再看 C06-00 论点 8 判断会不会牵动嵌套 FormItem 共享的那张字段表。C06-08 论点 7(单项与容器两个接口面、键盘路径绕开内核)、C06-08 论点 2(深 watch 的回滚)、C06-02 坑 8(重排与错误序分叉)。A02,要重跑 pnpm gen:entries 并核对包导出快照。