articulated 1.0.0 → 1.2.0

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.
Files changed (39) hide show
  1. package/README.md +26 -2
  2. package/build/commonjs/{id.d.ts → element_id.d.ts} +2 -0
  3. package/build/commonjs/{id.js → element_id.js} +1 -1
  4. package/build/commonjs/element_id.js.map +1 -0
  5. package/build/commonjs/element_id_generator.d.ts +27 -0
  6. package/build/commonjs/element_id_generator.js +45 -0
  7. package/build/commonjs/element_id_generator.js.map +1 -0
  8. package/build/commonjs/id_list.d.ts +39 -6
  9. package/build/commonjs/id_list.js +82 -14
  10. package/build/commonjs/id_list.js.map +1 -1
  11. package/build/commonjs/index.d.ts +2 -1
  12. package/build/commonjs/index.js +2 -1
  13. package/build/commonjs/index.js.map +1 -1
  14. package/build/commonjs/internal/misc.d.ts +1 -0
  15. package/build/commonjs/internal/misc.js +10 -0
  16. package/build/commonjs/internal/misc.js.map +1 -0
  17. package/build/esm/{id.d.ts → element_id.d.ts} +2 -0
  18. package/build/esm/{id.js → element_id.js} +1 -1
  19. package/build/esm/element_id.js.map +1 -0
  20. package/build/esm/element_id_generator.d.ts +27 -0
  21. package/build/esm/element_id_generator.js +41 -0
  22. package/build/esm/element_id_generator.js.map +1 -0
  23. package/build/esm/id_list.d.ts +39 -6
  24. package/build/esm/id_list.js +82 -14
  25. package/build/esm/id_list.js.map +1 -1
  26. package/build/esm/index.d.ts +2 -1
  27. package/build/esm/index.js +2 -1
  28. package/build/esm/index.js.map +1 -1
  29. package/build/esm/internal/misc.d.ts +1 -0
  30. package/build/esm/internal/misc.js +6 -0
  31. package/build/esm/internal/misc.js.map +1 -0
  32. package/package.json +1 -1
  33. package/src/{id.ts → element_id.ts} +2 -0
  34. package/src/element_id_generator.ts +43 -0
  35. package/src/id_list.ts +92 -15
  36. package/src/index.ts +2 -1
  37. package/src/internal/misc.ts +5 -0
  38. package/build/commonjs/id.js.map +0 -1
  39. package/build/esm/id.js.map +0 -1
package/src/id_list.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { SparseIndices } from "sparse-array-rled";
2
- import { ElementId } from "./id";
2
+ import { ElementId } from "./element_id";
3
3
  import { LeafMap, MutableLeafMap } from "./internal/leaf_map";
4
+ import { checkCount } from "./internal/misc";
4
5
  import { getAndBumpNextSeq, MutableSeqMap, SeqMap } from "./internal/seq_map";
5
6
  import { SavedIdList } from "./saved_id_list";
6
7
 
