@fluidframework/sequence 2.0.0-dev.6.4.0.192049 → 2.0.0-dev.7.2.0.204906

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 (226) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/README.md +130 -0
  3. package/api-extractor.json +1 -1
  4. package/api-report/sequence.api.md +717 -0
  5. package/dist/defaultMap.d.ts +1 -1
  6. package/dist/defaultMap.d.ts.map +1 -1
  7. package/dist/defaultMap.js +6 -6
  8. package/dist/defaultMap.js.map +1 -1
  9. package/dist/defaultMapInterfaces.d.ts +22 -2
  10. package/dist/defaultMapInterfaces.d.ts.map +1 -1
  11. package/dist/defaultMapInterfaces.js.map +1 -1
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +2 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/intervalCollection.d.ts +164 -16
  17. package/dist/intervalCollection.d.ts.map +1 -1
  18. package/dist/intervalCollection.js +174 -54
  19. package/dist/intervalCollection.js.map +1 -1
  20. package/dist/intervalIndex/endpointInRangeIndex.d.ts +17 -3
  21. package/dist/intervalIndex/endpointInRangeIndex.d.ts.map +1 -1
  22. package/dist/intervalIndex/endpointInRangeIndex.js +12 -6
  23. package/dist/intervalIndex/endpointInRangeIndex.js.map +1 -1
  24. package/dist/intervalIndex/endpointIndex.d.ts +19 -2
  25. package/dist/intervalIndex/endpointIndex.d.ts.map +1 -1
  26. package/dist/intervalIndex/endpointIndex.js +10 -5
  27. package/dist/intervalIndex/endpointIndex.js.map +1 -1
  28. package/dist/intervalIndex/idIntervalIndex.d.ts +6 -0
  29. package/dist/intervalIndex/idIntervalIndex.d.ts.map +1 -1
  30. package/dist/intervalIndex/idIntervalIndex.js +3 -0
  31. package/dist/intervalIndex/idIntervalIndex.js.map +1 -1
  32. package/dist/intervalIndex/index.d.ts +4 -4
  33. package/dist/intervalIndex/index.d.ts.map +1 -1
  34. package/dist/intervalIndex/index.js +5 -1
  35. package/dist/intervalIndex/index.js.map +1 -1
  36. package/dist/intervalIndex/intervalIndex.d.ts +1 -0
  37. package/dist/intervalIndex/intervalIndex.d.ts.map +1 -1
  38. package/dist/intervalIndex/intervalIndex.js.map +1 -1
  39. package/dist/intervalIndex/overlappingIntervalsIndex.d.ts +17 -6
  40. package/dist/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -1
  41. package/dist/intervalIndex/overlappingIntervalsIndex.js +17 -4
  42. package/dist/intervalIndex/overlappingIntervalsIndex.js.map +1 -1
  43. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +5 -2
  44. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -1
  45. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js +9 -1
  46. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -1
  47. package/dist/intervalIndex/sequenceIntervalIndexes.d.ts +2 -1
  48. package/dist/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -1
  49. package/dist/intervalIndex/sequenceIntervalIndexes.js.map +1 -1
  50. package/dist/intervalIndex/startpointInRangeIndex.d.ts +17 -3
  51. package/dist/intervalIndex/startpointInRangeIndex.d.ts.map +1 -1
  52. package/dist/intervalIndex/startpointInRangeIndex.js +12 -8
  53. package/dist/intervalIndex/startpointInRangeIndex.js.map +1 -1
  54. package/dist/intervalTree.d.ts +1 -1
  55. package/dist/intervalTree.d.ts.map +1 -1
  56. package/dist/intervals/interval.d.ts +4 -2
  57. package/dist/intervals/interval.d.ts.map +1 -1
  58. package/dist/intervals/interval.js +14 -5
  59. package/dist/intervals/interval.js.map +1 -1
  60. package/dist/intervals/intervalUtils.d.ts +51 -18
  61. package/dist/intervals/intervalUtils.d.ts.map +1 -1
  62. package/dist/intervals/intervalUtils.js +18 -10
  63. package/dist/intervals/intervalUtils.js.map +1 -1
  64. package/dist/intervals/sequenceInterval.d.ts +28 -13
  65. package/dist/intervals/sequenceInterval.d.ts.map +1 -1
  66. package/dist/intervals/sequenceInterval.js +124 -43
  67. package/dist/intervals/sequenceInterval.js.map +1 -1
  68. package/dist/localValues.d.ts.map +1 -1
  69. package/dist/localValues.js.map +1 -1
  70. package/dist/packageVersion.d.ts +1 -1
  71. package/dist/packageVersion.js +1 -1
  72. package/dist/packageVersion.js.map +1 -1
  73. package/dist/revertibles.d.ts +3 -15
  74. package/dist/revertibles.d.ts.map +1 -1
  75. package/dist/revertibles.js +11 -19
  76. package/dist/revertibles.js.map +1 -1
  77. package/dist/sequence-alpha.d.ts +1587 -0
  78. package/dist/sequence-beta.d.ts +1507 -0
  79. package/dist/sequence-public.d.ts +1507 -0
  80. package/dist/sequence-untrimmed.d.ts +1759 -0
  81. package/dist/sequence.d.ts +8 -4
  82. package/dist/sequence.d.ts.map +1 -1
  83. package/dist/sequence.js +53 -48
  84. package/dist/sequence.js.map +1 -1
  85. package/dist/sequenceDeltaEvent.d.ts +4 -0
  86. package/dist/sequenceDeltaEvent.d.ts.map +1 -1
  87. package/dist/sequenceDeltaEvent.js +3 -0
  88. package/dist/sequenceDeltaEvent.js.map +1 -1
  89. package/dist/sequenceFactory.d.ts +3 -0
  90. package/dist/sequenceFactory.d.ts.map +1 -1
  91. package/dist/sequenceFactory.js +4 -1
  92. package/dist/sequenceFactory.js.map +1 -1
  93. package/dist/sharedIntervalCollection.d.ts +5 -0
  94. package/dist/sharedIntervalCollection.d.ts.map +1 -1
  95. package/dist/sharedIntervalCollection.js +11 -9
  96. package/dist/sharedIntervalCollection.js.map +1 -1
  97. package/dist/sharedSequence.d.ts +6 -3
  98. package/dist/sharedSequence.d.ts.map +1 -1
  99. package/dist/sharedSequence.js +10 -8
  100. package/dist/sharedSequence.js.map +1 -1
  101. package/dist/sharedString.d.ts +17 -2
  102. package/dist/sharedString.d.ts.map +1 -1
  103. package/dist/sharedString.js +21 -7
  104. package/dist/sharedString.js.map +1 -1
  105. package/dist/tsdoc-metadata.json +1 -1
  106. package/lib/defaultMap.d.ts +1 -1
  107. package/lib/defaultMap.d.ts.map +1 -1
  108. package/lib/defaultMap.js +6 -6
  109. package/lib/defaultMap.js.map +1 -1
  110. package/lib/defaultMapInterfaces.d.ts +22 -2
  111. package/lib/defaultMapInterfaces.d.ts.map +1 -1
  112. package/lib/defaultMapInterfaces.js.map +1 -1
  113. package/lib/index.d.ts +2 -2
  114. package/lib/index.d.ts.map +1 -1
  115. package/lib/index.js +1 -1
  116. package/lib/index.js.map +1 -1
  117. package/lib/intervalCollection.d.ts +164 -16
  118. package/lib/intervalCollection.d.ts.map +1 -1
  119. package/lib/intervalCollection.js +172 -55
  120. package/lib/intervalCollection.js.map +1 -1
  121. package/lib/intervalIndex/endpointInRangeIndex.d.ts +17 -3
  122. package/lib/intervalIndex/endpointInRangeIndex.d.ts.map +1 -1
  123. package/lib/intervalIndex/endpointInRangeIndex.js +12 -7
  124. package/lib/intervalIndex/endpointInRangeIndex.js.map +1 -1
  125. package/lib/intervalIndex/endpointIndex.d.ts +19 -2
  126. package/lib/intervalIndex/endpointIndex.d.ts.map +1 -1
  127. package/lib/intervalIndex/endpointIndex.js +10 -6
  128. package/lib/intervalIndex/endpointIndex.js.map +1 -1
  129. package/lib/intervalIndex/idIntervalIndex.d.ts +6 -0
  130. package/lib/intervalIndex/idIntervalIndex.d.ts.map +1 -1
  131. package/lib/intervalIndex/idIntervalIndex.js +3 -0
  132. package/lib/intervalIndex/idIntervalIndex.js.map +1 -1
  133. package/lib/intervalIndex/index.d.ts +4 -4
  134. package/lib/intervalIndex/index.d.ts.map +1 -1
  135. package/lib/intervalIndex/index.js +4 -4
  136. package/lib/intervalIndex/index.js.map +1 -1
  137. package/lib/intervalIndex/intervalIndex.d.ts +1 -0
  138. package/lib/intervalIndex/intervalIndex.d.ts.map +1 -1
  139. package/lib/intervalIndex/intervalIndex.js.map +1 -1
  140. package/lib/intervalIndex/overlappingIntervalsIndex.d.ts +17 -6
  141. package/lib/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -1
  142. package/lib/intervalIndex/overlappingIntervalsIndex.js +18 -5
  143. package/lib/intervalIndex/overlappingIntervalsIndex.js.map +1 -1
  144. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +5 -2
  145. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -1
  146. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js +9 -1
  147. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -1
  148. package/lib/intervalIndex/sequenceIntervalIndexes.d.ts +2 -1
  149. package/lib/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -1
  150. package/lib/intervalIndex/sequenceIntervalIndexes.js.map +1 -1
  151. package/lib/intervalIndex/startpointInRangeIndex.d.ts +17 -3
  152. package/lib/intervalIndex/startpointInRangeIndex.d.ts.map +1 -1
  153. package/lib/intervalIndex/startpointInRangeIndex.js +12 -9
  154. package/lib/intervalIndex/startpointInRangeIndex.js.map +1 -1
  155. package/lib/intervalTree.d.ts +1 -1
  156. package/lib/intervalTree.d.ts.map +1 -1
  157. package/lib/intervals/interval.d.ts +4 -2
  158. package/lib/intervals/interval.d.ts.map +1 -1
  159. package/lib/intervals/interval.js +14 -5
  160. package/lib/intervals/interval.js.map +1 -1
  161. package/lib/intervals/intervalUtils.d.ts +51 -18
  162. package/lib/intervals/intervalUtils.d.ts.map +1 -1
  163. package/lib/intervals/intervalUtils.js +14 -6
  164. package/lib/intervals/intervalUtils.js.map +1 -1
  165. package/lib/intervals/sequenceInterval.d.ts +28 -13
  166. package/lib/intervals/sequenceInterval.d.ts.map +1 -1
  167. package/lib/intervals/sequenceInterval.js +125 -42
  168. package/lib/intervals/sequenceInterval.js.map +1 -1
  169. package/lib/localValues.d.ts.map +1 -1
  170. package/lib/localValues.js.map +1 -1
  171. package/lib/packageVersion.d.ts +1 -1
  172. package/lib/packageVersion.js +1 -1
  173. package/lib/packageVersion.js.map +1 -1
  174. package/lib/revertibles.d.ts +3 -15
  175. package/lib/revertibles.d.ts.map +1 -1
  176. package/lib/revertibles.js +11 -19
  177. package/lib/revertibles.js.map +1 -1
  178. package/lib/sequence.d.ts +8 -4
  179. package/lib/sequence.d.ts.map +1 -1
  180. package/lib/sequence.js +56 -49
  181. package/lib/sequence.js.map +1 -1
  182. package/lib/sequenceDeltaEvent.d.ts +4 -0
  183. package/lib/sequenceDeltaEvent.d.ts.map +1 -1
  184. package/lib/sequenceDeltaEvent.js +3 -0
  185. package/lib/sequenceDeltaEvent.js.map +1 -1
  186. package/lib/sequenceFactory.d.ts +3 -0
  187. package/lib/sequenceFactory.d.ts.map +1 -1
  188. package/lib/sequenceFactory.js +4 -1
  189. package/lib/sequenceFactory.js.map +1 -1
  190. package/lib/sharedIntervalCollection.d.ts +5 -0
  191. package/lib/sharedIntervalCollection.d.ts.map +1 -1
  192. package/lib/sharedIntervalCollection.js +11 -9
  193. package/lib/sharedIntervalCollection.js.map +1 -1
  194. package/lib/sharedSequence.d.ts +6 -3
  195. package/lib/sharedSequence.d.ts.map +1 -1
  196. package/lib/sharedSequence.js +10 -8
  197. package/lib/sharedSequence.js.map +1 -1
  198. package/lib/sharedString.d.ts +17 -2
  199. package/lib/sharedString.d.ts.map +1 -1
  200. package/lib/sharedString.js +21 -7
  201. package/lib/sharedString.js.map +1 -1
  202. package/package.json +31 -30
  203. package/src/defaultMapInterfaces.ts +22 -2
  204. package/src/index.ts +4 -1
  205. package/src/intervalCollection.ts +423 -82
  206. package/src/intervalIndex/endpointInRangeIndex.ts +23 -11
  207. package/src/intervalIndex/endpointIndex.ts +22 -9
  208. package/src/intervalIndex/idIntervalIndex.ts +7 -1
  209. package/src/intervalIndex/index.ts +12 -3
  210. package/src/intervalIndex/intervalIndex.ts +1 -0
  211. package/src/intervalIndex/overlappingIntervalsIndex.ts +40 -15
  212. package/src/intervalIndex/overlappingSequenceIntervalsIndex.ts +10 -1
  213. package/src/intervalIndex/sequenceIntervalIndexes.ts +2 -1
  214. package/src/intervalIndex/startpointInRangeIndex.ts +23 -18
  215. package/src/intervals/interval.ts +35 -8
  216. package/src/intervals/intervalUtils.ts +61 -27
  217. package/src/intervals/sequenceInterval.ts +197 -47
  218. package/src/localValues.ts +4 -1
  219. package/src/packageVersion.ts +1 -1
  220. package/src/revertibles.ts +14 -36
  221. package/src/sequence.ts +14 -5
  222. package/src/sequenceDeltaEvent.ts +4 -0
  223. package/src/sequenceFactory.ts +4 -1
  224. package/src/sharedIntervalCollection.ts +5 -0
  225. package/src/sharedSequence.ts +6 -3
  226. package/src/sharedString.ts +25 -2
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  /* eslint-disable no-bitwise */
7
+ /* eslint-disable import/no-deprecated */
7
8
 
