@y/y 14.0.0-rc.2 → 14.0.0-rc.21

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 (166) hide show
  1. package/README.md +36 -12
  2. package/dist/src/index.d.ts +24 -1
  3. package/dist/src/index.d.ts.map +1 -1
  4. package/dist/src/structs/AbstractStruct.d.ts +14 -17
  5. package/dist/src/structs/AbstractStruct.d.ts.map +1 -1
  6. package/dist/src/structs/GC.d.ts +9 -14
  7. package/dist/src/structs/GC.d.ts.map +1 -1
  8. package/dist/src/structs/Item.d.ts +569 -31
  9. package/dist/src/structs/Item.d.ts.map +1 -1
  10. package/dist/src/structs/Skip.d.ts +9 -11
  11. package/dist/src/structs/Skip.d.ts.map +1 -1
  12. package/dist/src/utils/BlockSet.d.ts +8 -7
  13. package/dist/src/utils/BlockSet.d.ts.map +1 -1
  14. package/dist/src/utils/Doc.d.ts +4 -9
  15. package/dist/src/utils/Doc.d.ts.map +1 -1
  16. package/dist/src/utils/ID.d.ts +0 -1
  17. package/dist/src/utils/ID.d.ts.map +1 -1
  18. package/dist/src/utils/RelativePosition.d.ts +2 -5
  19. package/dist/src/utils/RelativePosition.d.ts.map +1 -1
  20. package/dist/src/utils/Renderer.d.ts +144 -0
  21. package/dist/src/utils/Renderer.d.ts.map +1 -0
  22. package/dist/src/utils/Snapshot.d.ts +7 -11
  23. package/dist/src/utils/Snapshot.d.ts.map +1 -1
  24. package/dist/src/utils/StructStore.d.ts +45 -23
  25. package/dist/src/utils/StructStore.d.ts.map +1 -1
  26. package/dist/src/utils/Transaction.d.ts +11 -21
  27. package/dist/src/utils/Transaction.d.ts.map +1 -1
  28. package/dist/src/utils/UndoManager.d.ts +13 -14
  29. package/dist/src/utils/UndoManager.d.ts.map +1 -1
  30. package/dist/src/utils/UpdateDecoder.d.ts +1 -1
  31. package/dist/src/utils/UpdateDecoder.d.ts.map +1 -1
  32. package/dist/src/utils/UpdateEncoder.d.ts +0 -1
  33. package/dist/src/utils/UpdateEncoder.d.ts.map +1 -1
  34. package/dist/src/utils/YEvent.d.ts +22 -26
  35. package/dist/src/utils/YEvent.d.ts.map +1 -1
  36. package/dist/src/utils/content-helper.d.ts +2 -0
  37. package/dist/src/utils/content-helper.d.ts.map +1 -0
  38. package/dist/src/utils/delta-helpers.d.ts +2 -3
  39. package/dist/src/utils/delta-helpers.d.ts.map +1 -1
  40. package/dist/src/utils/encoding-helpers.d.ts +6 -0
  41. package/dist/src/utils/encoding-helpers.d.ts.map +1 -0
  42. package/dist/src/utils/encoding.d.ts +16 -18
  43. package/dist/src/utils/encoding.d.ts.map +1 -1
  44. package/dist/src/utils/ids.d.ts +331 -0
  45. package/dist/src/utils/ids.d.ts.map +1 -0
  46. package/dist/src/utils/isParentOf.d.ts +1 -2
  47. package/dist/src/utils/isParentOf.d.ts.map +1 -1
  48. package/dist/src/utils/logging.d.ts +0 -1
  49. package/dist/src/utils/logging.d.ts.map +1 -1
  50. package/dist/src/utils/meta.d.ts +15 -64
  51. package/dist/src/utils/meta.d.ts.map +1 -1
  52. package/dist/src/utils/renderer-helpers.d.ts +99 -0
  53. package/dist/src/utils/renderer-helpers.d.ts.map +1 -0
  54. package/dist/src/utils/schemas.d.ts +3 -0
  55. package/dist/src/utils/schemas.d.ts.map +1 -0
  56. package/dist/src/utils/transaction-helpers.d.ts +18 -0
  57. package/dist/src/utils/transaction-helpers.d.ts.map +1 -0
  58. package/dist/src/utils/updates.d.ts +19 -25
  59. package/dist/src/utils/updates.d.ts.map +1 -1
  60. package/dist/src/ytype.d.ts +144 -41
  61. package/dist/src/ytype.d.ts.map +1 -1
  62. package/global.d.ts +53 -0
  63. package/package.json +12 -16
  64. package/src/index.js +32 -124
  65. package/src/structs/AbstractStruct.js +21 -16
  66. package/src/structs/GC.js +15 -20
  67. package/src/structs/Item.js +992 -318
  68. package/src/structs/Skip.js +16 -19
  69. package/src/utils/BlockSet.js +20 -20
  70. package/src/utils/Doc.js +18 -29
  71. package/src/utils/ID.js +0 -2
  72. package/src/utils/RelativePosition.js +15 -25
  73. package/src/utils/{AttributionManager.js → Renderer.js} +42 -197
  74. package/src/utils/Snapshot.js +15 -37
  75. package/src/utils/StructStore.js +89 -227
  76. package/src/utils/Transaction.js +89 -321
  77. package/src/utils/UndoManager.js +157 -19
  78. package/src/utils/UpdateDecoder.js +2 -3
  79. package/src/utils/UpdateEncoder.js +0 -4
  80. package/src/utils/YEvent.js +20 -26
  81. package/src/utils/content-helper.js +0 -0
  82. package/src/utils/delta-helpers.js +21 -42
  83. package/src/utils/encoding-helpers.js +110 -0
  84. package/src/utils/encoding.js +197 -122
  85. package/src/utils/ids.js +1527 -0
  86. package/src/utils/isParentOf.js +2 -4
  87. package/src/utils/logging.js +0 -4
  88. package/src/utils/meta.js +57 -46
  89. package/src/utils/renderer-helpers.js +110 -0
  90. package/src/utils/schemas.js +3 -0
  91. package/src/utils/transaction-helpers.js +413 -0
  92. package/src/utils/updates.js +24 -146
  93. package/src/ytype.js +626 -255
  94. package/tests/testHelper.js +10 -12
  95. package/dist/src/internals.d.ts +0 -36
  96. package/dist/src/internals.d.ts.map +0 -1
  97. package/dist/src/structs/ContentAny.d.ts +0 -67
  98. package/dist/src/structs/ContentAny.d.ts.map +0 -1
  99. package/dist/src/structs/ContentBinary.d.ts +0 -64
  100. package/dist/src/structs/ContentBinary.d.ts.map +0 -1
  101. package/dist/src/structs/ContentDeleted.d.ts +0 -64
  102. package/dist/src/structs/ContentDeleted.d.ts.map +0 -1
  103. package/dist/src/structs/ContentDoc.d.ts +0 -72
  104. package/dist/src/structs/ContentDoc.d.ts.map +0 -1
  105. package/dist/src/structs/ContentEmbed.d.ts +0 -67
  106. package/dist/src/structs/ContentEmbed.d.ts.map +0 -1
  107. package/dist/src/structs/ContentFormat.d.ts +0 -69
  108. package/dist/src/structs/ContentFormat.d.ts.map +0 -1
  109. package/dist/src/structs/ContentJSON.d.ts +0 -70
  110. package/dist/src/structs/ContentJSON.d.ts.map +0 -1
  111. package/dist/src/structs/ContentString.d.ts +0 -70
  112. package/dist/src/structs/ContentString.d.ts.map +0 -1
  113. package/dist/src/structs/ContentType.d.ts +0 -77
  114. package/dist/src/structs/ContentType.d.ts.map +0 -1
  115. package/dist/src/utils/AttributionManager.d.ts +0 -238
  116. package/dist/src/utils/AttributionManager.d.ts.map +0 -1
  117. package/dist/src/utils/IdMap.d.ts +0 -164
  118. package/dist/src/utils/IdMap.d.ts.map +0 -1
  119. package/dist/src/utils/IdSet.d.ts +0 -163
  120. package/dist/src/utils/IdSet.d.ts.map +0 -1
  121. package/dist/tests/IdMap.tests.d.ts +0 -9
  122. package/dist/tests/IdMap.tests.d.ts.map +0 -1
  123. package/dist/tests/IdSet.tests.d.ts +0 -9
  124. package/dist/tests/IdSet.tests.d.ts.map +0 -1
  125. package/dist/tests/attribution.tests.d.ts +0 -9
  126. package/dist/tests/attribution.tests.d.ts.map +0 -1
  127. package/dist/tests/compatibility.tests.d.ts +0 -5
  128. package/dist/tests/compatibility.tests.d.ts.map +0 -1
  129. package/dist/tests/delta.tests.d.ts +0 -7
  130. package/dist/tests/delta.tests.d.ts.map +0 -1
  131. package/dist/tests/doc.tests.d.ts +0 -13
  132. package/dist/tests/doc.tests.d.ts.map +0 -1
  133. package/dist/tests/encoding.tests.d.ts +0 -5
  134. package/dist/tests/encoding.tests.d.ts.map +0 -1
  135. package/dist/tests/index.d.ts +0 -2
  136. package/dist/tests/index.d.ts.map +0 -1
  137. package/dist/tests/relativePositions.tests.d.ts +0 -11
  138. package/dist/tests/relativePositions.tests.d.ts.map +0 -1
  139. package/dist/tests/snapshot.tests.d.ts +0 -14
  140. package/dist/tests/snapshot.tests.d.ts.map +0 -1
  141. package/dist/tests/testHelper.d.ts +0 -171
  142. package/dist/tests/testHelper.d.ts.map +0 -1
  143. package/dist/tests/undo-redo.tests.d.ts +0 -27
  144. package/dist/tests/undo-redo.tests.d.ts.map +0 -1
  145. package/dist/tests/updates.tests.d.ts +0 -26
  146. package/dist/tests/updates.tests.d.ts.map +0 -1
  147. package/dist/tests/y-array.tests.d.ts +0 -43
  148. package/dist/tests/y-array.tests.d.ts.map +0 -1
  149. package/dist/tests/y-map.tests.d.ts +0 -42
  150. package/dist/tests/y-map.tests.d.ts.map +0 -1
  151. package/dist/tests/y-text.tests.d.ts +0 -49
  152. package/dist/tests/y-text.tests.d.ts.map +0 -1
  153. package/dist/tests/y-xml.tests.d.ts +0 -14
  154. package/dist/tests/y-xml.tests.d.ts.map +0 -1
  155. package/src/internals.js +0 -35
  156. package/src/structs/ContentAny.js +0 -115
  157. package/src/structs/ContentBinary.js +0 -93
  158. package/src/structs/ContentDeleted.js +0 -101
  159. package/src/structs/ContentDoc.js +0 -141
  160. package/src/structs/ContentEmbed.js +0 -98
  161. package/src/structs/ContentFormat.js +0 -105
  162. package/src/structs/ContentJSON.js +0 -119
  163. package/src/structs/ContentString.js +0 -113
  164. package/src/structs/ContentType.js +0 -152
  165. package/src/utils/IdMap.js +0 -673
  166. package/src/utils/IdSet.js +0 -825
