Slate v2 滚动与选区可见性治理:从 stale selection 修复到 caret visibility 请求管线
Slate v2 滚动与选区可见性治理从 stale selection 修复到 caret visibility 请求管线【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate导读本文基于 docs/plans/2026-05-11-slate-v2-scroll-selection-visibility-ralplan.md 展开深入剖析 Plate 项目中 Slate v2 编辑器在嵌套滚动容器下滚动后点击输入却回跳到旧光标位置这一经典缺陷的根因、生态参照与完整治理方案。读完你将掌握为什么浏览器可见选区必须在原生输入前导入模型、为什么滚动必须作为提交后的可见性请求而非事件副作用、以及如何用自研 rect walker 取代scroll-into-view-if-needed的方法覆盖从而构建可证明、可回归的 caret 可见性管线。一、问题全貌滚动后输入跟随旧选区远不止自动滚动坏了该规划文档ralplan以一份用户录屏.tmp/issue-scroll.mp4为起点。从录像表象看编辑器的行为像autoscroll 失效用户滚动到文档下方、点击一个较低段落后开始输入视口却会往回滚到旧的光标位置附近。但文档给出的结论更准确、也更严重The video report looks like autoscroll, but the worse bug is stale selection: after the user scrolls and clicks a lower paragraph, typing can still follow an older model selection and scroll back to that older caret.真正的缺陷是 stale selection过期模型选区自动滚动只是它的外在表现。用户点击后浏览器 DOM 选区已经移动到了下方段落但 Slate 模型中的选区仍是旧位置随后的输入事件让 Slate 基于过期模型选区插入文本于是文本落到了旧段落、视口也随之回到旧光标处。同类问题的另一个变体记录在 docs/plans/2026-05-11-scroll-into-view-repeat-regression.md在scroll-into-view示例路由上第一次滚动后输入能自动滚回光标第二次滚动离开再输入却不再自动滚动。调查发现其局部根因是默认滚动助手临时把leafEl.getBoundingClientRect覆盖为 range 的测量函数清理时却赋值为undefined导致该 DOM 节点原型上的方法被遮蔽后续滚动尝试因typeof leafEl.getBoundingClientRect ! function而提前返回。这个方法污染问题本身在 docs/solutions/logic-errors/2026-05-11-slate-react-scroll-range-measurement-must-restore-dom-methods.md 中已被部分修复但文档明确指出它只修了方法清理没有触及 stale selection 这个根本失败点。规划文档由此确立核心修复顺序The accepted plan is selection import first, scroll request second, post-update visibility third.即先导入选区 → 再发起滚动请求 → 最后在更新后做可见性处理。二、意图与边界把问题精确框定规划文档对本次工作的意图、成果、范围和非目标做了严格界定维度内容intent让 Slate v2 的 caret 可见性在嵌套滚动父级、浏览器自有选区移动、beforeinput、IME composition 与应用自定义滚动下boringly correct无惊喜地正确outcome滚动并点击后在可见光标处输入随后只滚动让该光标可见所需的最小父级链in scopeSlate React 选区导入、beforeinput 选区新鲜度、滚动请求生命周期、caret 几何测量、嵌套滚动父级、测试/浏览器证明non-goals应用级 sticky 工具栏策略、Plate 产品命令、无精确复现的上游 issue 自动关闭、由桌面模拟得出的原始移动端结论decision boundary后续ralph执行可能重写 Slate React 内部运行时并新增小型无观点unopinionated滚动策略 APIraw Slate 不得依赖 Plate 产品行为关键约束是最后一条滚动策略 API 必须无观点Plate 可以把 sticky 工具栏这类产品概念作为 margin 输入但 raw Slate 不应知道工具栏为何物。三、决策原则与核心方案3.1 四条决策原则编辑器内当前 DOM 选区对原生用户输入具有权威性除非存在显式的模型自有model-owned操作正在生效滚动是选区之后、更新之后的可见性请求不是每次选区检查的副作用几何数据属于测量得到的矩形measured rectangles不属于对临时 DOM 方法的一次性覆盖应用自定义应是策略输入policy input而非运行时所有权runtime ownership。3.2 选定的三种核心形状新增选区新鲜度阶段selection freshness phase在原生文本输入和模型自有回退之前执行。对于编辑器内的beforeinput除非事件属于 internal-control、程序化修复/导出、活跃 composition 或已知模型自有命令否则导入当前 DOM 选区。新增内部 caret 可见性请求队列携带 reason、margin、threshold、scroll mode 与 skip/force 策略。用 Slate 自有的矩形父级遍历器rect walker替换默认滚动助手保留scrollSelectionIntoView作为公共逃生舱但喂给它正确的更新后 DOM range。3.3 被否决的替代方案及理由备选方案否决理由保留scroll-into-view-if-needed只修清理逻辑过于局部既漏掉 stale selection也漏掉嵌套策略缺口永远信任 beforeinput 的 target rangeLexical 与 Slate 的 bug 均证明在 model-owned 或 internal-control 路径下 target range 可能是过期的在事件处理器内立即滚动在最差时机强制 layout可能暴露提交前pre-commit位置暴露大型 ProseMirror 式插件 API对 raw Slate 过重四、生态对比六大编辑器的滚动与选区机制规划文档对六个主流编辑器系统做了源码级调研结论表格如下系统证据位置规划期外部仓库源码机制Slate 借鉴目标结论ProseMirrorprosemirror-view/src/index.ts:178、domcoords.ts:32、state/src/transaction.ts:204transaction 携带滚动意图view 在更新后通过父级链滚动选区矩形非滚动更新保留 anchors增加提交/请求作用域的滚动意图与自定义 rect walkeragreeLexicalLexicalEvents.ts:760、LexicalSelection.ts:2637、LexicalUtils.ts:1399安全时 beforeinput 应用 DOM target range选区对账为原生事件选择 DOM 选区滚动接受 rect在文本插入前导入 DOM 选区除非有显式 model-owned 理由partialCodeMirrorcodemirror/view的index.d.ts:861/:1125/:1365滚动是 transaction effect测量批量执行滚动 margin 建模被遮挡区域调度读/写几何增加 margin 策略agreeTiptapcommands/scrollIntoView.ts:15、commands/focus.ts:36产品命令委托给 ProseMirror transaction 滚动focus 可选择性退出保留简单的应用侧自定义边界partialMilkdowntoolkit/position/index.ts:53/:74UI 定位用 ProseMirrorcoordsAtPos命令路径用tr.scrollIntoView()视为 ProseMirror 的佐证而非新引擎模式agreeObsidianEditor/scrollIntoView.md:16产品 API 暴露scrollIntoView(range, center?)raw Slate 保持底层Plate 可暴露产品命令diverge调研结论被编译进 docs/research/sources/editor-architecture/scroll-selection-visibility-runtime.md。值得强调的是规划从这些系统借鉴的是生命周期纪律intent → post-update rect → parent walk → anchor preservation而非照搬任何一家的 API。五、当前源码证据盘点规划前的现场规划文档记录了当时 live Slate v2工作副本.tmp/slate-v2的现场证据映射到当前仓库可以交叉印证公共 API 槽位当前仓库中Editable的scrollSelectionIntoView?: (editor: Editor, domRange: DOMRange) void定义在 packages/core/src/lib/types/EditableProps.ts旧默认实现当前仓库 packages/slate/src/internal/editor-extension/scrollIntoView.ts 展示了文档批评的实现模式——通过requestAnimationFrame延迟把domRange.getBoundingClientRect绑定到 leaf 元素上再调用scroll-into-view-if-needed然后用setTimeout清理其选项类型ScrollIntoViewOptions定义在 packages/slate/src/interfaces/scroll.ts测试见 packages/slate/src/internal/editor-extension/scrollIntoView.spec.ts规划期记录.tmp/slate-v2工作副本路径非当前仓库文件editable.tsx:420已有带固定可见性 margin 的scrollRectIntoViewIfNeeded父级遍历器editable.tsx:513的defaultScrollSelectionIntoView测量折叠 focus range 并回退到 leaf rectruntime-before-input-events.ts:239仍调用syncSelectionForBeforeInputselection-controller.ts:291的模型选区偏好仍是布尔 source而非带理由的新鲜度令牌selection-reconciler.ts:564仍以preferModelSelectionForInput门控insertText的 DOM 选区导入playwright/integration/examples/scroll-into-view.test.ts已存在但用程序化方式选中末尾块site/examples/ts/scroll-into-view.tsx:20构造了嵌套滚动父级复现面。先例学习prior learning也指向同一类问题docs/solutions/ui-bugs/2026-04-22-slate-react-keydown-must-import-dom-selection-before-model-owned-navigation.md 记录了同一类教训浏览器可见的选区移动必须在 model-owned 导航之前导入。该方案给出核心代码模式if (!isInteractiveInternalTarget(editor, event.target)) { syncEditorSelectionFromDOM({ editor, preferModelSelectionForInputRef, }) }docs/solutions/logic-errors/2026-05-11-slate-react-scroll-range-measurement-must-restore-dom-methods.md 已降级为仅部分有效因为它只修复方法清理未修复根因docs/solutions/performance-issues/2026-04-11-shell-promotion-must-move-selection-into-the-promoted-island-or-it-is-just-cosmetic.md 强化了规则可见的编辑位置必须成为模型选区。六、目标架构设计6.1 公共 API保持兼容不添新 prop第一个执行切片不新增公共scrollPolicyprop保留Editable的scrollSelectionIntoView?: (editor, domRange) void以维持兼容。在默认助手背后放置内部策略对象margin数字或每边矩形threshold数字或每边矩形mode默认nearestscrollParents默认 DOM 父级遍历skip reasonskip-scroll提交标签、composition 修复、应用覆盖。不直接暴露 ProseMirror transaction 或 CodeMirror effect只有当 issue#4995的自定义滚动行有了浏览器证明、证实现有回调确实表达不了应用需求时才把策略对象提升为公共 API。6.2 内部运行时契约syncSelectionForBeforeInput对编辑器内的insertText在 model-owned 回退之前导入当前 DOM 选区——除非模型偏好由一个当前显式来源背书internal-control、程序化导出、修复setEditableModelSelectionPreference携带 reason/timestamp而非只传布尔避免过期偏好压制后续用户选区在选区运行时上新增requestCaretVisibility路径输入为range、reason、margin、threshold、mode、phase在DOM 选区导出或输入后修复之后测量目标是一个 DOMRange rect仅当 range 产生零值或无效 rect 时才回退到 leaf/node rect用将Rect穿过滚动父级的函数取代临时方法覆盖当状态更新改变了 DOM 但未请求 caret reveal 时保留滚动锚点scroll anchors第一个实现保持在selection-controller/selection-reconciler内部现有公共回调仍收到正确的更新后 DOM range。6.3 Plate 与 slate-yjs 迁移骨架Plate 可通过滚动策略传入 sticky 工具栏/内容 chrome 的 marginraw Slate 不应知道 Plate 工具栏名slate-yjs 的远程提交默认对远程选区变更使用skip-scroll除非本地用户显式跟随远程光标本地提交保持当前用户 caret 可见性远程提交保留滚动锚点。七、关联问题账目无证明不声明该规划阶段不允许产生任何新的已修复 issue声明相关 issue 同步来自缓存台账而非实时抓取Issue定位#5826最接近的匹配refocus/selection-scroll 家族不声明长编辑器 refocus 自动滚动的精确闭环作为第一浏览器行的首个目标#4995滚动自定义压力仅作为相关项当前契约下不是直接 red-test 目标#5639移动端/RTL 重复滚动压力在真实 iPhone/RTL 证明之前仅作矩阵未来证明#5291Android 高块内光标跳动长表单证明路线积压不得以桌面 Chromium 声明#5524软换行间垂直导航除非 DOM 桥证明另有指向精确闭环路由到核心 caret/导航#5806自定义内联手势选区不声明精确的拖拽/滑动选区浏览器证明#5711DOM point 解析崩溃家族精确 iOS 闭环需要匹配的浏览器证明#4961插入后焦点相邻的 React focus/input 运行时所有权仍需精确的 insert-and-focus 重放规划阶段不修改 docs/slate-v2/references/pr-description.md 与 docs/slate-v2/ledgers/fork-issue-dossier.md 等台账。八、回归证明矩阵行测试所有者行为输入前的 stale click 选区收紧scroll-into-view.test.ts现有行滚动嵌套编辑器、点击下方段落、输入断言文本落在被点段落且 caret 保持可见重复 scroll/click/type同一浏览器文件把上报周期重复三次断言滚动不追逐旧选区beforeinput 前导入 DOM 选区selection-controller-contract.test.ts或新增聚焦测试模型偏好过期时insertText导入当前编辑器内 DOM 选区internal-control 撤销/文本输入现有 editable-void/read-only 行internal-control 偏好仍保护原生控件零矩形回退editable-behavior.test.tsx空行/换行的 caret 仍用回退 rect 揭示且不修改元素方法嵌套父级滚动rendering-strategy-and-scroll.test.tsx内外滚动容器都收到最小增量滚动 margin新增单元行sticky chrome margin 收缩可见矩形composition现有 IME 行 按需新增 skip-scroll 行composition 修复不导入/不滚动过期的非组合选区九、浏览器压力测试策略使用真实/examples/scroll-into-view路由而非仅单元级合成 DOM使用按文本块矩形点击的浏览器助手同时校验模型文本与可见 DOM 选区增加失败断言旧段落收到了输入字符——这正是视频暴露的回归移动端/RTL 跟进行必须在桌面行为稳定后加入不得以桌面 Chromium 声明#5639。十、性能与 React 审查约束Vercel React几何应在事件/请求调度下进行使用 refs/请求队列而非 React state 抖动performance-oracle父级遍历复杂度为 O(滚动祖先深度)而非 O(文档大小)无逐块监听器performance skill交互预算为嵌套滚动下输入字符 p95内存预算为每个编辑器一个待处理可见性请求性能通道细节重复单元是滚动父级而非块/leaf预算为每次请求揭示一次 range→rect 测量 一次祖先遍历队列涵盖普通嵌套编辑器、长文档、高块移动端积压与病态自定义内联/RTL 设备行退化契约桌面原生输入无退化移动/RTL 与远程光标跟随在设备证明前不声明RUM 缺口raw Slate 无生产仪表盘浏览器回归行是当前证明机制tdd先建一个红色浏览器行再写最小的选区导入单元然后写滚动 rect walkershadcn/react-useeffect跳过——本阶段无组件 UI 或 effect API 决策。十一、高风险预检Pre-Mortem破坏 internal-control用户在嵌入式输入框输入时导入了 DOM 选区。对策显式 internal-control 理由存活并有聚焦的 editable-void 证明测量引发滚动抖动在 beforeinput 中测量。对策将可见性请求排队在 DOM 导出/修复后再测量过度拟合桌面 Chromium。对策移动/IME/RTL 的 issue 声明保持相关状态直到原始设备或受限移动证明存在。十二、维护者异议台账异议回答结论这只是示例的 bug示例只是复现外壳stale selection 路径在 slate-react 的 beforeinput/import 逻辑里keep现有scrollSelectionIntoView已可自定义它自定义的是最终滚动回调不是选区新鲜度或正确的测量目标keep自定义 rect walker 是过度设计ProseMirror、Lexical、CodeMirror 都自有滚动数学/生命周期通过方法变更委托已产生重复 bugkeep不要偷 ProseMirror只借鉴生命周期纪律intent、post-update rect、parent walk、anchor 保留keep新策略 prop 是 API 膨胀正确。第一个切片保持策略内部化、保留现有回调公共提升等待精确自定义证明revise输入前导入 DOM 选区会破坏 internal-controlinternal-control、composition、shell、programmatic-export 理由必须保持模型/原生自有守护这是浏览器复现后的第一个单元测试行keep一个浏览器行无法关闭移动/RTL 滚动问题正确。#5639与#5291在原始设备证明前保持相关/积压keep滚动遍历器会在每次按键强制 layout它只在显式可见性请求时运行而非每次状态变更预算为每次揭示一次 range rect 一次祖先遍历keep十三、附加滚动算法遍历五条执行契约13.1 选区权威 Pass结论布尔preferModelSelectionForInput对这个 bug 类别太弱需要带理由的新鲜度令牌。算法契约编辑器内的原生 pointer/click/selectionchange 记录一个带事件帧与 source 的dom-current选区令牌model-owned 路径记录具体理由internal-control、composition、programmatic-repair、shell-export或model-transform原生insertText前当前编辑器内 DOM 选区胜出——除非仍生效的 model-owned 理由显式拥有该帧过期的模型偏好会在编辑器内出现更新的用户 DOM 选区后过期外部或不可选中的 DOM 选区 fail-closed不导入、不崩溃、不声明滚动。参考实现方向来自已落地解决方案 docs/solutions/logic-errors/2026-05-11-slate-react-scroll-range-measurement-must-restore-dom-methods.mdexport const isEditableModelSelectionPreferredForInput ({ inputController, inputType, }: { inputController: EditableInputController inputType: string }) { if (!isEditableModelSelectionPreferred(inputController)) return false if (inputType ! insertText) return true const preference inputController.state.modelSelectionPreference if (!preference?.preferModelSelection) return false return ( inputController.state.isComposing || preference.reason browser-handle || preference.reason composition || preference.reason internal-control || preference.reason model-command || preference.reason shell-backed ) }执行所有者规划期.tmp/slate-v2路径editable/input-state.ts、editable/selection-controller.ts、editable/selection-reconciler.ts、editable/runtime-before-input-events.ts。13.2 滚动请求生命周期 Pass结论滚动必须是附加到已提交选区可见性上的请求而非每次选区检查的副作用。算法契约用户输入或选区导出每编辑器帧至多产生一个待处理可见性请求请求存储range、reason、phase、mode、margin、threshold、skip/force测量发生在 DOM 选区导出或输入后修复之后而非beforeinput中途非揭示更新保留滚动锚点不追逐模型选区远程/协作文档选区变更默认skip-scroll除非本地用户跟随该远程光标。执行所有者editable/selection-controller.ts、editable/dom-repair-queue.ts、editable/runtime-repair-engine.ts、editable/runtime-root-engine.ts。13.3 几何与父级遍历 Pass结论rect walker 方向正确但执行时应显式化策略并通过嵌套父级证明。算法契约测量有效更新后选区的折叠 focus range零值或不可用 range rect 是回退信号不是 monkeypatch DOM 方法的许可仅当 range rect 不可用时回退到最近的 Slate 文本/leaf rect从内到外遍历可滚动祖先最后处理 viewport每次祖先滚动后按实际滚动增量偏移目标 rect再评估下一个父级默认按 nearest-edge 增量揭示而非居中margin 与 threshold 按边收缩可见矩形Plate 之后可喂入 sticky chrome marginraw Slate 无需知道工具栏概念水平与 RTL 滚动在 rect walker 范围内但精确的波斯语/iPhone 闭环在设备证明前不声明。已落地的调用形状同样来自上述解决方案文档scrollRectIntoViewIfNeeded({ rect: toScrollRect(targetRect), startElement: leafEl, })修复路径应揭示当前 DOM 选区或物化后的更新后折叠 rangeconst domRange domNode.ownerDocument.createRange() domRange.setStart(domNode, domOffset) domRange.setEnd(domNode, domOffset) domSelection.setBaseAndExtent(domNode, domOffset, domNode, domOffset) scrollSelectionIntoView(editor, domRange)执行所有者components/editable.tsx、test/rendering-strategy-and-scroll.test.tsx、test/editable-behavior.test.tsx。13.4 浏览器证明 Pass结论现有浏览器行有用但不够——程序化选区可绕过视频中的精确 stale click 路径。必建第一行打开/examples/scroll-into-view通过真实滚动容器滚动到下方内容按 DOM 矩形点击可见的下方段落而非设置 Slate 选区输入再次滚动离开再次点击/输入断言输入文本落在被点段落断言旧段落未收到第二次文本断言可见 caret 在最近滚动父级内。执行所有者playwright/integration/examples/scroll-into-view.test.ts、site/examples/ts/scroll-into-view.tsx。13.5 API 裁剪 Pass 与失败模式 Pass保留scrollSelectionIntoView?: (editor, domRange) void默认助手背后的内部策略对象#4995仅作相关压力。裁剪第一个切片中的公共scrollPolicypropraw Slate React 中的 ProseMirror 式 transaction APIraw Slate 中的 Plate sticky-toolbar 概念。只有聚焦的#4995浏览器行证明现有回调无法表达应用需求时才提升公共策略。执行不得回归的失败模式清单嵌入式/原生 internal-control、活跃 composition 与 IME 修复、shadow-root 选区查找、换行上的零矩形折叠 caret、离屏编辑器焦点修复、远程/协作选区保留、自定义内联手势选区、移动/RTL 声明边界。匹配的证明行存在之前不允许任何已修复 issue声明。十四、实现阶段与执行所有者映射Ralplan 只拥有这张所有者地图ralph拥有实现、验证与任何源码/测试编辑收紧/examples/scroll-into-view上针对.tmp/issue-scroll.mp4行为的浏览器行选区新鲜度修复带理由的模型选区偏好 文本插入前 DOM 导入滚动助手重写rect walker、margins、thresholds、无方法变更非揭示更新的滚动锚点保留证明结果后的聚焦 issue 账目更新。第一浏览器行细化scroll-into-view.test.ts使用scroll-into-view.tsx在滚动 → 点击下方段落 → 输入 → 滚动离开 → 点击下方段落 → 再输入之后同时断言浏览器 DOM 文本与 Slate handle/model 文本首个 issue 目标是#5826相关证明行在实现后通过之前不做 fixed 声明。第一个运行时切片editable/input-state.ts用 reason/source 新鲜度数据替换过期的纯布尔模型偏好editable/selection-controller.ts更新setEditableModelSelectionPreference并暴露单个过期偏好检查的内部助手editable/selection-reconciler.ts模型偏好过期时对insertText导入当前编辑器内 DOM 选区同时保留 internal-control/composition/programmatic 守护editable/runtime-before-input-events.ts传递新鲜度感知偏好而非原始布尔。第二个运行时切片components/editable.tsx用基于 rect 的默认助手替换临时getBoundingClientRect变更editable/selection-controller.ts在 DOM 选区导出后排队 caret 可见性请求test/editable-behavior.test.tsx保留两滚动回归行新增零矩形回退证明test/rendering-strategy-and-scroll.test.tsx证明嵌套父级增量与内部策略 marginstest/selection-controller-contract.test.ts证明过期模型偏好随显式用户 DOM 选区而过期。裁剪线在聚焦的#4995浏览器行证明现有回调不够用之前不编辑EditableTextBlocksProps不新增公共 API。十五、快速驱动门禁规划文档给出的验证命令基于规划期工作副本在当前仓库可用bun在对应包目录执行等价测试bun test ./packages/slate-react/test/selection-controller-contract.test.ts ./packages/slate-react/test/selection-runtime-contract.test.ts ./packages/slate-react/test/editable-behavior.test.tsx ./packages/slate-react/test/rendering-strategy-and-scroll.test.tsx bun --filter slate-react typecheck bun lint:fix bun test:integration-local --grep scroll into view规划工件侧的校验plate 仓库pnpm lint:fix bun run completion-check十六、评分卡与通过状态台账规划文档对六个维度的自评满分 1.0维度得分依据React 19.2 运行时性能0.94请求队列 O(父级深度) 目标明确读/写时机无逐块监听器Slate-close 无观点 DX0.94保留scrollSelectionIntoView首个策略保持内部Plate/slate-yjs 迁移骨架0.88margins 与skip-scroll已规格化远程跟随是后续证明行回归证明测试0.95用户路径浏览器行、stale 选区单元、rect walker、零矩形与失败模式行均已点名研究证据完整性0.95ProseMirror、Lexical、CodeMirror、Tiptap、Milkdown、Obsidian 本地证据已编译并同步 issue组合性/极简0.94公共 API 膨胀从首个切片裁剪内部策略避免 prop 抖动总分0.94。通过状态台账逐项记录当前状态与生态源码遍历、关联 issue 同步、维护者异议闭环、执行计划闭环、附加滚动算法遍历均已 completeralph执行闭环也已 complete手动滚动离开浏览器行、带理由的模型选区偏好、DOM 修复 caret 可见性、rect 父级遍历器、依赖裁剪、聚焦的单元/类型/lint/build 证明并确认scroll-into-view-if-needed已移除、公共 API 未变、issue 声明保持保守。十七、完成门禁Slate Ralplan 闭环得分 0.92且无维度低于0.85issue 同步完整且无虚假 fixed 声明执行所有者地图点名精确的实现与测试文件移交前重读 live Slate v2 源码Ralplan 阶段不做任何实现编辑。Ralph 执行门禁首个用户路径浏览器行在行为修复前存在且失败或现有行被收紧为直接证明用户路径Slate v2 聚焦的单元/浏览器门禁从.tmp/slate-v2通过默认 caret 可见性路径中不再残留scroll-into-view-if-needed方法变更依赖最终移交把 fixed、improved、related 与 unclaimed 的 issue 行分离。结语一以贯之的纪律这条滚动选区可见性的治理路径本质上是三条纪律的组合浏览器可见选区优先selection authority、滚动是提交后的最小揭示请求scroll request lifecycle、几何交给测量与遍历而非方法覆盖geometry and parent walk。它既是针对具体录屏 bug 的修复方案也是一份可复用的编辑器运行时设计蓝本——同样的原则可以迁移到任何 contenteditable 渲染层的滚动与选区对账实现中。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考