8
9
  import { TypedEventEmitter } from "@fluid-internal/client-utils";
9
10
  import { assert } from "@fluidframework/core-utils";
@@ -22,6 +23,8 @@ import {
22
23
  reservedRangeLabelsKey,
23
24
  UnassignedSequenceNumber,
24
25
  DetachedReferencePosition,
26
+ UniversalSequenceNumber,
27
+ SlidingPreference,
25
28
  } from "@fluidframework/merge-tree";
26
29
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
27
30
  import { LoggingError, UsageError } from "@fluidframework/telemetry-utils";
@@ -53,15 +56,64 @@ import {
53
56
  createInterval,
54
57
  } from "./intervals";
55
58
  import {
59
+ EndpointIndex,
56
60
  IEndpointIndex,
57
61
  IIdIntervalIndex,
58
62
  IOverlappingIntervalsIndex,
59
63
  IntervalIndex,
60
- createEndpointIndex,
64
+ OverlappingIntervalsIndex,
61
65
  createIdIntervalIndex,
62
- createOverlappingIntervalsIndex,
63
66
  } from "./intervalIndex";
64
67
 
68
+ /**
69
+ * Defines a position and side relative to a character in a sequence.
70
+ *
71
+ * For this purpose, sequences look like:
72
+ *
73
+ * `{start} - {character 0} - {character 1} - ... - {character N} - {end}`
74
+ *
75
+ * Each `{value}` in the diagram is a character within a sequence.
76
+ * Each `-` in the above diagram is a position where text could be inserted.
77
+ * Each position between a `{value}` and a `-` is a `SequencePlace`.
78
+ *
79
+ * The special endpoints `{start}` and `{end}` refer to positions outside the
80
+ * contents of the string.
81
+ *
82
+ * This gives us 2N + 2 possible positions to refer to within a string, where N
83
+ * is the number of characters.
84
+ *
85
+ * If the position is specified with a bare number, the side defaults to
86
+ * `Side.Before`.
87
+ *
88
+ * If a SequencePlace is the endpoint of a range (e.g. start/end of an interval or search range),
89
+ * the Side value means it is exclusive if it is nearer to the other position and inclusive if it is farther.
90
+ * E.g. the start of a range with Side.After is exclusive of the character at the position.
91
+ * @public
92
+ */
93
+ export type SequencePlace = number | "start" | "end" | InteriorSequencePlace;
94
+
95
+ /**
96
+ * A sequence place that does not refer to the special endpoint segments.
97
+ *
98
+ * See {@link SequencePlace} for additional context.
99
+ * @public
100
+ */
101
+ export interface InteriorSequencePlace {
102
+ pos: number;
103
+ side: Side;
104
+ }
105
+
106
+ /**
107
+ * Defines a side relative to a character in a sequence.
108
+ *
109
+ * @remarks See {@link SequencePlace} for additional context on usage.
110
+ * @public
111
+ */
112
+ export enum Side {
113
+ Before = 0,
114
+ After = 1,
115
+ }
116
+
65
117
  const reservedIntervalIdKey = "intervalId";