@@ -1,27 +1,22 @@
1
- import {
2
- getState,
3
- writeStructsFromTransaction,
4
- writeIdSet,
5
- getStateVector,
6
- findIndexSS,
7
- callEventHandlerListeners,
8
- createIdSet,
9
- Item,
10
- generateNewClientId,
11
- createID,
12
- iterateStructsByIdSet,
13
- ContentFormat,
14
- IdSet, UpdateEncoderV1, UpdateEncoderV2, GC, StructStore, AbstractStruct, YEvent, Doc // eslint-disable-line
15
- } from '../internals.js'
16
-
17
- import { YType } from '../ytype.js' // eslint-disable-line
18
1
  import * as error from 'lib0/error'
19
2
  import * as map from 'lib0/map'
20
3
  import * as math from 'lib0/math'
21
- import * as set from 'lib0/set'
22
4
  import * as logging from 'lib0/logging'
23
5
  import { callAll } from 'lib0/function'
24
6
 
7
+ import { ContentFormat } from '../structs/Item.js'
8
+ import { getStateVector } from './StructStore.js'
9
+ import { callEventHandlerListeners } from './EventHandler.js'
10
+ import { createIdSet, iterateStructsByIdSet } from './ids.js'
11
+ import { GC } from '../structs/GC.js'
12
+ import { YEvent } from './YEvent.js'
13
+ import { writeUpdateMessageFromTransaction } from './encoding-helpers.js'
14
+ import { UpdateEncoderV1, UpdateEncoderV2 } from './UpdateEncoder.js'
15
+ import { findIndexSS, updateCurrentFormats, cleanupFormattingGap, tryGcDeleteSet, tryMerge, tryToMergeWithLefts, cleanupContextlessFormattingGap } from './transaction-helpers.js'
16
+ import * as random from 'lib0/random'
17
+
18
+ export const generateNewClientId = random.uint53
19
+
25
20
  /**
26
21
  * A transaction is created for every change on the Yjs model. It is possible
27
22
  * to bundle changes on the Yjs model in a single transaction to
@@ -31,18 +26,18 @@ import { callAll } from 'lib0/function'
31
26
  *
32
27
  * @example
33
28
  * const ydoc = new Y.Doc()
34
- * const map = ydoc.getMap('map')
29
+ * const map = ydoc.get('map')
35
30
  * // Log content when change is triggered
36
31
  * map.observe(() => {
37
32
  * console.log('change triggered')
38
33
  * })
39
34
  * // Each change on the map type triggers a log message:
40
- * map.set('a', 0) // => "change triggered"
41
- * map.set('b', 0) // => "change triggered"
35
+ * map.setAttr('a', 0) // => "change triggered"
36
+ * map.setAttr('b', 0) // => "change triggered"
42
37
  * // When put in a transaction, it will trigger the log after the transaction:
43
38
  * ydoc.transact(() => {
44
- * map.set('a', 1)
45
- * map.set('b', 1)
39
+ * map.setAttr('a', 1)
40
+ * map.setAttr('b', 1)
46
41
  * }) // => "change triggered"
47
42
  *
48
43
  * @public
@@ -170,257 +165,17 @@ export class Transaction {
170
165
  }
171
166
  }
172
167
 
173
- /**
174
- * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
175
- * @param {Transaction} transaction
176
- * @return {boolean} Whether data was written.
177
- */
178
- export const writeUpdateMessageFromTransaction = (encoder, transaction) => {
179
- if (transaction.deleteSet.clients.size === 0 && transaction.insertSet.clients.size === 0) {
180
- return false
181
- }
182
- writeStructsFromTransaction(encoder, transaction)
183
- writeIdSet(encoder, transaction.deleteSet)
184
- return true
185
- }
186
-
187
- /**
188
- * @param {Transaction} transaction
189
- *
190
- * @private
191
- * @function
192
- */
193
- export const nextID = transaction => {
194
- const y = transaction.doc
195
- return createID(y.clientID, getState(y.store, y.clientID))
196
- }
197
-
198
- /**
199
- * If `type.parent` was added in current transaction, `type` technically
200
- * did not change, it was just added and we should not fire events for `type`.
201
- *
202
- * @param {Transaction} transaction
203
- * @param {YType} type
204
- * @param {string|null} parentSub
205
- */
206
- export const addChangedTypeToTransaction = (transaction, type, parentSub) => {
207
- const item = type._item
208
- if (item === null || (!item.deleted && !transaction.insertSet.hasId(item.id))) {
209
- map.setIfUndefined(transaction.changed, type, set.create).add(parentSub)
210
- }
211
- }
212
-
213
- /**
214
- * @param {Array<AbstractStruct>} structs
215
- * @param {number} pos
216
- * @return {number} # of merged structs
217
- */
218
- const tryToMergeWithLefts = (structs, pos) => {
219
- let right = structs[pos]
220
- let left = structs[pos - 1]
221
- let i = pos
222
- for (; i > 0; right = left, left = structs[--i - 1]) {
223
- if (left.deleted === right.deleted && left.constructor === right.constructor) {
224
- if (left.mergeWith(right)) {
225
- if (right instanceof Item && right.parentSub !== null && /** @type {YType} */ (right.parent)._map.get(right.parentSub) === right) {
226
- /** @type {YType} */ (right.parent)._map.set(right.parentSub, /** @type {Item} */ (left))
227
- }
228
- continue
229
- }
230
- }
231
- break
232
- }
233
- const merged = pos - i
234
- if (merged) {
235
- // remove all merged structs from the array
236
- structs.splice(pos + 1 - merged, merged)
237
- }
238
- return merged
239
- }
240
-
241
- /**
242
- * @param {Transaction} tr
243
- * @param {IdSet} ds
244
- * @param {function(Item):boolean} gcFilter
245
- */
246
- const tryGcDeleteSet = (tr, ds, gcFilter) => {
247
- for (const [client, _deleteItems] of ds.clients.entries()) {
248
- const deleteItems = _deleteItems.getIds()
249
- const structs = /** @type {Array<GC|Item>} */ (tr.doc.store.clients.get(client))
250
- for (let di = deleteItems.length - 1; di >= 0; di--) {
251
- const deleteItem = deleteItems[di]
252
- const endDeleteItemClock = deleteItem.clock + deleteItem.len
253
- for (
254
- let si = findIndexSS(structs, deleteItem.clock), struct = structs[si];
255
- si < structs.length && struct.id.clock < endDeleteItemClock;
256
- struct = structs[++si]
257
- ) {
258
- const struct = structs[si]
259
- if (deleteItem.clock + deleteItem.len <= struct.id.clock) {
260
- break
261
- }
262
- if (struct instanceof Item && struct.deleted && !struct.keep && gcFilter(struct)) {
263
- struct.gc(tr, false)
264
- }
265
- }
266
- }
267
- }
268
- }
269
-
270
- /**
271
- * @param {IdSet} ds
272
- * @param {StructStore} store
273
- */
274
- const tryMerge = (ds, store) => {
275
- // try to merge deleted / gc'd items
276
- // merge from right to left for better efficiency and so we don't miss any merge targets
277
- ds.clients.forEach((_deleteItems, client) => {
278
- const deleteItems = _deleteItems.getIds()
279
- const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client))
280
- for (let di = deleteItems.length - 1; di >= 0; di--) {
281
- const deleteItem = deleteItems[di]
282
- // start with merging the item next to the last deleted item
283
- const mostRightIndexToCheck = math.min(structs.length - 1, 1 + findIndexSS(structs, deleteItem.clock + deleteItem.len - 1))
284
- for (
285
- let si = mostRightIndexToCheck, struct = structs[si];
286
- si > 0 && struct.id.clock >= deleteItem.clock;
287
- struct = structs[si]
288
- ) {
289
- si -= 1 + tryToMergeWithLefts(structs, si)
290
- }
291
- }
292
- })
293
- }
294
-
295
- /**
296
- * @param {Transaction} tr
297
- * @param {IdSet} idset
298
- * @param {function(Item):boolean} gcFilter
299
- */
300
- export const tryGc = (tr, idset, gcFilter) => {
301
- tryGcDeleteSet(tr, idset, gcFilter)
302
- tryMerge(idset, tr.doc.store)
303
- }
304
-
305
- /**
306
- * @param {Transaction} transaction
307
- * @param {Item | null} item
308
- */
309
- const cleanupContextlessFormattingGap = (transaction, item) => {
310
- if (!transaction.doc.cleanupFormatting) return 0
311
- // iterate until item.right is null or content
312
- while (item && item.right && (item.right.deleted || !item.right.countable)) {
313
- item = item.right
314
- }
315
- const attrs = new Set()
316
- // iterate back until a content item is found
317
- while (item && (item.deleted || !item.countable)) {
318
- if (!item.deleted && item.content.constructor === ContentFormat) {
319
- const key = /** @type {ContentFormat} */ (item.content).key
320
- if (attrs.has(key)) {
321
- item.delete(transaction)
322
- transaction.cleanUps.add(item.id.client, item.id.clock, item.length)
323
- } else {
324
- attrs.add(key)
325
- }
326
- }
327
- item = item.left
328
- }
329
- }
330
-
331
- /**
332
- * @param {Map<string,any>} currentAttributes
333
- * @param {ContentFormat} format
334
- *
335
- * @private
336
- * @function
337
- */
338
- const updateCurrentAttributes = (currentAttributes, { key, value }) => {
339
- if (value === null) {
340
- currentAttributes.delete(key)
341
- } else {
342
- currentAttributes.set(key, value)
343
- }
344
- }
345
-
346
- /**
347
- * Call this function after string content has been deleted in order to
348
- * clean up formatting Items.
349
- *
350
- * @param {Transaction} transaction
351
- * @param {Item} start
352
- * @param {Item|null} curr exclusive end, automatically iterates to the next Content Item
353
- * @param {Map<string,any>} startAttributes
354
- * @param {Map<string,any>} currAttributes
355
- * @return {number} The amount of formatting Items deleted.
356
- *
357
- * @function
358
- */
359
- export const cleanupFormattingGap = (transaction, start, curr, startAttributes, currAttributes) => {
360
- if (!transaction.doc.cleanupFormatting) return 0
361
- /**
362
- * @type {Item|null}
363
- */
364
- let end = start
365
- /**
366
- * @type {Map<string,ContentFormat>}
367
- */
368
- const endFormats = map.create()
369
- while (end && (!end.countable || end.deleted)) {
370
- if (!end.deleted && end.content.constructor === ContentFormat) {
371
- const cf = /** @type {ContentFormat} */ (end.content)
372
- endFormats.set(cf.key, cf)
373
- }
374
- end = end.right
375
- }
376
- let cleanups = 0
377
- let reachedCurr = false
378
- while (start !== end) {
379
- if (curr === start) {
380
- reachedCurr = true
381
- }
382
- if (!start.deleted) {
383
- const content = start.content
384
- switch (content.constructor) {
385
- case ContentFormat: {
386
- const { key, value } = /** @type {ContentFormat} */ (content)
387
- const startAttrValue = startAttributes.get(key) ?? null
388
- if (endFormats.get(key) !== content || startAttrValue === value) {
389
- // Either this format is overwritten or it is not necessary because the attribute already existed.
390
- start.delete(transaction)
391
- transaction.cleanUps.add(start.id.client, start.id.clock, start.length)
392
- cleanups++
393
- if (!reachedCurr && (currAttributes.get(key) ?? null) === value && startAttrValue !== value) {
394
- if (startAttrValue === null) {
395
- currAttributes.delete(key)
396
- } else {
397
- currAttributes.set(key, startAttrValue)
398
- }
399
- }
400
- }
401
- if (!reachedCurr && !start.deleted) {
402
- updateCurrentAttributes(currAttributes, /** @type {ContentFormat} */ (content))
403
- }
404
- break
405
- }
406
- }
407
- }
408
- start = /** @type {Item} */ (start.right)
409
- }
410
- return cleanups
411
- }
412
-
413
168
  /**
414
169
  * This function is experimental and subject to change / be removed.
415
170
  *
416
- * Ideally, we don't need this function at all. Formatting attributes should be cleaned up
171
+ * Ideally, we don't need this function at all. Formats should be cleaned up
417
172
  * automatically after each change. This function iterates twice over the complete YText type
418
- * and removes unnecessary formatting attributes. This is also helpful for testing.
173
+ * and removes unnecessary formats. This is also helpful for testing.
419
174
  *
420
175
  * This function won't be exported anymore as soon as there is confidence that the YText type works as intended.
421
176
  *
422
177
  * @param {YType} type
423
- * @return {number} How many formatting attributes have been cleaned up.
178
+ * @return {number} How many formats have been cleaned up.
424
179
  */
