@pylonsync/loro 0.22.43 → 0.22.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -346,8 +346,21 @@ function childIndexFor(node: Node, pmIndex: number): number {
346
346
  * A cursor for a document position. Walks the position's path from the
347
347
  * field's root down, so it does not depend on node identity (a replace
348
348
  * rebuilds the nodes along its path).
349
+ *
350
+ * Inside text the cursor binds to a character: by default the one on the
351
+ * left, so text typed at a caret goes before it. `bind: "right"` binds to the
352
+ * character on the right instead, for the start of a range, so text typed
353
+ * just before the range stays out of it. Where there is no character on that
354
+ * side, it binds to the other one.
349
355
  */
350
- export function positionToCursor(root: Node, pos: number, doc: LoroDoc, containerId: ContainerID): Cursor | null {
356
+ export function positionToCursor(
357
+ root: Node,
358
+ pos: number,
359
+ doc: LoroDoc,
360
+ containerId: ContainerID,
361
+ options: { bind?: "left" | "right" } = {},
362
+ ): Cursor | null {
363
+ const bind = options.bind ?? "left";
351
364
  if (pos < 0 || pos > root.content.size) return null;
352
365
  const $pos = root.resolve(pos);
353
366
  let map = field(doc, containerId);
@@ -382,14 +395,14 @@ export function positionToCursor(root: Node, pos: number, doc: LoroDoc, containe
382
395
  } else {
383
396
  // The caret at the end of a text run binds to that run, so typing
384
397
  // there continues it.
385
- if (runStart >= 0 && offset <= pmOffset) return textCursor(children.get(group), offset - runStart);
398
+ if (runStart >= 0 && offset <= pmOffset) return textCursor(children.get(group), offset - runStart, bind);
386
399
  runStart = -1;
387
400
  group += 1;
388
401
  if (offset === pmOffset) return children.getCursor(group) ?? null;
389
402
  }
390
403
  pmOffset += child.nodeSize;
391
404
  }
392
- if (runStart >= 0 && offset <= pmOffset) return textCursor(children.get(group), offset - runStart);
405
+ if (runStart >= 0 && offset <= pmOffset) return textCursor(children.get(group), offset - runStart, bind);
393
406
  return children.getCursor(children.length) ?? null;
394
407
  }
395
408
 
@@ -403,11 +416,16 @@ function isLowSurrogate(text: string): boolean {
403
416
  * A cursor `offset` UTF-16 units into a text run, bound to the character on
404
417
  * its left (side 1: just after it), the whole character when it is a
405
418
  * surrogate pair. At the start of the run there is none, so it binds to the
406
- * first character.
419
+ * first character. With `bind` "right" it binds to the character on its
420
+ * right (side -1: just before it) while there is one.
407
421
  */
408
- function textCursor(text: unknown, offset: number): Cursor | null {
422
+ function textCursor(text: unknown, offset: number, bind: "left" | "right"): Cursor | null {
409
423
  if (!(text instanceof LoroText)) return null;
410
424
  if (offset <= 0) return text.getCursor(0, 0) ?? null;
425
+ if (bind === "right" && offset < text.length) {
426
+ const at = isLowSurrogate(text.slice(offset, offset + 1)) ? offset - 1 : offset;
427
+ return text.getCursor(at, -1) ?? null;
428
+ }
411
429
  let start = Math.min(offset, text.length) - 1;
412
430
  if (start > 0 && isLowSurrogate(text.slice(start, start + 1))) start -= 1;
413
431
  return text.getCursor(start, 1) ?? null;