66
118
 
67
119
  export interface ISerializedIntervalCollectionV2 {
@@ -70,6 +122,13 @@ export interface ISerializedIntervalCollectionV2 {
70
122
  intervals: CompressedSerializedInterval[];
71
123
  }
72
124
 
125
+ export function sidesFromStickiness(stickiness: IntervalStickiness) {
126
+ const startSide = (stickiness & IntervalStickiness.START) !== 0 ? Side.After : Side.Before;
127
+ const endSide = (stickiness & IntervalStickiness.END) !== 0 ? Side.Before : Side.After;
128
+
129
+ return { startSide, endSide };
130
+ }
131
+
73
132
  /**
74
133
  * Decompress an interval after loading a summary from JSON. The exact format
75
134
  * of this compression is unspecified and subject to change
@@ -78,13 +137,17 @@ function decompressInterval(
78
137
  interval: CompressedSerializedInterval,
79
138
  label?: string,
80
139
  ): ISerializedInterval {
140
+ const stickiness = interval[5] ?? IntervalStickiness.END;
141
+ const { startSide, endSide } = sidesFromStickiness(stickiness);
81
142
  return {
82
143
  start: interval[0],
83
144
  end: interval[1],
84
145
  sequenceNumber: interval[2],
85
146
  intervalType: interval[3],
86
147
  properties: { ...interval[4], [reservedRangeLabelsKey]: [label] },
87
- stickiness: interval[5],
148
+ stickiness,
149
+ startSide,
150
+ endSide,
88
151
  };
89
152
  }
90
153
 
@@ -95,7 +158,7 @@ function decompressInterval(
95
158
  function compressInterval(interval: ISerializedInterval): CompressedSerializedInterval {
96
159
  const { start, end, sequenceNumber, intervalType, properties } = interval;
97
160
 
98
- const base: CompressedSerializedInterval = [
161
+ let base: CompressedSerializedInterval = [
99
162
  start,
100
163
  end,
101
164
  sequenceNumber,
@@ -106,18 +169,69 @@ function compressInterval(interval: ISerializedInterval): CompressedSerializedIn
106
169
  ];
107
170
 
108
171
  if (interval.stickiness !== undefined && interval.stickiness !== IntervalStickiness.END) {
109
- base.push(interval.stickiness);
172
+ // reassignment to make it easier for typescript to reason about types
173
+ base = [...base, interval.stickiness];
110
174
  }
111
175
 
112
176
  return base;
113
177
  }
114
178
 
179
+ export function endpointPosAndSide(
180
+ start: SequencePlace | undefined,
181
+ end: SequencePlace | undefined,
182
+ ) {
183
+ const startIsPlainEndpoint = typeof start === "number" || start === "start" || start === "end";
184
+ const endIsPlainEndpoint = typeof end === "number" || end === "start" || end === "end";
185
+
186
+ const startSide = startIsPlainEndpoint ? Side.Before : start?.side;
187
+ const endSide = endIsPlainEndpoint ? Side.Before : end?.side;
188
+
189
+ const startPos = startIsPlainEndpoint ? start : start?.pos;
190
+ const endPos = endIsPlainEndpoint ? end : end?.pos;
191
+
192
+ return {
193
+ startSide,
194
+ endSide,
195
+ startPos,
196
+ endPos,
197
+ };
198
+ }
199
+
200
+ function toSequencePlace(pos: number | "start" | "end", side: Side): SequencePlace {
201
+ return typeof pos === "number" ? { pos, side } : pos;
202
+ }
203
+
204
+ function toOptionalSequencePlace(
205
+ pos: number | "start" | "end" | undefined,
206
+ side: Side = Side.Before,
207
+ ): SequencePlace | undefined {
208
+ return typeof pos === "number" ? { pos, side } : pos;
209
+ }
210
+
211
+ export function computeStickinessFromSide(
212
+ startPos: number | "start" | "end" | undefined = -1,
213
+ startSide: Side = Side.Before,
214
+ endPos: number | "start" | "end" | undefined = -1,
215
+ endSide: Side = Side.Before,
216
+ ): IntervalStickiness {
217
+ let stickiness: IntervalStickiness = IntervalStickiness.NONE;
218
+
219
+ if (startSide === Side.After || startPos === "start") {
220
+ stickiness |= IntervalStickiness.START;
221
+ }
222
+
223
+ if (endSide === Side.Before || endPos === "end") {
224
+ stickiness |= IntervalStickiness.END;
225
+ }
226
+
227
+ return stickiness as IntervalStickiness;
228
+ }
229
+
115
230
  export function createIntervalIndex() {
116
231
  const helpers: IIntervalHelpers<Interval> = {
117
- compareEnds: (a: Interval, b: Interval) => a.end - b.end,
118
232
  create: createInterval,
119
233
  };
120
- const lc = new LocalIntervalCollection<Interval>(undefined as any as Client, "", helpers);
234
+ const lc = new LocalIntervalCollection<Interval>(undefined as any as Client, "", helpers, {});
121
235
  return lc;
122
236
  }
123
237
 
@@ -132,15 +246,16 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
132
246
  private readonly client: Client,
133
247
  private readonly label: string,
134
248
  private readonly helpers: IIntervalHelpers<TInterval>,
249
+ private readonly options: Partial<SequenceOptions>,
135
250
  /** Callback invoked each time one of the endpoints of an interval slides. */
136
251
  private readonly onPositionChange?: (
137
252
  interval: TInterval,
138
253
  previousInterval: TInterval,
139
254
  ) => void,
140
255
  ) {
141
- this.overlappingIntervalsIndex = createOverlappingIntervalsIndex(client, helpers);
256
+ this.overlappingIntervalsIndex = new OverlappingIntervalsIndex(client, helpers);
142
257
  this.idIntervalIndex = createIdIntervalIndex<TInterval>();
143
- this.endIntervalIndex = createEndpointIndex(client, helpers);
258
+ this.endIntervalIndex = new EndpointIndex(client, helpers);
144
259
  this.indexes = new Set([
145
260
  this.overlappingIntervalsIndex,
146
261
  this.idIntervalIndex,
@@ -148,7 +263,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
148
263
  ]);
149
264
  }