425
180
  export const cleanupYTextFormatting = type => {
426
181
  if (!type.doc?.cleanupFormatting) return 0
@@ -428,17 +183,17 @@ export const cleanupYTextFormatting = type => {
428
183
  transact(/** @type {Doc} */ (type.doc), transaction => {
429
184
  let start = /** @type {Item} */ (type._start)
430
185
  let end = type._start
431
- let startAttributes = map.create()
432
- const currentAttributes = map.copy(startAttributes)
186
+ let startFormats = map.create()
187
+ const currentFormats = map.copy(startFormats)
433
188
  while (end) {
434
189
  if (end.deleted === false) {
435
190
  switch (end.content.constructor) {
436
191
  case ContentFormat:
437
- updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (end.content))
192
+ updateCurrentFormats(currentFormats, /** @type {ContentFormat} */ (end.content))
438
193
  break
439
194
  default:
440
- res += cleanupFormattingGap(transaction, start, end, startAttributes, currentAttributes)
441
- startAttributes = map.copy(currentAttributes)
195
+ res += cleanupFormattingGap(transaction, start, end, startFormats, currentFormats)
196
+ startFormats = map.copy(currentFormats)
442
197
  start = end
443
198
  break
444
199
  }
@@ -449,50 +204,6 @@ export const cleanupYTextFormatting = type => {
449
204
  return res
450
205
  }
451
206
 
452
- /**
453
- * This will be called by the transaction once the event handlers are called to potentially cleanup
454
- * formatting attributes.
455
- *
456
- * @param {Transaction} transaction
457
- */
458
- export const cleanupYTextAfterTransaction = transaction => {
459
- /**
460
- * @type {Set<YType>}
461
- */
462
- const needFullCleanup = new Set()
463
- // check if another formatting item was inserted
464
- const doc = transaction.doc
465
- iterateStructsByIdSet(transaction, transaction.insertSet, (item) => {
466
- if (
467
- !item.deleted && /** @type {Item} */ (item).content.constructor === ContentFormat && item.constructor !== GC
468
- ) {
469
- needFullCleanup.add(/** @type {any} */ (item).parent)
470
- }
471
- })
472
- // cleanup in a new transaction
473
- transact(doc, (t) => {
474
- iterateStructsByIdSet(transaction, transaction.deleteSet, item => {
475
- if (item instanceof GC || !(/** @type {YType} */ (item.parent)._hasFormatting) || needFullCleanup.has(/** @type {YType} */ (item.parent))) {
476
- return
477
- }
478
- const parent = /** @type {YType} */ (item.parent)
479
- if (item.content.constructor === ContentFormat) {
480
- needFullCleanup.add(parent)
481
- } else {
482
- // If no formatting attribute was inserted or deleted, we can make due with contextless
483
- // formatting cleanups.
484
- // Contextless: it is not necessary to compute currentAttributes for the affected position.
485
- cleanupContextlessFormattingGap(t, item)
486
- }
487
- })
488
- // If a formatting item was inserted, we simply clean the whole type.
489
- // We need to compute currentAttributes for the current position anyway.
490
- for (const yText of needFullCleanup) {
491
- cleanupYTextFormatting(yText)
492
- }
493
- })
494
- }
495
-
496
207
  /**
497
208
  * @param {Array<Transaction>} transactionCleanups
498
209
  * @param {number} i
@@ -525,17 +236,29 @@ const cleanupTransactions = (transactionCleanups, i) => {
525
236
  })
526
237
  )
527
238
  fs.push(() => {
528
- // deep observe events
239
+ // deep observe events + the RDT `'delta'` channel. `changedParentTypes` holds the changed
240
+ // type AND all of its ancestors, so both `observeDeep` and `'delta'` bubble identically.
529
241
  transaction.changedParentTypes.forEach((events, type) => {
530
242
  // We need to think about the possibility that the user transforms the
531
243
  // Y.Doc in the event.
532
- if (type._dEH.l.length > 0 && (type._item === null || !type._item.deleted)) {
533
- /**
534
- * @type {YEvent<any>}
535
- */
536
- const deepEventHandler = events.find(event => event.target === type) || new YEvent(type, transaction, new Set(null))
244
+ if (type._item !== null && type._item.deleted) return
245
+ const hasDeep = type._dEH.l.length > 0
246
+ const hasDeltaListeners = (type._observers.get('delta')?.size ?? 0) > 0
247
+ const maintaining = type._delta !== null
248
+ if (!hasDeep && !hasDeltaListeners && !maintaining) return
249
+ /**
250
+ * @type {YEvent<any>}
251
+ */
252
+ const deepEventHandler = events.find(event => event.target === type) || new YEvent(type, transaction, new Set(null))
253
+ if (hasDeep) {
537
254
  callEventHandlerListeners(type._dEH, deepEventHandler, transaction)
538
255
  }
256
+ if (hasDeltaListeners || maintaining) {
257
+ // the type-rooted deep delta of this transaction (a nested `modify` chain for ancestors)
258
+ const change = /** @type {any} */ (deepEventHandler.getDelta({ renderer: type._renderer, deep: true }).done())
259
+ type._delta?.apply(change) // keep the cache current (incl. ancestors and diff-renderer attributions)
260
+ if (hasDeltaListeners) type.emit('delta', [change])
261
+ }
539
262
  })
540
263
  })
541
264
  fs.push(() => doc.emit('afterTransaction', [transaction, doc]))
@@ -621,6 +344,50 @@ const cleanupTransactions = (transactionCleanups, i) => {
621
344
  }
622
345
  }
