免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Slate Point API 完整指南:路径与偏移定位光标位置

Slate Point API 完整指南:路径与偏移定位光标位置 Slate Point API 完整指南路径与偏移定位光标位置【免费下载链接】slateA completely customizable framework for building rich text editors. (Currently in beta.)项目地址: https://gitcode.com/gh_mirrors/sl/slatePoint点是 Slate 文档模型中用于精确描述光标位置的基础类型它用一条path节点路径加一个offset文本偏移量组合指向某个文本节点内部的具体位置。本文基于当前仓库GitHub 加速计划 / sl / slate中 docs/api/locations/point.md 的 Point API 文档结合 packages/slate/src/interfaces/point.ts 的实现源码与packages/slate/test/interfaces/Point/下的完整测试用例系统讲解 Point 的接口定义、六种静态方法compare、isAfter、isBefore、equals、isPoint、transform的语义、底层实现与典型应用。读完本文你将能够准确构造 Point、判断两个 Point 的先后关系、校验任意值是否为 Point并理解Point.transform如何在不同 Operation 下维护光标位置的正确性这是实现选区保持、历史记录、协同编辑的基础能力之一。一、Point 是什么在 Slate 中Location是描述文档位置的联合类型而 Point 是其中最精确的一档type Location Path | Point | RangePoint对象引用的是 Slate 文档中某个文本节点内的一个具体位置。它的path表示该节点在树中的位置offset表示深入该节点文本字符串的距离。需要特别强调的是Point 只能指向Text节点——因为只有文本节点才拥有可承载光标的字符串。接口定义如下interface Point { path: Path offset: number }其中Path就是一组数字索引组成的数组type Path number[]描述节点在文档树中的精确位置详见 Path API。1.1 一个直观的例子参考 docs/concepts/03-locations.md 中的文档结构const editor { children: [ { type: paragraph, children: [ { text: A line of text!, }, ], }, ], }其中叶子文本节点的路径为[0, 0]。如果要把光标放到整段文字的最开头对应的 Point 是const start { path: [0, 0], offset: 0, }如果要指向句子的末尾A line of text!共 15 个字符则是const end { path: [0, 0], offset: 15, }可以把 Point 理解为选区的光标或插入符caret选区Range正是由anchor锚点与focus焦点两个 Point 构成的见 Range API。此外遍历选区点时返回的PointEntry类型正是[Point, anchor | focus]的二元组定义见 packages/slate/src/interfaces/point.ts可见 Point 是整个选区模型的基石。1.2 扩展类型支持与 Slate 其他核心接口一样Point 通过ExtendedTypePoint, BasePoint支持类型扩展用户可以在 types/custom-types.ts 中为 Point 追加自定义属性export interface BasePoint { path: Path offset: number } export type Point ExtendedTypePoint, BasePoint从源码结构看PointInterface与Point常量声明在同一个文件中前者是类型层面的接口契约后者是运行时实现见 packages/slate/src/interfaces/point.ts二者通过 eslint-disable 注释实现了type 与 value 同名导出。二、静态方法总览Point命名空间下共提供 6 个静态方法按用途可分为三类Retrieval methods检索方法Check methods校验方法Transform methods变换方法方法签名分类用途Point.compare(point, another) -1 \| 0 \| 1Retrieval比较两个 Point 的前后关系Point.isAfter(point, another) booleanCheck判断point是否在another之后Point.isBefore(point, another) booleanCheck判断point是否在another之前Point.equals(point, another) booleanCheck判断两个 Point 是否完全相同Point.isPoint(value) value is PointCheck判断值是否实现 Point 接口类型守卫Point.transform(point, op, options?) Point \| nullTransform按 Operation 变换 Point 位置下面分别深入讲解每个方法。三、Retrieval methodsPoint.compare3.1 语义Point.compare(point: Point, another: Point) -1 | 0 | 1比较point与another返回一个整数表示该 Point 在另一个 Point 之前、重合还是之后返回-1point在another之前返回0二者指向同一位置返回1point在another之后。3.2 底层实现源码位于 packages/slate/src/interfaces/point.ts实现遵循先比路径、再比偏移的字典序逻辑compare(point: Point, another: Point): -1 | 0 | 1 { const result Path.compare(point.path, another.path) if (result 0) { if (point.offset another.offset) return -1 if (point.offset another.offset) return 1 return 0 } return result }也就是说两个 Point 的先后顺序主要由path决定复用 Path.compare注意Path.compare对互为祖先/后代的不等长路径也可能返回0只有当路径完全相等时才进一步比较offset的大小。3.3 测试用例验证packages/slate/test/interfaces/Point/compare/目录下共有 9 个用例覆盖了pathbefore / equal / after与offsetbefore / equal / after的全部 3×3 组合。例如path-equal-offset-equal.tsximport { Point } from slate export const input { point: { path: [0, 1], offset: 7, }, another: { path: [0, 1], offset: 7, }, } export const test ({ point, another }) { return Point.compare(point, another) } export const output 0四、Check methods四个布尔校验4.1Point.isAfter与Point.isBeforePoint.isAfter(point: Point, another: Point) boolean Point.isBefore(point: Point, another: Point) boolean分别检查point是否位于another之后 / 之前。实现上直接复用了compareisAfter(point: Point, another: Point): boolean { return Point.compare(point, another) 1 }, isBefore(point: Point, another: Point): boolean { return Point.compare(point, another) -1 },注意二者是严格比较当两个 Point 重合compare返回0时isAfter与isBefore都返回false。这也是 Range.isBackwardPoint.isAfter(anchor, focus)等上层判断的基础。4.2Point.equalsPoint.equals(point: Point, another: Point) boolean检查两个 Point 是否完全相等。源码实现packages/slate/src/interfaces/point.ts有一处刻意为之的性能优化——先比较更廉价的offset再比较路径equals(point: Point, another: Point): boolean { // PERF: ensure the offsets are equal first since they are cheaper to check. return ( point.offset another.offset Path.equals(point.path, another.path) ) }equals被大量上层逻辑依赖例如Range.equals就是分别对anchor、focus两个 Point 调用Point.equals见 packages/slate/src/interfaces/range.ts。4.3Point.isPointPoint.isPoint(value: any) value is Point类型守卫判断某个值是否实现了Point接口。源码实现packages/slate/src/interfaces/point.ts要求同时满足三个条件isPoint(value: any): value is Point { return ( isObject(value) typeof value.offset number Path.isPath(value.path) ) }即值是对象、offset是数字、path是合法的Path非空数字数组。packages/slate/test/interfaces/Point/isPoint/下的 8 个测试用例验证了各种边界输入空对象、缺offset、缺path、offset非数字、path非法、带额外自定义属性合法等。例如空对象{}返回false而带有自定义属性的合法 Point 对象依然返回true。五、Transform methodsPoint.transform5.1 签名与作用Point.transform(point: Point, op: Operation, options?) Point | null Options: { affinity?: forward | backward | null }transform是 Point API 中最重要的方法当文档发生一次编辑操作Operation后原有的 Point 可能已不再指向原来的位置transform会返回一个应用该 Operation 后调整过的新 Point从而让光标/选区在编辑后仍然保持正确。op是 Slate 的低级操作对象所有类型定义见 packages/slate/src/interfaces/operation.ts包括节点操作insert_node、merge_node、move_node、remove_node、set_node、split_node、文本操作insert_text、remove_text与选区操作set_selection。options.affinity亲和方向用于处理Point 恰好落在操作边界上的歧义情况取值范围为forward默认、backward或null对应 types/types.ts 中的TextDirection类型。5.2 逐 Operation 的变换规则完整的transform实现位于 packages/slate/src/interfaces/point.ts核心逻辑是对 6 种会改变文档结构的 Operation 分别处理set_node与set_selection不影响位置落入default分支原样返回。源码中的类型签名允许传入Point | null若point为null则直接返回null用于变换失效的传递。Operation变换规则insert_node/move_node节点插入或移动会改变后续节点的下标仅需对path调用Path.transformoffset不变insert_text若插入发生在同节点且插入位置在 Point 之前或恰好在 Point 处且 affinity 为forward则offset加上插入文本长度merge_node节点合并同节点时offset增加op.position随后对path做路径变换remove_text若删除发生在同节点且删除起点不晚于 Point则offset减去被删除文本中位于 Point 之前的部分remove_node若被删除的节点正是 Point 所在节点或其祖先则 Point 失效返回null否则仅做路径变换split_node切分节点切分点恰在 Point 处且 affinity 为null时返回nullPoint 落在切分点之后或恰在切分点且 affinity 为forward时offset减去op.position并切换到新节点路径以最常见的文本插入为例关键代码如下case insert_text: { if ( Path.equals(op.path, path) (op.offset offset || (op.offset offset affinity forward)) ) { offset op.text.length } break }5.3 affinity 的实战意义测试用例对比affinity决定当插入/切分恰好发生在 Point 处时光标跟着文本走forward还是留在原地backward。packages/slate/test/interfaces/Point/transform/下的两组对照用例可以非常直观地说明这一点。forward-insert-text-at-point.tsx在path: [0, 0]、offset: 1处插入一个字符affinity: forward结果 offset 变为2——光标跟着新插入的文本移动export const input { path: [0, 0], offset: 1, } export const test value { return Point.transform( value, { type: insert_text, path: [0, 0], text: a, offset: 1, properties: {}, }, { affinity: forward } ) } export const output { path: [0, 0], offset: 2, }backward-insert-text-at-point.tsx同样的输入affinity: backward结果 offset 保持1——光标留在原地新文本插入到光标之前。这两个用例分别对应在光标处输入与粘贴/自动完成等场景下光标应停留在插入内容之前两种需求是理解 affinity 语义的最佳教材。Range.transform见 packages/slate/src/interfaces/range.ts正是通过为anchor/focus分别指定不同的 Point affinity 方向实现了inward向内收缩与outward向外扩张的选区保持策略。5.4 何时返回nulltransform返回null意味着 Point 因编辑操作而彻底失效主要有两种情形输入point本身为null便于链式传递remove_node删除了 Point 所在节点或其祖先Path.isAncestor(op.path, path)该位置已不存在split_node的切分点恰与 Point 重合且affinity为null调用方显式声明不允许选择任何一侧。上层 API如Range.transform在收到null后会同样返回null表示整个选区失效需要调用方另行处理例如清除选区。六、Point 在编辑器中的协作生态Point 并非孤立存在它与 Slate 的其他核心 API 紧密协作构造与获取Editor上提供了Editor.point、Editor.start、Editor.end、Editor.before、Editor.after等方法源码位于 packages/slate/src/editor/ 下的point.ts、start.ts、end.ts、before.ts、after.ts用于按路径/位置解析出真实的 Point引用追踪PointRef API 提供带引用的 Point 包装PointRef.current会随文档编辑自动更新配合unref()释放适合在跨多次操作时持续追踪某个位置选区构成Range由anchor与focus两个 Point 组成Range.edges/start/end/includes/intersection等全部基于Point.compare、Point.isBefore、Point.isAfter实现见 packages/slate/src/interfaces/range.ts操作记录Slate 把所有变更建模为 OperationOperation APIPoint.transform与Path.transform共同保证了在应用任意操作序列后引用位置仍能正确跟随文档变化——这正是slate-history撤销/重做见 packages/slate-history/src/with-history.ts与协同编辑得以实现的基础。七、实践要点小结Point 永远指向 Text 节点构造 Point 前先用Node.leaf或Editor.leaf拿到叶子文本节点及其路径不要直接对 Element 节点取 offset优先复用静态方法比较位置用Point.compare判断先后用isAfter/isBefore判断相等用Point.equals注意与compare 0的等价性校验输入用Point.isPoint变换时正确设置 affinity涉及在光标处插入/切分的边界场景affinity: forward让光标随内容移动backward让光标留在原地null表示该位置歧义时直接判失效留意transform的null返回值节点被删除或切分歧义时 Point 会失效上层代码如选区保持逻辑必须处理这一分支避免把失效位置写回编辑器。更多相关信息可继续查阅Point API 文档、Path API、Range API、Location 类型总览以及概念篇 Locations源码与测试分别在 packages/slate/src/interfaces/point.ts 与 packages/slate/test/interfaces/Point/ 中其中测试用例覆盖了 compare 的 9 种路径×偏移组合、isPoint 的 8 种边界输入以及 transform 的 forward/backward 对照场景是深入理解 Point 语义的第一手资料。【免费下载链接】slateA completely customizable framework for building rich text editors. (Currently in beta.)项目地址: https://gitcode.com/gh_mirrors/sl/slate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表