150
265
 
151
- public createLegacyId(start: number, end: number): string {
266
+ public createLegacyId(start: number | "start" | "end", end: number | "start" | "end"): string {
152
267
  // Create a non-unique ID based on start and end to be used on intervals that come from legacy clients
153
268
  // without ID's.
154
269
  return `${LocalIntervalCollection.legacyIdPrefix}${start}-${end}`;
@@ -203,11 +318,10 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
203
318
  }
204
319
 
205
320
  public createInterval(
206
- start: number,
207
- end: number,
321
+ start: SequencePlace,
322
+ end: SequencePlace,
208
323
  intervalType: IntervalType,
209
324
  op?: ISequencedDocumentMessage,
210
- stickiness: IntervalStickiness = IntervalStickiness.END,
211
325
  ): TInterval {
212
326
  return this.helpers.create(
213
327
  this.label,
@@ -217,19 +331,18 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
217
331
  intervalType,
218
332
  op,
219
333
  undefined,
220
- stickiness,
334
+ this.options.mergeTreeReferencesCanSlideToEndpoint,
221
335
  );
222
336
  }
223
337
 
224
338
  public addInterval(
225
- start: number,
226
- end: number,
339
+ start: SequencePlace,
340
+ end: SequencePlace,
227
341
  intervalType: IntervalType,
228
342
  props?: PropertySet,
229
343
  op?: ISequencedDocumentMessage,
230
- stickiness: IntervalStickiness = IntervalStickiness.END,
231
344
  ) {
232
- const interval: TInterval = this.createInterval(start, end, intervalType, op, stickiness);
345
+ const interval: TInterval = this.createInterval(start, end, intervalType, op);
233
346
  if (interval) {
234
347
  if (!interval.properties) {
235
348
  interval.properties = createMap<any>();
@@ -276,14 +389,19 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
276
389
 
277
390
  public changeInterval(
278
391
  interval: TInterval,
279
- start: number | undefined,
280
- end: number | undefined,
392
+ start: SequencePlace | undefined,
393
+ end: SequencePlace | undefined,
281
394
  op?: ISequencedDocumentMessage,
282
395
  localSeq?: number,
283
396
  ) {
284
- const newInterval = interval.modify(this.label, start, end, op, localSeq) as
285
- | TInterval
286
- | undefined;
397
+ const newInterval = interval.modify(
398
+ this.label,
399
+ start,
400
+ end,
401
+ op,
402
+ localSeq,
403
+ this.options.mergeTreeReferencesCanSlideToEndpoint,
404
+ ) as TInterval | undefined;
287
405
  if (newInterval) {
288
406
  this.removeExistingInterval(interval);
289
407
  this.add(newInterval);
@@ -317,6 +435,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
317
435
  ReferenceType.Transient,
318
436
  ref.properties,
319
437
  ref.slidingPreference,
438
+ ref.canSlideToEndpoint,
320
439
  );
321
440
  };
322
441
  if (interval instanceof SequenceInterval) {
@@ -410,7 +529,6 @@ class IntervalCollectionFactory implements IValueFactory<IntervalCollection<Inte
410
529
  options?: Partial<SequenceOptions>,
411
530
  ): IntervalCollection<Interval> {
412
531
  const helpers: IIntervalHelpers<Interval> = {
413
- compareEnds: (a: Interval, b: Interval) => a.end - b.end,
414
532
  create: createInterval,
415
533
  };
416
534
  const collection = new IntervalCollection<Interval>(helpers, false, emitter, raw, options);
@@ -505,6 +623,9 @@ export function makeOpsMap<T extends ISerializableInterval>(): Map<
505
623
  ]);
506
624
  }
507
625
 
626
+ /**
627
+ * @public
628
+ */
508
629
  export type DeserializeCallback = (properties: PropertySet) => void;
509
630
 
510
631
  class IntervalCollectionIterator<TInterval extends ISerializableInterval>
@@ -542,6 +663,7 @@ class IntervalCollectionIterator<TInterval extends ISerializableInterval>
542
663
 
543
664
  /**
544
665
  * Change events emitted by `IntervalCollection`s
666
+ * @public
545
667
  */
546
668
  export interface IIntervalCollectionEvent<TInterval extends ISerializableInterval> extends IEvent {
547
669
  /**
@@ -600,9 +722,16 @@ export interface IIntervalCollectionEvent<TInterval extends ISerializableInterva
600
722
  );
601
723
  }
602
724
 
725
+ // solely for type checking in the implementation of add - will be removed once
726
+ // deprecated signatures are removed
727
+ const isSequencePlace = (place: any): place is SequencePlace => {
728
+ return typeof place === "number" || typeof place === "string" || place.pos !== undefined;
729
+ };
730
+
603
731
  /**
604
732
  * Collection of intervals that supports addition, modification, removal, and efficient spatial querying.
605
733
  * Changes to this collection will be incur updates on collaborating clients (i.e. they are not local-only).
734
+ * @public
606
735
  */
607
736
  export interface IIntervalCollection<TInterval extends ISerializableInterval>
608
737
  extends TypedEventEmitter<IIntervalCollectionEvent<TInterval>> {
@@ -631,22 +760,101 @@ export interface IIntervalCollection<TInterval extends ISerializableInterval>
631
760
  getIntervalById(id: string): TInterval | undefined;
632
761
  /**
633
762
  * Creates a new interval and add it to the collection.
763
+ * @deprecated call IntervalCollection.add without specifying an intervalType
634
764
  * @param start - interval start position (inclusive)
635
765
  * @param end - interval end position (exclusive)
636
766
  * @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
637
767
  * @param props - properties of the interval
638
- * @param stickiness - {@link (IntervalStickiness:type)} to apply to the added interval.
639
768
  * @returns The created interval
640
- * @remarks See documentation on {@link SequenceInterval} for comments on interval endpoint semantics: there are subtleties
641
- * with how the current half-open behavior is represented.
769
+ * @remarks See documentation on {@link SequenceInterval} for comments on
770
+ * interval endpoint semantics: there are subtleties with how the current
771
+ * half-open behavior is represented.
772
+ *
773
+ * Note that intervals may behave unexpectedly if the entire contents
774
+ * of the string are deleted. In this case, it is possible for one endpoint
775
+ * of the interval to become detached, while the other remains on the string.
776
+ *
777
+ * By adjusting the `side` and `pos` values of the `start` and `end` parameters,
778
+ * it is possible to control whether the interval expands to include content
779
+ * inserted at its start or end.
780
+ *
781
+ * See {@link SequencePlace} for more details on the model.
782
+ *
783
+ * @example
784
+ *
785
+ * Given the string "ABCD":
786
+ *
787
+ *```typescript
788
+ * // Refers to "BC". If any content is inserted before B or after C, this
789
+ * // interval will include that content
790
+ * //
791
+ * // Picture:
792
+ * // \{start\} - A[- B - C -]D - \{end\}
793
+ * // \{start\} - A - B - C - D - \{end\}
794
+ * collection.add(\{ pos: 0, side: Side.After \}, \{ pos: 3, side: Side.Before \}, IntervalType.SlideOnRemove);
795
+ * // Equivalent to specifying the same positions and Side.Before.
796
+ * // Refers to "ABC". Content inserted after C will be included in the
797
+ * // interval, but content inserted before A will not.
798
+ * // \{start\} -[A - B - C -]D - \{end\}
799
+ * // \{start\} - A - B - C - D - \{end\}
800
+ * collection.add(0, 3, IntervalType.SlideOnRemove);
801
+ *```
802
+ *
803
+ * In the case of the first example, if text is deleted,
804
+ *
805
+ * ```typescript
806
+ * // Delete the character "B"
807
+ * string.removeRange(1, 2);
808
+ * ```
809
+ *
810
+ * The start point of the interval will slide to the position immediately
811
+ * before "C", and the same will be true.
812
+ *
813
+ * ```
814
+ * \{start\} - A[- C -]D - \{end\}
815
+ * ```
816
+ *
817
+ * In this case, text inserted immediately before "C" would be included in
818
+ * the interval.
819
+ *
820
+ * ```typescript
821
+ * string.insertText(1, "EFG");
822
+ * ```
823
+ *
824
+ * With the string now being,
825
+ *
826
+ * ```
827
+ * \{start\} - A[- E - F - G - C -]D - \{end\}
828
+ * ```
829
+ *
830
+ * @privateRemarks TODO: ADO:5205 the above comment regarding behavior in
831
+ * the case that the entire interval has been deleted should be resolved at
832
+ * the same time as this ticket
642
833
  */
643
834
  add(
644
- start: number,
645
- end: number,
835
+ start: SequencePlace,
836
+ end: SequencePlace,
646
837
  intervalType: IntervalType,
647
838
  props?: PropertySet,
648
- stickiness?: IntervalStickiness,
649
839
  ): TInterval;
840
+ /**
841
+ * Creates a new interval and add it to the collection.
842
+ * @param start - interval start position (inclusive)
843
+ * @param end - interval end position (exclusive)
844
+ * @param props - properties of the interval
845
+ * @returns - the created interval
846
+ * @remarks - See documentation on {@link SequenceInterval} for comments on interval endpoint semantics: there are subtleties
847
+ * with how the current half-open behavior is represented.
848
+ */
849
+ add({
850
+ start,
851
+ end,
852
+ props,
853
+ }: {
854
+ start: SequencePlace;
855
+ end: SequencePlace;
856
+ props?: PropertySet;
857
+ }): TInterval;
650
858
  /**
651
859
  * Removes an interval from the collection.
652
860
  * @param id - Id of the interval to remove
@@ -663,11 +871,11 @@ export interface IIntervalCollection<TInterval extends ISerializableInterval>
663
871
  /**
664
872
  * Changes the endpoints of an existing interval.
665
873
  * @param id - Id of the interval to change
666
- * @param start - New start value, if defined. `undefined` signifies this endpoint should be left unchanged.
667
- * @param end - New end value, if defined. `undefined` signifies this endpoint should be left unchanged.
874
+ * @param start - New start value. To leave the endpoint unchanged, pass the current value.
875
+ * @param end - New end value. To leave the endpoint unchanged, pass the current value.
668
876
  * @returns the interval that was changed, if it existed in the collection.
669
877
  */
670
- change(id: string, start?: number, end?: number): TInterval | undefined;
878
+ change(id: string, start: SequencePlace, end: SequencePlace): TInterval | undefined;
671
879
 
672
880
  attachDeserializer(onDeserialize: DeserializeCallback): void;
673
881
  /**
@@ -810,13 +1018,18 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
810
1018
  }
811
1019
 
812
1020
  private rebasePositionWithSegmentSlide(
813
- pos: number,
1021
+ pos: number | "start" | "end",
814
1022
  seqNumberFrom: number,
815
1023
  localSeq: number,
816
- ): number | undefined {
1024
+ ): number | "start" | "end" | undefined {
817
1025
  if (!this.client) {
818
1026
  throw new LoggingError("mergeTree client must exist");
819
1027
  }
1028
+
1029
+ if (pos === "start" || pos === "end") {
1030
+ return pos;
1031
+ }
1032
+
820
1033
  const { clientId } = this.client.getCollabWindow();
821
1034
  const { segment, offset } = this.client.getContainingSegment(
822
1035
  pos,
@@ -830,7 +1043,12 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
830
1043
  // if segment is undefined, it slid off the string
831
1044
  assert(segment !== undefined, 0x54e /* No segment found */);
832
1045
 
833
- const segoff = getSlideToSegoff({ segment, offset }) ?? segment;
1046
+ const segoff =
1047
+ getSlideToSegoff(
1048
+ { segment, offset },
1049
+ undefined,
1050
+ this.options.mergeTreeReferencesCanSlideToEndpoint,
1051
+ ) ?? segment;
834
1052
 
835
1053
  // case happens when rebasing op, but concurrently entire string has been deleted
836
1054
  if (segoff.segment === undefined || segoff.offset === undefined) {
@@ -894,12 +1112,28 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
894
1112
  client,
895
1113
  label,
896
1114
  this.helpers,
1115
+ this.options,
897
1116
  (interval, previousInterval) => this.emitChange(interval, previousInterval, true, true),
898
1117
  );
899
1118
  if (this.savedSerializedIntervals) {
900
1119
  for (const serializedInterval of this.savedSerializedIntervals) {
901
1120
  this.localCollection.ensureSerializedId(serializedInterval);
902
- const { start, end, intervalType, properties, stickiness } = serializedInterval;
1121
+ const {
1122
+ start: startPos,
1123
+ end: endPos,
1124
+ intervalType,
1125
+ properties,
1126
+ startSide,
1127
+ endSide,
1128
+ } = serializedInterval;
1129
+ const start =
1130
+ typeof startPos === "number" && startSide !== undefined
1131
+ ? { pos: startPos, side: startSide }
1132
+ : startPos;
1133
+ const end =
1134
+ typeof endPos === "number" && endSide !== undefined
1135
+ ? { pos: endPos, side: endSide }
1136
+ : endPos;
903
1137
  const interval = this.helpers.create(
904
1138
  label,
905
1139
  start,
@@ -908,7 +1142,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
908
1142
  intervalType,
909
1143
  undefined,
910
1144
  true,
911
- stickiness,
1145
+ this.options.mergeTreeReferencesCanSlideToEndpoint,
912
1146
  );
913
1147
  if (properties) {
914
1148
  interval.addProperties(properties);
@@ -965,45 +1199,111 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
965
1199
  return this.localCollection.idIntervalIndex.getIntervalById(id);
966
1200
  }
967
1201
 
1202
+ private assertStickinessEnabled(start: SequencePlace, end: SequencePlace) {
1203
+ if (
1204
+ !(typeof start === "number" && typeof end === "number") &&
1205
+ !this.options.intervalStickinessEnabled
1206
+ ) {
1207
+ throw new UsageError(
1208
+ "attempted to set interval stickiness without enabling `intervalStickinessEnabled` feature flag",
1209
+ );
1210
+ }
1211
+ }
1212
+
968
1213
  /**
969
1214
  * {@inheritdoc IIntervalCollection.add}
1215
+ * @deprecated call IntervalCollection.add without specifying an intervalType
970
1216
  */
971
1217
  public add(
972
- start: number,
973
- end: number,
1218
+ start: SequencePlace,
1219
+ end: SequencePlace,
974
1220
  intervalType: IntervalType,
975
1221
  props?: PropertySet,
976
- stickiness: IntervalStickiness = IntervalStickiness.END,
1222
+ ): TInterval;
1223
+
1224
+ public add({
1225
+ start,
1226
+ end,
1227
+ props,
1228
+ }: {
1229
+ start: SequencePlace;
1230
+ end: SequencePlace;
1231
+ props?: PropertySet;
1232
+ }): TInterval;
1233
+
1234
+ public add(
1235
+ start:
1236
+ | SequencePlace
1237
+ | {
1238
+ start: SequencePlace;
1239
+ end: SequencePlace;
1240
+ props?: PropertySet;
1241
+ },
1242
+ end?: SequencePlace,
1243
+ intervalType?: IntervalType,
1244
+ props?: PropertySet,
977
1245
  ): TInterval {
1246
+ let intStart: SequencePlace;
1247
+ let intEnd: SequencePlace;
1248
+ let type: IntervalType;
1249
+ let properties: PropertySet | undefined;
1250
+
1251
+ if (isSequencePlace(start)) {
1252
+ intStart = start;
1253
+ assert(end !== undefined, 0x7c0 /* end must be defined */);
1254
+ intEnd = end;
1255
+ assert(intervalType !== undefined, 0x7c1 /* intervalType must be defined */);
1256
+ type = intervalType;
1257
+ properties = props;
1258
+ } else {
1259
+ intStart = start.start;
1260
+ intEnd = start.end;
1261
+ type = IntervalType.SlideOnRemove;
1262
+ properties = start.props;
1263
+ }
1264
+
978
1265
  if (!this.localCollection) {
979
1266
  throw new LoggingError("attach must be called prior to adding intervals");
980
1267
  }
981
- if (intervalType & IntervalType.Transient) {
1268
+ if (type & IntervalType.Transient) {
982
1269
  throw new LoggingError("Can not add transient intervals");
983
1270
  }
984
- if (stickiness !== IntervalStickiness.END && !this.options.intervalStickinessEnabled) {
985
- throw new UsageError(
986
- "attempted to set interval stickiness without enabling `intervalStickinessEnabled` feature flag",
987
- );
988
- }
1271
+
1272
+ const { startSide, endSide, startPos, endPos } = endpointPosAndSide(intStart, intEnd);
1273
+
1274
+ assert(
1275
+ startPos !== undefined &&
1276
+ endPos !== undefined &&
1277
+ startSide !== undefined &&
1278
+ endSide !== undefined,
1279
+ 0x793 /* start and end cannot be undefined because they were not passed in as undefined */,
1280
+ );
1281
+
1282
+ const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
1283
+
1284
+ this.assertStickinessEnabled(intStart, intEnd);
989
1285
 
990
1286
  const interval: TInterval = this.localCollection.addInterval(
991
- start,
992
- end,
993
- intervalType,
994
- props,
995
- undefined,
996
- stickiness,
1287
+ toSequencePlace(startPos, startSide),
1288
+ toSequencePlace(endPos, endSide),
1289
+ type,
1290
+ properties,
997
1291
  );
998
1292
 
999
1293
  if (interval) {
1000
- const serializedInterval = {
1001
- end,
1002
- intervalType,
1294
+ if (!this.isCollaborating && interval instanceof SequenceInterval) {
1295
+ setSlideOnRemove(interval.start);
1296
+ setSlideOnRemove(interval.end);
1297
+ }
1298
+ const serializedInterval: ISerializedInterval = {
1299
+ start: startPos,
1300
+ end: endPos,
1301
+ intervalType: type,
1003
1302
  properties: interval.properties,
1004
1303
  sequenceNumber: this.client?.getCurrentSeq() ?? 0,
1005
- start,
1006
1304
  stickiness,
1305
+ startSide,
1306
+ endSide,
1007
1307
  };
1008
1308
  const localSeq = this.getNextLocalSeq();
1009
1309
  this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
@@ -1080,8 +1380,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1080
1380
 
1081
1381
  const interval = this.getIntervalById(id);
1082
1382
  if (interval) {
1083
- // Pass Unassigned as the sequence number to indicate that this is a local op that is waiting for an ack.
1084
- const deltaProps = interval.addProperties(props, true, UnassignedSequenceNumber);
1383
+ const deltaProps = interval.addProperties(
1384
+ props,
1385
+ true,
1386
+ this.isCollaborating ? UnassignedSequenceNumber : UniversalSequenceNumber,
1387
+ );
1085
1388
  const serializedInterval: ISerializedInterval = interval.serialize();
1086
1389
 
1087
1390
  // Emit a change op that will only change properties. Add the ID to
@@ -1101,7 +1404,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1101
1404
  /**
1102
1405
  * {@inheritdoc IIntervalCollection.change}
1103
1406
  */
1104
- public change(id: string, start?: number, end?: number): TInterval | undefined {
1407
+ public change(id: string, start: SequencePlace, end: SequencePlace) {
1105
1408
  if (!this.localCollection) {
1106
1409
  throw new LoggingError("Attach must be called before accessing intervals");
1107
1410
  }
@@ -1117,9 +1420,18 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1117
1420
  if (!newInterval) {
1118
1421
  return undefined;
1119
1422
  }
1423
+ if (!this.isCollaborating && newInterval instanceof SequenceInterval) {
1424
+ setSlideOnRemove(newInterval.start);
1425
+ setSlideOnRemove(newInterval.end);
1426
+ }
1120
1427
  const serializedInterval: SerializedIntervalDelta = interval.serialize();
1121
- serializedInterval.start = start;
1122
- serializedInterval.end = end;
1428
+ const { startPos, startSide, endPos, endSide } = endpointPosAndSide(start, end);
1429
+ const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
1430
+ serializedInterval.start = startPos;
1431
+ serializedInterval.end = endPos;
1432
+ serializedInterval.startSide = startSide;
1433
+ serializedInterval.endSide = endSide;
1434
+ serializedInterval.stickiness = stickiness;
1123
1435
  // Emit a property bag containing only the ID, as we don't intend for this op to change any properties.
1124
1436
  serializedInterval.properties = {
1125
1437
  [reservedIntervalIdKey]: interval.getIntervalId(),
@@ -1135,7 +1447,14 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1135
1447
  return undefined;
1136
1448
  }
1137
1449
 
1450
+ private get isCollaborating(): boolean {
1451
+ return this.client?.getCollabWindow().collaborating ?? false;
1452
+ }
1453
+
1138
1454
  private addPendingChange(id: string, serializedInterval: SerializedIntervalDelta) {
1455
+ if (!this.isCollaborating) {
1456
+ return;
1457
+ }
1139
1458
  if (serializedInterval.start !== undefined) {
1140
1459
  this.addPendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
1141
1460
  }
@@ -1241,8 +1560,8 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1241
1560
  } else {
1242
1561
  // If there are pending changes with this ID, don't apply the remote start/end change, as the local ack
1243
1562
  // should be the winning change.
1244
- let start: number | undefined;
1245
- let end: number | undefined;
1563
+ let start: number | "start" | "end" | undefined;
1564
+ let end: number | "start" | "end" | undefined;
1246
1565
  // Track pending start/end independently of one another.
1247
1566
  if (!this.hasPendingChangeStart(id)) {
1248
1567
  start = serializedInterval.start;
@@ -1256,7 +1575,12 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1256
1575
  // If changeInterval gives us a new interval, work with that one. Otherwise keep working with
1257
1576
  // the one we originally found in the tree.
1258
1577
  newInterval =
1259
- this.localCollection.changeInterval(interval, start, end, op) ?? interval;
1578
+ this.localCollection.changeInterval(
1579
+ interval,
1580
+ toOptionalSequencePlace(start, serializedInterval.startSide),
1581
+ toOptionalSequencePlace(end, serializedInterval.endSide),
1582
+ op,
1583
+ ) ?? interval;
1260
1584
  }
1261
1585
  const deltaProps = newInterval.addProperties(newProps, true, op.sequenceNumber);
1262
1586
  if (this.onDeserialize) {
@@ -1312,7 +1636,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1312
1636
  throw new LoggingError("attachSequence must be called");
1313
1637
  }
1314
1638
 
1315
- const { intervalType, properties } = serializedInterval;
1639
+ const { intervalType, properties, stickiness, startSide, endSide } = serializedInterval;
1316
1640
 
1317
1641
  const { start: startRebased, end: endRebased } =
1318
1642
  this.localSeqToRebasedInterval.get(localSeq) ?? this.computeRebasedPositions(localSeq);
@@ -1326,10 +1650,14 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1326
1650
  intervalType,
1327
1651
  sequenceNumber: this.client?.getCurrentSeq() ?? 0,
1328
1652
  properties,
1653
+ stickiness,
1654
+ startSide,
1655
+ endSide,
1329
1656
  };
1330
1657
 
1331
1658
  if (
1332
1659
  opName === "change" &&
1660
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when .hasPendingChangeStart returns false.
1333
1661
  (this.hasPendingChangeStart(intervalId) || this.hasPendingChangeEnd(intervalId))
1334
1662
  ) {
1335
1663
  this.removePendingChange(serializedInterval);
@@ -1357,8 +1685,8 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1357
1685
  // updates the local client's state to be consistent with the emitted op.
1358
1686
  this.localCollection?.changeInterval(
1359
1687
  localInterval,
1360
- startRebased,
1361
- endRebased,
1688
+ toOptionalSequencePlace(startRebased, startSide),
1689
+ toOptionalSequencePlace(endRebased, endSide),
1362
1690
  undefined,
1363
1691
  localSeq,
1364
1692
  );
@@ -1377,7 +1705,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1377
1705
  if (segoff.segment?.localRefs?.has(lref) !== true) {
1378
1706
  return undefined;
1379
1707
  }
1380
- const newSegoff = getSlideToSegoff(segoff);
1708
+ const newSegoff = getSlideToSegoff(
1709
+ segoff,
1710
+ undefined,
1711
+ this.options.mergeTreeReferencesCanSlideToEndpoint,
1712
+ );
1381
1713
  const value: { segment: ISegment | undefined; offset: number | undefined } | undefined =
1382
1714
  segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset
1383
1715
  ? undefined
@@ -1385,14 +1717,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1385
1717
  return value;
1386
1718
  }
1387
1719
 
1388
- private setSlideOnRemove(lref: LocalReferencePosition) {
1389
- let refType = lref.refType;
1390
- refType = refType & ~ReferenceType.StayOnRemove;
1391
- refType = refType | ReferenceType.SlideOnRemove;
1392
- lref.refType = refType;
1393
- }
1394
-
1395
- private ackInterval(interval: TInterval, op: ISequencedDocumentMessage) {
1720
+ private ackInterval(interval: TInterval, op: ISequencedDocumentMessage): void {
1396
1721
  // Only SequenceIntervals need potential sliding
1397
1722
  if (!(interval instanceof SequenceInterval)) {
1398
1723
  return;
@@ -1413,11 +1738,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1413
1738
  const hasPendingEndChange = this.hasPendingChangeEnd(id);
1414
1739
 
1415
1740
  if (!hasPendingStartChange) {
1416
- this.setSlideOnRemove(interval.start);
1741
+ setSlideOnRemove(interval.start);
1417
1742
  }
1418
1743
 
1419
1744
  if (!hasPendingEndChange) {
1420
- this.setSlideOnRemove(interval.end);
1745
+ setSlideOnRemove(interval.end);
1421
1746
  }
1422
1747
 
1423
1748
  const needsStartUpdate = newStart !== undefined && !hasPendingStartChange;
@@ -1446,7 +1771,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1446
1771
  newStart,
1447
1772
  interval.start.refType,
1448
1773
  op,
1774
+ undefined,
1775
+ undefined,
1449
1776
  startReferenceSlidingPreference(interval.stickiness),
1777
+ startReferenceSlidingPreference(interval.stickiness) ===
1778
+ SlidingPreference.BACKWARD,
1450
1779
  );
1451
1780
  if (props) {
1452
1781
  interval.start.addProperties(props);
@@ -1464,7 +1793,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1464
1793
  newEnd,
1465
1794
  interval.end.refType,
1466
1795
  op,
1796
+ undefined,
1797
+ undefined,
1467
1798
  endReferenceSlidingPreference(interval.stickiness),
1799
+ endReferenceSlidingPreference(interval.stickiness) ===
1800
+ SlidingPreference.FORWARD,
1468
1801
  );
1469
1802
  if (props) {
1470
1803
  interval.end.addProperties(props);
@@ -1508,12 +1841,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1508
1841
  this.localCollection.ensureSerializedId(serializedInterval);
1509
1842
 
1510
1843
  const interval: TInterval = this.localCollection.addInterval(
1511
- serializedInterval.start,
1512
- serializedInterval.end,
1844
+ toSequencePlace(serializedInterval.start, serializedInterval.startSide ?? Side.Before),
1845
+ toSequencePlace(serializedInterval.end, serializedInterval.endSide ?? Side.Before),
1513
1846
  serializedInterval.intervalType,
1514
1847
  serializedInterval.properties,
1515
1848
  op,
1516
- serializedInterval.stickiness,
1517
1849
  );
1518
1850
 
1519
1851
  if (interval) {
@@ -1691,8 +2023,16 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1691
2023
  }
1692
2024
  }
1693
2025
 
2026
+ function setSlideOnRemove(lref: LocalReferencePosition) {
2027
+ let refType = lref.refType;
2028
+ refType = refType & ~ReferenceType.StayOnRemove;
2029
+ refType = refType | ReferenceType.SlideOnRemove;
2030
+ lref.refType = refType;
2031
+ }
2032
+
1694
2033
  /**
1695
2034
  * Information that identifies an interval within a `Sequence`.
2035
+ * @public
1696
2036
  */
1697
2037
  export interface IntervalLocator {
1698
2038
  /**
@@ -1710,6 +2050,7 @@ export interface IntervalLocator {
1710
2050
  * @returns undefined if the reference position is not the endpoint of any interval (e.g. it was created
1711
2051
  * on the merge tree directly by app code), otherwise an {@link IntervalLocator} for the interval this
1712
2052
  * endpoint is a part of.
2053
+ * @public
1713
2054
  */
1714
2055
  export function intervalLocatorFromEndpoint(
1715
2056
  potentialEndpoint: LocalReferencePosition,