623
346
 
347
+ /**
348
+ * This will be called by the transaction once the event handlers are called to potentially cleanup
349
+ * formats.
350
+ *
351
+ * @param {Transaction} transaction
352
+ */
353
+ export const cleanupYTextAfterTransaction = transaction => {
354
+ /**
355
+ * @type {Set<YType>}
356
+ */
357
+ const needFullCleanup = new Set()
358
+ // check if another formatting item was inserted
359
+ const doc = transaction.doc
360
+ iterateStructsByIdSet(transaction, transaction.insertSet, (item) => {
361
+ if (
362
+ !item.deleted && /** @type {Item} */ (item).content.constructor === ContentFormat && item.constructor !== GC
363
+ ) {
364
+ needFullCleanup.add(/** @type {any} */ (item).parent)
365
+ }
366
+ })
367
+ // cleanup in a new transaction
368
+ transact(doc, (t) => {
369
+ iterateStructsByIdSet(transaction, transaction.deleteSet, item => {
370
+ if (item instanceof GC || !(/** @type {YType} */ (item.parent)._hasFormatting) || needFullCleanup.has(/** @type {YType} */ (item.parent))) {
371
+ return
372
+ }
373
+ const parent = /** @type {YType} */ (item.parent)
374
+ if (item.content.constructor === ContentFormat) {
375
+ needFullCleanup.add(parent)
376
+ } else {
377
+ // If no format was inserted or deleted, we can make due with contextless
378
+ // formatting cleanups.
379
+ // Contextless: it is not necessary to compute currentFormats for the affected position.
380
+ cleanupContextlessFormattingGap(t, item)
381
+ }
382
+ })
383
+ // If a formatting item was inserted, we simply clean the whole type.
384
+ // We need to compute currentFormats for the current position anyway.
385
+ for (const yText of needFullCleanup) {
386
+ cleanupYTextFormatting(yText)
387
+ }
388
+ })
389
+ }
390
+
624
391
  /**
625
392
  * Implements the functionality of `y.transact(()=>{..})`
626
393
  *
@@ -628,6 +395,7 @@ const cleanupTransactions = (transactionCleanups, i) => {
628
395
  * @param {Doc} doc
629
396
  * @param {function(Transaction):T} f
630
397
  * @param {any} [origin=true]
398
+ * @param {boolean} [local=true]
631
399
  * @return {T}
632
400
  *
633
401
  * @function