@@ -271,9 +272,7 @@ export class IdList {
271
272
  if (!(Number.isSafeInteger(newId.counter) && newId.counter >= 0)) {
272
273
  throw new Error(`Invalid counter: ${newId.counter}`);
273
274
  }
274
- if (!(Number.isSafeInteger(count) && count >= 0)) {
275
- throw new Error(`Invalid count: ${count}`);
276
- }
275
+ checkCount(count);
277
276
  if (this.isAnyKnown(newId, count)) {
278
277
  throw new Error("An inserted id is already known");
279
278
  }
@@ -386,9 +385,7 @@ export class IdList {
386
385
  if (!(Number.isSafeInteger(newId.counter) && newId.counter >= 0)) {
387
386
  throw new Error(`Invalid counter: ${newId.counter}`);
388
387
  }
389
- if (!(Number.isSafeInteger(count) && count >= 0)) {
390
- throw new Error(`Invalid count: ${count}`);
391
- }
388
+ checkCount(count);
392
389
  if (this.isAnyKnown(newId, count)) {
393
390
  throw new Error("An inserted id is already known");
394
391
  }
@@ -489,9 +486,7 @@ export class IdList {
489
486
  * `uninsert(id, count)` is an exact inverse to `insertAfter(-, id, count)` or `insertBefore(-, id, count)`.
490
487
  */
491
488
  uninsert(id: ElementId, count = 1) {
492
- if (!(Number.isSafeInteger(count) && count >= 0)) {
493
- throw new Error(`Invalid count: ${count}`);
494
- }
489
+ checkCount(count);
495
490
  if (count === 0) return this;
496
491
 
497
492
  // We optimize for the case where you are undoing the most recent insert operation.
@@ -568,8 +563,23 @@ export class IdList {
568
563
  * This does have a memory cost, but it is compressed in common cases.
569
564
  *
570
565
  * If `id` is already deleted or is not known, this method does nothing.
566
+ *
567
+ * @param count Provide this to bulk-delete `count` ids,
568
+ * starting with newId and proceeding with the same bunchId and sequential counters.
569
+ * __Note__: To delete multiple ids at sequential *indexes*, use deleteRange.
571
570
  */
572
- delete(id: ElementId) {
571
+ delete(id: ElementId, count = 1) {
572
+ checkCount(count);
573
+
574
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
575
+ let ans: IdList = this;
576
+ for (let i = 0; i < count; i++) {
577
+ ans = ans.deleteOne({ bunchId: id.bunchId, counter: id.counter + i });
578
+ }
579
+ return ans;
580
+ }
581
+
582
+ private deleteOne(id: ElementId) {
573
583
  const located = this.locate(id);
574
584
  if (located === null) return this;
575
585
 
@@ -582,6 +592,24 @@ export class IdList {
582
592
  return this.replaceLeaf(located, { ...leaf, present: newPresent });
583
593
  }
584
594
 
595
+ /**
596
+ * Deletes all ids with indexes in the range [from, to).
597
+ *
598
+ * @throws If any deleted index is out of bounds.
599
+ */
600
+ deleteRange(from: number, to: number) {
601
+ const allIds: ElementId[] = [];
602
+ for (let i = from; i < to; i++) {
603
+ allIds.push(this.at(i));
604
+ }
605
+
606
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
607
+ let ans: IdList = this;
608
+ for (const id of allIds) ans = ans.delete(id);
609
+
610
+ return ans;
611
+ }
612
+
585
613
  /**
586
614
  * Un-marks `id` as deleted from this list, making it present again.
587
615
  * A new IdList is returned and the current list remains unchanged.
@@ -590,9 +618,22 @@ export class IdList {
590
618
  *
591
619
  * If `id` is already present, this method does nothing.
592
620
  *
593
- * @throws If `id` is not known.
621
+ * @param count Provide this to bulk-undelete `count` ids,
622
+ * starting with newId and proceeding with the same bunchId and sequential counters.
623
+ * @throws If any deleted id is not known.
594
624
  */
595
- undelete(id: ElementId) {
625
+ undelete(id: ElementId, count = 1) {
626
+ checkCount(count);
627
+
628
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
629
+ let ans: IdList = this;
630
+ for (let i = count - 1; i >= 0; i--) {
631
+ ans = ans.undeleteOne({ bunchId: id.bunchId, counter: id.counter + i });
632
+ }
633
+ return ans;
634
+ }
635
+
636
+ private undeleteOne(id: ElementId) {
596
637
  const located = this.locate(id);
597
638
  if (located === null) {
598
639
  throw new Error("id is not known");
@@ -753,8 +794,6 @@ export class IdList {
753
794
  /**
754
795
  * Returns the maximum counter across all known ElementIds with the given bunchId,
755
796
  * or undefined if no such ElementIds are known.
756
- *
757
- * This method is useful when creating ElementIds.
758
797
  */
759
798
  maxCounter(bunchId: string): number | undefined {
760
799
  // Find the greatest-counter leaf containing bunchId.
@@ -872,6 +911,44 @@ export class IdList {
872
911
  }
873
912
  }
874
913
 
914
+ /**
915
+ * Returns the cursor at the given index within the list, i.e., between `index - 1` and `index`.
916
+ * See [Cursors](https://github.com/mweidner037/articulated#cursors).
917
+ *
918
+ * Invert with {@link cursorIndex}.
919
+ *
920
+ * @param bind Whether to bind to the left or the right side of the gap, in case ids
921
+ * later appear between `index - 1` and `index`. Default: `"left"`, which is typical for text cursors.
922
+ * @throws If index is not in the range `[0, list.length]`.
923
+ */
924
+ cursorAt(index: number, bind: "left" | "right" = "left"): ElementId | null {
925
+ if (bind === "left") {
926
+ return index === 0 ? null : this.at(index - 1);
927
+ } else {
928
+ return index === this.length ? null : this.at(index);
929
+ }
930
+ }
931
+
932
+ /**
933
+ * Returns the current index of the given cursor within the list.
934
+ * That is, the cursor is in the gap between `index - 1` and `index`.
935
+ *
936
+ * Inverts {@link cursorAt}.
937
+ *
938
+ * @param bind The `bind` value that was used with {@link cursorAt}, if any.
939
+ * @throws If `cursor` is an ElementId that is not known.
940
+ */
941
+ cursorIndex(
942
+ cursor: ElementId | null,
943
+ bind: "left" | "right" = "left"
944
+ ): number {
945
+ if (bind === "left") {
946
+ return cursor === null ? 0 : this.indexOf(cursor, "left") + 1;
947
+ } else {
948
+ return cursor === null ? this.length : this.indexOf(cursor, "right");
949
+ }
950
+ }
951
+
875
952
  // Iterators and views
876
953
 
877
954
  /**
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
- export * from "./id";
1
+ export * from "./element_id";
2
+ export * from "./element_id_generator";
2
3
  export { IdList, KnownIdView } from "./id_list";
3
4
  export * from "./saved_id_list";
@@ -0,0 +1,5 @@
1
+ export function checkCount(count: number) {
2
+ if (!(Number.isSafeInteger(count) && count >= 0)) {
3
+ throw new Error(`Invalid count: ${count}`);
4
+ }
5
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/id.ts"],"names":[],"mappings":";;;AAsCA;;GAEG;AACH,SAAgB,QAAQ,CAAC,CAAY,EAAE,CAAY;IACjD,OAAO,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC;AAC5D,CAAC;AAFD,4BAEC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAgB,SAAS,CAAC,OAAkB,EAAE,KAAa;IACzD,IAAI,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAVD,8BAUC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/id.ts"],"names":[],"mappings":"AAsCA;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,CAAY,EAAE,CAAY;IACjD,OAAO,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CAAC,OAAkB,EAAE,KAAa;IACzD,IAAI,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}