Selection 与 Path/Range 映射机制
1. 这是 Slate 最难、也最有区分度的部分
很多编辑器问题最后都落在“选区映射”:
- 为什么会报
Cannot resolve a Slate point from DOM point - 为什么光标跳动
- 为什么 onChange 里
findPath偶发不准
根因通常是:DOM 与 Slate 树在某个时刻失步。
2. 映射核心在 slate-dom,不是 slate-react
核心 API(packages/slate-dom/src/plugin/dom-editor.ts):
toDOMPoint/toDOMRangetoSlatePoint/toSlateRangefindPath
slate-react 的 ReactEditor 只是复用 DOMEditor:
- 文件:
packages/slate-react/src/plugin/react-editor.ts - 结论:React 层主要做事件编排,不重写映射算法主体
3. 双向同步路径(必须会说)
DOM -> Slate
在 Editable 里监听 selectionchange 与 beforeinput,调用:
ReactEditor.toSlateRange(...)Transforms.select(editor, range)
Slate -> DOM
在 layout effect 中,拿 editor.selection 转为:
ReactEditor.toDOMRange(...)domSelection.setBaseAndExtent(...)
这就形成闭环:DOM 事件驱动状态更新,状态再驱动 DOM 选区校准。
4. 关键兼容细节(进阶加分)
dom-editor.ts / editable.tsx 中有大量兼容分支:
- zero-width 节点 offset 对齐(Slate offset=0,DOM 常是 1)
- void 节点 offset 修正
- ShadowRoot 选择问题
- Firefox 多 range 与 table 选择兼容
- Chrome/Safari 对
beforeinput与 composition 的顺序差异
结论:富文本编辑器不是“业务复杂”,而是“浏览器行为复杂”。
5. findPath 的时序陷阱
Issue #5938 很典型:
NODE_TO_INDEX / NODE_TO_PARENT在渲染后更新onChange触发可能早于视图同步- 在特定时机
DOMEditor.findPath可能拿到旧映射
对应设计演进:
- PR #5800 尝试把 path 计算脱离 DOM 依赖
这类问题本质是“映射索引更新时序”的工程权衡。
6. 回答模板
Slate 选区系统是双向映射:DOM 选区变化经
toSlateRange回写 editor,editor.selection 再经toDOMRange推回原生 Selection。
映射层在slate-dom,并带大量浏览器兼容逻辑(zero-width、void、shadow root、Firefox table range)。
线上常见选区 bug 通常是 DOM 映射缓存与 React 渲染时序错位,不是简单 API 调用错误。