lib0 1.0.0-rc.2 → 1.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 (141) hide show
  1. package/README.md +0 -1315
  2. package/{types → dist}/buffer.d.ts +2 -10
  3. package/{types → dist}/delta/delta.d.ts +582 -144
  4. package/dist/delta/position.d.ts +65 -0
  5. package/dist/delta/rdt/delta.d.ts +57 -0
  6. package/dist/delta/rdt/dom.d.ts +102 -0
  7. package/dist/delta/rdt.d.ts +180 -0
  8. package/dist/delta/transformer/attr.d.ts +64 -0
  9. package/dist/delta/transformer/attribution-to-format.d.ts +52 -0
  10. package/dist/delta/transformer/children.d.ts +69 -0
  11. package/dist/delta/transformer/conform.d.ts +83 -0
  12. package/dist/delta/transformer/core.d.ts +189 -0
  13. package/dist/delta/transformer/full-attributions.d.ts +37 -0
  14. package/dist/delta/transformer/id.d.ts +2 -0
  15. package/dist/delta/transformer/inline.d.ts +89 -0
  16. package/dist/delta/transformer/pipe.d.ts +182 -0
  17. package/dist/delta/transformer/project.d.ts +223 -0
  18. package/dist/delta/transformer/rename-attrs.d.ts +75 -0
  19. package/dist/delta/transformer/rename.d.ts +41 -0
  20. package/dist/delta/transformer/value.d.ts +92 -0
  21. package/dist/delta/transformer.d.ts +14 -0
  22. package/dist/diff/patience.d.ts +25 -0
  23. package/{types → dist}/environment.d.ts +10 -0
  24. package/dist/hash/fnv1a.d.ts +11 -0
  25. package/{types → dist}/schema.d.ts +49 -38
  26. package/{types → dist}/ts.d.ts +4 -0
  27. package/dist/webcrypto.d.ts +3 -0
  28. package/package.json +148 -82
  29. package/src/bin/0serve.js +11 -3
  30. package/src/broadcastchannel.js +6 -1
  31. package/src/buffer.js +35 -25
  32. package/src/decoding.js +1 -1
  33. package/src/delta/delta.js +2531 -564
  34. package/src/delta/position.js +165 -0
  35. package/src/delta/rdt/delta.js +105 -0
  36. package/src/delta/rdt/dom.js +307 -0
  37. package/src/delta/rdt.js +255 -0
  38. package/src/delta/transformer/attr.js +126 -0
  39. package/src/delta/transformer/attribution-to-format.js +335 -0
  40. package/src/delta/transformer/children.js +219 -0
  41. package/src/delta/transformer/conform.js +401 -0
  42. package/src/delta/transformer/core.js +323 -0
  43. package/src/delta/transformer/full-attributions.js +282 -0
  44. package/src/delta/transformer/id.js +13 -0
  45. package/src/delta/transformer/inline.js +818 -0
  46. package/src/delta/transformer/pipe.js +272 -0
  47. package/src/delta/transformer/project.js +526 -0
  48. package/src/delta/transformer/rename-attrs.js +152 -0
  49. package/src/delta/transformer/rename.js +84 -0
  50. package/src/delta/transformer/value.js +214 -0
  51. package/src/delta/transformer.js +38 -0
  52. package/src/diff/patience.js +22 -11
  53. package/src/dom.js +2 -2
  54. package/src/encoding.js +1 -1
  55. package/src/environment.js +15 -0
  56. package/src/hash/fnv1a.js +82 -0
  57. package/src/number.js +1 -3
  58. package/src/performance.node.js +3 -3
  59. package/src/schema.js +231 -177
  60. package/src/string.js +3 -5
  61. package/src/testing.js +1 -1
  62. package/src/trait/equality.js +1 -1
  63. package/src/trait/fingerprint.js +1 -1
  64. package/src/ts.js +11 -0
  65. package/src/bin/gendocs.js +0 -117
  66. package/src/component.js +0 -414
  67. package/src/delta/binding.js +0 -372
  68. package/src/tree.js +0 -546
  69. package/types/bin/gendocs.d.ts +0 -3
  70. package/types/component.d.ts +0 -86
  71. package/types/delta/binding.d.ts +0 -106
  72. package/types/diff/patience.d.ts +0 -16
  73. package/types/tree.d.ts +0 -96
  74. package/types/webcrypto.d.ts +0 -3
  75. /package/{types → dist}/array.d.ts +0 -0
  76. /package/{types → dist}/bin/0ecdsa-generate-keypair.d.ts +0 -0
  77. /package/{types → dist}/bin/0serve.d.ts +0 -0
  78. /package/{types → dist}/bin/gentesthtml.d.ts +0 -0
  79. /package/{types → dist}/binary.d.ts +0 -0
  80. /package/{types → dist}/broadcastchannel.d.ts +0 -0
  81. /package/{types → dist}/cache.d.ts +0 -0
  82. /package/{types → dist}/conditions.d.ts +0 -0
  83. /package/{types → dist}/crypto/aes-gcm.d.ts +0 -0
  84. /package/{types → dist}/crypto/common.d.ts +0 -0
  85. /package/{types → dist}/crypto/ecdsa.d.ts +0 -0
  86. /package/{types → dist}/crypto/jwt.d.ts +0 -0
  87. /package/{types → dist}/crypto/rsa-oaep.d.ts +0 -0
  88. /package/{types → dist}/decoding.d.ts +0 -0
  89. /package/{types → dist}/diff.d.ts +0 -0
  90. /package/{types → dist}/dom.d.ts +0 -0
  91. /package/{types → dist}/encoding.d.ts +0 -0
  92. /package/{types → dist}/error.d.ts +0 -0
  93. /package/{types → dist}/eventloop.d.ts +0 -0
  94. /package/{types → dist}/function.d.ts +0 -0
  95. /package/{types → dist}/hash/rabin-gf2-polynomial.d.ts +0 -0
  96. /package/{types → dist}/hash/rabin-uncached.d.ts +0 -0
  97. /package/{types → dist}/hash/rabin.d.ts +0 -0
  98. /package/{types → dist}/hash/sha256.d.ts +0 -0
  99. /package/{types → dist}/hash/sha256.node.d.ts +0 -0
  100. /package/{types → dist}/indexeddb.d.ts +0 -0
  101. /package/{types → dist}/indexeddbV2.d.ts +0 -0
  102. /package/{types → dist}/iterator.d.ts +0 -0
  103. /package/{types → dist}/json.d.ts +0 -0
  104. /package/{types → dist}/list.d.ts +0 -0
  105. /package/{types → dist}/logging.common.d.ts +0 -0
  106. /package/{types → dist}/logging.d.ts +0 -0
  107. /package/{types → dist}/logging.node.d.ts +0 -0
  108. /package/{types → dist}/map.d.ts +0 -0
  109. /package/{types → dist}/math.d.ts +0 -0
  110. /package/{types → dist}/metric.d.ts +0 -0
  111. /package/{types → dist}/mutex.d.ts +0 -0
  112. /package/{types → dist}/number.d.ts +0 -0
  113. /package/{types → dist}/object.d.ts +0 -0
  114. /package/{types → dist}/observable.d.ts +0 -0
  115. /package/{types → dist}/pair.d.ts +0 -0
  116. /package/{types → dist}/performance.d.ts +0 -0
  117. /package/{types → dist}/performance.node.d.ts +0 -0
  118. /package/{types → dist}/pledge.d.ts +0 -0
  119. /package/{types → dist}/prng/Mt19937.d.ts +0 -0
  120. /package/{types → dist}/prng/Xoroshiro128plus.d.ts +0 -0
  121. /package/{types → dist}/prng/Xorshift32.d.ts +0 -0
  122. /package/{types → dist}/prng.d.ts +0 -0
  123. /package/{types → dist}/promise.d.ts +0 -0
  124. /package/{types → dist}/queue.d.ts +0 -0
  125. /package/{types → dist}/random.d.ts +0 -0
  126. /package/{types → dist}/set.d.ts +0 -0
  127. /package/{types → dist}/sort.d.ts +0 -0
  128. /package/{types → dist}/statistics.d.ts +0 -0
  129. /package/{types → dist}/storage.d.ts +0 -0
  130. /package/{types → dist}/string.d.ts +0 -0
  131. /package/{types → dist}/symbol.d.ts +0 -0
  132. /package/{types → dist}/testing.d.ts +0 -0
  133. /package/{types → dist}/time.d.ts +0 -0
  134. /package/{types → dist}/trait/equality.d.ts +0 -0
  135. /package/{types → dist}/trait/fingerprint.d.ts +0 -0
  136. /package/{types → dist}/trait/traits.d.ts +0 -0
  137. /package/{types → dist}/url.d.ts +0 -0
  138. /package/{types → dist}/webcrypto.deno.d.ts +0 -0
  139. /package/{types → dist}/webcrypto.node.d.ts +0 -0
  140. /package/{types → dist}/webcrypto.react-native.d.ts +0 -0
  141. /package/{types → dist}/websocket.d.ts +0 -0
@@ -0,0 +1,818 @@
1
+ import * as delta from '../delta.js'
2
+ import * as math from '../../math.js'
3
+ import { Transformer, Template, createTransformResult } from './core.js'
4
+
5
+ /**
6
+ * The (unexported) delta {@link import('../delta.js').createMark Mark} type, referenced here for the
7
+ * local mark-bucketing annotations.
8
+ *
9
+ * @typedef {ReturnType<typeof delta.createMark>} Mark
10
+ */
11
+
12
+ /**
13
+ * One entry of the segment layout maintained by {@link InlineTransformer}. A segment is either a
14
+ * coalesced run of pass-through positions (root text + opaque non-inlined nodes - same length in both
15
+ * coordinate spaces) or a single inline node (1 structured position that expands to `inlineLen`
16
+ * inlined positions).
17
+ */
18
+ class Seg {
19
+ /**
20
+ * @param {boolean} isInline whether this seg is an inline node (its children are spliced into the parent)
21
+ * @param {number} structLen
22
+ * @param {number} inlineLen
23
+ */
24
+ constructor (isInline, structLen, inlineLen) {
25
+ this.isInline = isInline
26
+ this.structLen = structLen
27
+ this.inlineLen = inlineLen
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Whether `el` is a child node that should be inlined - i.e. a node delta whose `name` is in the
33
+ * configured `names` set (use `null` to select anonymous/typeless nodes).
34
+ *
35
+ * @param {any} el
36
+ * @param {Array<string|null>} names
37
+ * @return {boolean}
38
+ */
39
+ const isInlineNode = (el, names) => delta.$deltaAny.check(el) && names.includes(el.name)
40
+
41
+ /**
42
+ * Whether any direct child of `d` is an inline node - the only thing this transformer inlines. Called
43
+ * on each incoming change to feed the cached {@link InlineTransformer#hasInline} flag, which drives
44
+ * the fast-path: while no inline node has ever appeared, the structured and inlined representations
45
+ * are identical and a change is its own transform. An inline node nested inside a non-inlined node is
46
+ * opaque (one level) and intentionally not counted.
47
+ *
48
+ * @param {delta.DeltaAny} d
49
+ * @param {Array<string|null>} names
50
+ * @return {boolean}
51
+ */
52
+ const hasInlineChild = (d, names) => {
53
+ for (const op of d.children) {
54
+ if (delta.$insertOp.check(op)) {
55
+ for (const el of op.insert) {
56
+ if (isInlineNode(el, names)) return true
57
+ }
58
+ }
59
+ }
60
+ return false
61
+ }
62
+
63
+ /**
64
+ * Net change in a parent's direct child count from applying a settled child-change delta: positions
65
+ * inserted minus positions deleted (retain/modify keep the count). Lets an inline node's inlined
66
+ * length be kept current when a `modify` edits it, without re-deriving the node's children.
67
+ *
68
+ * @param {delta.DeltaAny} d
69
+ * @return {number}
70
+ */
71
+ const netLen = d => {
72
+ let n = 0
73
+ for (const op of d.children) {
74
+ if (delta.$insertOp.check(op) || delta.$textOp.check(op)) {
75
+ n += op.insert.length
76
+ } else if (delta.$deleteOp.check(op)) {
77
+ n -= op.delete
78
+ }
79
+ }
80
+ return n
81
+ }
82
+
83
+ /**
84
+ * Apply a settled *structured* change to the segment layout `segs` in place, advancing it to the
85
+ * layout of the changed state without re-deriving the whole list. The cursor `(si, so)` is a
86
+ * structured position - seg index plus structural offset within that seg. The layout is kept
87
+ * coalesced (no two adjacent pass-through segs, no empty pass-through seg; every inline node is its
88
+ * own length-1 seg, kept even when its inlined length is 0) so it stays identical to a from-scratch
89
+ * derivation of the settled state. One level only: a direct inline node's length is `childCnt`; an
90
+ * inline node nested deeper is an opaque element of length 1.
91
+ *
92
+ * @param {Array<Seg>} segs
93
+ * @param {delta.DeltaAny} change
94
+ * @param {Array<string|null>} names the node names that are inlined (see {@link isInlineNode})
95
+ */
96
+ const applySegsChange = (segs, change, names) => {
97
+ let si = 0
98
+ let so = 0
99
+ // Move the cursor onto a seg boundary (so === 0), splitting the current pass-through seg if the
100
+ // cursor sits in its interior. A null seg has structLen 1, so the cursor is never in its interior.
101
+ const ensureBoundary = () => {
102
+ if (so === 0) return
103
+ const s = segs[si]
104
+ segs.splice(si + 1, 0, new Seg(false, s.structLen - so, s.inlineLen - so))
105
+ s.structLen = so
106
+ s.inlineLen = so
107
+ si++
108
+ so = 0
109
+ }
110
+ for (const op of change.children) {
111
+ if (delta.$retainOp.check(op)) {
112
+ // formats on a retain never change the layout - just advance the cursor
113
+ let rem = op.retain
114
+ while (rem > 0 && si < segs.length) {
115
+ const room = segs[si].structLen - so
116
+ if (rem < room) {
117
+ so += rem
118
+ rem = 0
119
+ } else {
120
+ rem -= room
121
+ si++
122
+ so = 0
123
+ }
124
+ }
125
+ } else if (delta.$textOp.check(op) || delta.$insertOp.check(op)) {
126
+ // Build the inserted layout: a text op is one pass-through run; an insert op is a mix of
127
+ // length-1 null segs and pass-through runs that coalesce the opaque (named) elements.
128
+ /** @type {Array<Seg>} */
129
+ const ins = []
130
+ if (delta.$textOp.check(op)) {
131
+ ins.push(new Seg(false, op.insert.length, op.insert.length))
132
+ } else {
133
+ for (const el of op.insert) {
134
+ const last = ins[ins.length - 1]
135
+ if (isInlineNode(el, names)) {
136
+ ins.push(new Seg(true, 1, /** @type {delta.DeltaAny} */ (el).childCnt))
137
+ } else if (last !== undefined && !last.isInline) {
138
+ last.structLen++
139
+ last.inlineLen++
140
+ } else {
141
+ ins.push(new Seg(false, 1, 1))
142
+ }
143
+ }
144
+ }
145
+ if (ins.length === 1 && !ins[0].isInline) {
146
+ // fast path - pure pass-through content grows an adjacent run (no split); the common case
147
+ const len = ins[0].structLen
148
+ if (so > 0) {
149
+ segs[si].structLen += len
150
+ segs[si].inlineLen += len
151
+ so += len
152
+ } else if (si > 0 && !segs[si - 1].isInline) {
153
+ segs[si - 1].structLen += len
154
+ segs[si - 1].inlineLen += len
155
+ } else if (si < segs.length && !segs[si].isInline) {
156
+ segs[si].structLen += len
157
+ segs[si].inlineLen += len
158
+ so = len
159
+ } else {
160
+ segs.splice(si, 0, ins[0])
161
+ si++
162
+ }
163
+ } else {
164
+ // general case (the content contains an inline node): land on a boundary, then coalesce the
165
+ // block's pass-through ends into the surrounding runs
166
+ ensureBoundary()
167
+ let block = ins
168
+ if (si > 0 && !segs[si - 1].isInline && !block[0].isInline) {
169
+ segs[si - 1].structLen += block[0].structLen
170
+ segs[si - 1].inlineLen += block[0].inlineLen
171
+ block = block.slice(1)
172
+ }
173
+ let tail = 0
174
+ if (si < segs.length && !segs[si].isInline && block.length > 0 && !block[block.length - 1].isInline) {
175
+ const lastSeg = block[block.length - 1]
176
+ tail = lastSeg.structLen
177
+ segs[si].structLen += lastSeg.structLen
178
+ segs[si].inlineLen += lastSeg.inlineLen
179
+ block = block.slice(0, -1)
180
+ }
181
+ segs.splice(si, 0, ...block)
182
+ si += block.length
183
+ so = tail
184
+ }
185
+ } else if (delta.$deleteOp.check(op)) {
186
+ ensureBoundary()
187
+ let rem = op.delete
188
+ while (rem > 0 && si < segs.length) {
189
+ const s = segs[si]
190
+ if (s.isInline) {
191
+ // the single structured position is the whole inline node
192
+ segs.splice(si, 1)
193
+ rem -= 1
194
+ } else {
195
+ const take = math.min(s.structLen, rem)
196
+ s.structLen -= take
197
+ s.inlineLen -= take
198
+ rem -= take
199
+ if (s.structLen === 0) segs.splice(si, 1)
200
+ // a partial delete leaves rem 0 and the shortened seg in place
201
+ }
202
+ }
203
+ // a deletion can bring two pass-through runs together - coalesce, keeping the cursor on the seam
204
+ if (si > 0 && si < segs.length && !segs[si - 1].isInline && !segs[si].isInline) {
205
+ const leftLen = segs[si - 1].structLen
206
+ segs[si - 1].structLen += segs[si].structLen
207
+ segs[si - 1].inlineLen += segs[si].inlineLen
208
+ segs.splice(si, 1)
209
+ si--
210
+ so = leftLen
211
+ }
212
+ } else if (delta.$modifyOp.check(op)) {
213
+ if (si < segs.length) {
214
+ const s = segs[si]
215
+ if (so === 0 && s.isInline) {
216
+ // editing inside an inline node changes only its inlined length
217
+ s.inlineLen += netLen(op.value)
218
+ si++
219
+ } else {
220
+ // an opaque (named) node - no layout change; step over its single structured position
221
+ so++
222
+ if (so >= s.structLen) {
223
+ si++
224
+ so = 0
225
+ }
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Map a structured root-mark offset to its inlined offset through the seg layout. A mark on a
234
+ * pass-through position keeps its in-run offset; a mark on an inline node's own structured position
235
+ * maps to that node's inlined start (its `childStart`). A key past the end clamps to the inlined end
236
+ * (a change-delta root mark may sit at the trailing implicit-retain boundary).
237
+ *
238
+ * @param {Array<Seg>} segs
239
+ * @param {number} key
240
+ * @return {number}
241
+ */
242
+ const structOffsetToInlineOffset = (segs, key) => {
243
+ let s = 0
244
+ let inl = 0
245
+ for (const seg of segs) {
246
+ if (key < s + seg.structLen) return seg.isInline ? inl : inl + (key - s)
247
+ s += seg.structLen
248
+ inl += seg.inlineLen
249
+ }
250
+ return inl
251
+ }
252
+
253
+ /**
254
+ * Map an inlined root-mark offset to a structured location. `inner` is `null` for a pass-through
255
+ * position (the mark is a structured root mark at `struct`); otherwise the offset fell inside the
256
+ * inline node at structured position `struct` (seg index `si`), and `inner` is the offset within that
257
+ * node. A key past the end clamps to the structured end.
258
+ *
259
+ * @param {Array<Seg>} segs
260
+ * @param {number} key
261
+ * @return {{ struct: number, inner: number|null, si: number }}
262
+ */
263
+ const inlineOffsetToStructLocation = (segs, key) => {
264
+ let s = 0
265
+ let inl = 0
266
+ for (let si = 0; si < segs.length; si++) {
267
+ const seg = segs[si]
268
+ if (key < inl + seg.inlineLen) {
269
+ return seg.isInline ? { struct: s, inner: key - inl, si } : { struct: s + (key - inl), inner: null, si }
270
+ }
271
+ s += seg.structLen
272
+ inl += seg.inlineLen
273
+ }
274
+ return { struct: s, inner: null, si: segs.length }
275
+ }
276
+
277
+ /**
278
+ * Build an output delta that passes through the node name and attribute ops of `src` (only the
279
+ * children are transformed by this transformer).
280
+ *
281
+ * reason: returns `any` rather than `DeltaBuilderAny` on purpose. The result is internal scratch the
282
+ * transformer mutates with fluent builder calls; typing it would instantiate a `DeltaConfOverwrite`
283
+ * per call, and across this file that tips the shared TS2589 instantiation budget that the long
284
+ * pipe-type test (delta-transformer.test.js) sits against. The public output type lives on `init`.
285
+ *
286
+ * @param {delta.DeltaAny} src
287
+ * @return {any}
288
+ */
289
+ const passThrough = src => {
290
+ const d = delta.create(/** @type {any} */ (src.name))
291
+ for (const op of src.attrs) {
292
+ // reason: `attrs` is a mapped type over the conf's attr keys, so a dynamic-key write can't be
293
+ // checked (same limitation delta.slice suppresses when copying attrs).
294
+ // @ts-ignore
295
+ d.attrs[op.key] = op.clone()
296
+ }
297
+ // carry the conservative mark flag (covers attr-value marks the direct assignment above bypasses);
298
+ // marks carried into the rebuilt children below flag it via the builder / addRootMark
299
+ d.maybeHasMarks = src.maybeHasMarks
300
+ return d
301
+ }
302
+
303
+ /**
304
+ * Stateful transformer that inlines child nodes whose name is in the configured `names` set. Side A
305
+ * is the structured representation (inline nodes present, e.g. `<p>some<>text</></p>`), side B is the
306
+ * inlined representation (inline nodes flattened, e.g. `<p>sometext</p>`).
307
+ *
308
+ * The state the user calls "offsets and inline node length" is the segment layout {@link Seg} stored
309
+ * in `segs`: a coalesced run-length view of the structured side where each inline node is one
310
+ * structured position expanding to `inlineLen` inlined positions, and every other position (root text
311
+ * and opaque non-inlined nodes) is a pass-through run. It is kept current incrementally - every mapped
312
+ * change is folded into `segs` by {@link applySegsChange} rather than re-derived from scratch.
313
+ *
314
+ * @extends {Transformer<any,any>}
315
+ */
316
+ export class InlineTransformer extends Transformer {
317
+ /**
318
+ * @param {import('../../schema.js').Schema<delta.Delta<any>>} $in
319
+ * @param {import('../../schema.js').Schema<delta.Delta<any>>} $out
320
+ * @param {Array<string|null>} names the node names to inline (`null` selects anonymous nodes)
321
+ */
322
+ constructor ($in, $out, names) {
323
+ super($in, $out)
324
+ /**
325
+ * @type {Array<string|null>}
326
+ */
327
+ this.names = names
328
+ /**
329
+ * The segment layout of the structured side, maintained incrementally (see {@link applySegsChange}).
330
+ *
331
+ * @type {Array<Seg>}
332
+ */
333
+ this.segs = []
334
+ /**
335
+ * Whether the structured side has ever contained a direct inline-node child. Monotonic: once a
336
+ * change introduces an inline node it stays set (even if that node is later deleted), which only
337
+ * costs a missed fast-path, never correctness.
338
+ *
339
+ * @type {boolean}
340
+ */
341
+ this.hasInline = false
342
+ }
343
+
344
+ /**
345
+ * structured change -> inlined change
346
+ *
347
+ * @param {delta.DeltaBuilderAny} da
348
+ */
349
+ applyA (da) {
350
+ this.hasInline ||= hasInlineChild(da, this.names)
351
+ if (!this.hasInline) {
352
+ // fast-path: nothing to inline now or after this change - structured === inlined, so the
353
+ // change passes through unchanged. Keep the layout current and hand back the same delta.
354
+ applySegsChange(this.segs, da, this.names)
355
+ return createTransformResult(null, da)
356
+ }
357
+ const segs = this.segs
358
+ const b = passThrough(da)
359
+ let si = 0
360
+ let so = 0
361
+ let srcInline = 0
362
+ let emitted = 0
363
+ // position in the resulting inlined doc that `b` has built up to (retain + insert advance it,
364
+ // delete does not). Used to anchor marks lifted out of a spliced inline node (A.3).
365
+ let outPos = 0
366
+ /**
367
+ * @param {number} target
368
+ */
369
+ const alignB = target => {
370
+ if (target > emitted) {
371
+ b.retain(target - emitted)
372
+ outPos += target - emitted
373
+ emitted = target
374
+ }
375
+ }
376
+ /**
377
+ * Advance the structured cursor by `n` positions, returning the inlined length spanned.
378
+ *
379
+ * @param {number} n
380
+ */
381
+ const advanceStruct = n => {
382
+ let rem = n
383
+ let inl = 0
384
+ while (rem > 0 && si < segs.length) {
385
+ const s = segs[si]
386
+ const take = math.min(s.structLen - so, rem)
387
+ inl += s.isInline ? s.inlineLen : take
388
+ so += take
389
+ rem -= take
390
+ if (so >= s.structLen) {
391
+ si++
392
+ so = 0
393
+ }
394
+ }
395
+ return inl
396
+ }
397
+ for (const op of da.children) {
398
+ if (delta.$retainOp.check(op)) {
399
+ if (op.format === undefined && op.attribution === undefined) {
400
+ srcInline += advanceStruct(op.retain)
401
+ } else {
402
+ // formatted retain: emit the format over the mapped inline range. An inline node's wrapper
403
+ // has no inlined representation, so its format is dropped (consistent with the inliner); root
404
+ // text and opaque nodes carry the format straight through.
405
+ alignB(srcInline)
406
+ let rem = op.retain
407
+ while (rem > 0 && si < segs.length) {
408
+ const s = segs[si]
409
+ const take = math.min(s.structLen - so, rem)
410
+ const inl = s.isInline ? s.inlineLen : take
411
+ b.retain(inl, s.isInline ? undefined : op.format, s.isInline ? undefined : op.attribution)
412
+ emitted += inl
413
+ outPos += inl
414
+ srcInline += inl
415
+ so += take
416
+ rem -= take
417
+ if (so >= s.structLen) { si++; so = 0 }
418
+ }
419
+ }
420
+ } else if (delta.$textOp.check(op)) {
421
+ alignB(srcInline)
422
+ b.insert(op.insert, op.format, op.attribution)
423
+ outPos += op.insert.length
424
+ } else if (delta.$insertOp.check(op)) {
425
+ alignB(srcInline)
426
+ for (const el of op.insert) {
427
+ if (isInlineNode(el, this.names)) {
428
+ // splice the inline node's children verbatim (one level), then lift the node's own root
429
+ // marks onto the flattened parent at `childStart + innerKey` (A.3). String-keyed (attr)
430
+ // marks on the inline node have no home in the flattened parent and are dropped.
431
+ const node = /** @type {delta.DeltaAny} */ (el)
432
+ const base = outPos
433
+ b.append(node)
434
+ outPos += node.childCnt
435
+ delta.mergeRootMarks(b, node, k => typeof k === 'number' ? base + k : null)
436
+ } else {
437
+ b.insert([el], op.format, op.attribution)
438
+ outPos += 1
439
+ }
440
+ }
441
+ } else if (delta.$deleteOp.check(op)) {
442
+ alignB(srcInline)
443
+ const delInline = advanceStruct(op.delete)
444
+ b.delete(delInline)
445
+ srcInline += delInline
446
+ emitted += delInline
447
+ } else if (delta.$modifyOp.check(op)) {
448
+ const s = segs[si]
449
+ alignB(srcInline)
450
+ if (s !== undefined && s.isInline) {
451
+ // data side edited inside an inline node: its inner ops act on the node's children, which are
452
+ // spliced into b at the same offset - replay them directly
453
+ const base = outPos
454
+ for (const inner of op.value.children) {
455
+ if (delta.$retainOp.check(inner)) {
456
+ b.retain(inner.retain, inner.format, inner.attribution)
457
+ emitted += inner.retain
458
+ outPos += inner.retain
459
+ } else if (delta.$textOp.check(inner) || delta.$insertOp.check(inner)) {
460
+ b.insert(inner.insert, inner.format, inner.attribution)
461
+ outPos += inner.insert.length
462
+ } else if (delta.$deleteOp.check(inner)) {
463
+ b.delete(inner.delete)
464
+ emitted += inner.delete
465
+ } else if (delta.$modifyOp.check(inner)) {
466
+ b.modify(delta.clone(inner.value), inner.format, inner.attribution)
467
+ emitted += 1
468
+ outPos += 1
469
+ }
470
+ }
471
+ // marks set inside the inline node by this change lift onto the flattened parent (A.3)
472
+ delta.mergeRootMarks(b, op.value, k => typeof k === 'number' ? base + k : null)
473
+ srcInline += s.inlineLen
474
+ advanceStruct(1)
475
+ } else {
476
+ // opaque (named) node: forward the modify verbatim
477
+ b.modify(delta.clone(op.value), op.format, op.attribution)
478
+ emitted += 1
479
+ outPos += 1
480
+ srcInline += advanceStruct(1)
481
+ }
482
+ }
483
+ }
484
+ applySegsChange(this.segs, da, this.names)
485
+ // carry the change's own (root) marks, re-anchoring number offsets through the post-change layout;
486
+ // attr-key marks pass through unchanged. deleteMarks ride verbatim (merged with any lifted above).
487
+ delta.mergeRootMarks(b, da, k => typeof k === 'number' ? structOffsetToInlineOffset(this.segs, k) : k)
488
+ b.done(false)
489
+ return createTransformResult(null, b)
490
+ }
491
+
492
+ /**
493
+ * inlined change -> structured change
494
+ *
495
+ * @param {delta.DeltaBuilderAny} db
496
+ */
497
+ applyB (db) {
498
+ this.hasInline ||= hasInlineChild(db, this.names)
499
+ if (!this.hasInline) {
500
+ // fast-path: with no inline nodes the inlined and structured coordinate spaces coincide, so the
501
+ // inlined change is already the structured change.
502
+ applySegsChange(this.segs, db, this.names)
503
+ return createTransformResult(db, null)
504
+ }
505
+ const segs = this.segs
506
+ const a = passThrough(db)
507
+ // --- marks: bucket the change's own (root) marks against the (pre-change) layout. A pass-through
508
+ // offset (or an attr-key mark) becomes a structured root mark; an offset inside an inline node is
509
+ // routed into that node - seeded into its modify when the walk opens one (below), else emitted as a
510
+ // mark-only modify (pure cursor move) or collapsed to the node position (mixed change) after the
511
+ // walk. NB: a change that simultaneously restructures content and moves a cursor maps the cursor in
512
+ // the pre-change layout (best-effort - marks are local/ephemeral).
513
+ /** @type {Array<Mark>} */
514
+ const passMarks = []
515
+ /** @type {Map<number, Array<{ innerKey: number, mark: Mark }>>} */
516
+ const innerBySi = new Map()
517
+ if (db.marks !== null) {
518
+ for (const m of db.marks) {
519
+ if (typeof m.key !== 'number') { passMarks.push(m); continue }
520
+ const r = inlineOffsetToStructLocation(segs, m.key)
521
+ if (r.inner === null) {
522
+ passMarks.push(m.copy(r.struct))
523
+ } else {
524
+ const arr = innerBySi.get(r.si) ?? []
525
+ arr.push({ innerKey: r.inner, mark: m })
526
+ innerBySi.set(r.si, arr)
527
+ }
528
+ }
529
+ }
530
+ // structured start offset of each seg (pre-change), for placing inner marks after the walk
531
+ /** @type {Array<number>} */
532
+ const segStart = []
533
+ for (let i = 0, acc = 0; i < segs.length; i++) { segStart.push(acc); acc += segs[i].structLen }
534
+ let hadChildOps = false
535
+ let si = 0
536
+ let so = 0
537
+ let structBefore = 0
538
+ let emittedStruct = 0
539
+ // An accumulator for edits that fall inside the current inline node. Several inlined ops can
540
+ // target the same inline node, so they must coalesce into a single `modify` op. `openSegIdx` is
541
+ // the seg index it belongs to, or -1 when there is no open modify.
542
+ let openInner = /** @type {any} */ (delta.create())
543
+ let openNodeStruct = 0
544
+ let openInnerEmitted = 0
545
+ let openSegIdx = -1
546
+ /**
547
+ * @param {number} target
548
+ */
549
+ const alignA = target => {
550
+ if (target > emittedStruct) {
551
+ a.retain(target - emittedStruct)
552
+ emittedStruct = target
553
+ }
554
+ }
555
+ const flushModify = () => {
556
+ if (openSegIdx !== -1) {
557
+ alignA(openNodeStruct)
558
+ a.modify(openInner.done(false))
559
+ emittedStruct += 1
560
+ openSegIdx = -1
561
+ }
562
+ }
563
+ /**
564
+ * @param {number} target inlined offset within the open inline node
565
+ */
566
+ const alignInner = target => {
567
+ if (target > openInnerEmitted) {
568
+ openInner.retain(target - openInnerEmitted)
569
+ openInnerEmitted = target
570
+ }
571
+ }
572
+ /**
573
+ * @param {number} idx
574
+ */
575
+ const openModifyFor = idx => {
576
+ openInner = /** @type {any} */ (delta.create())
577
+ openNodeStruct = structBefore
578
+ openInnerEmitted = 0
579
+ openSegIdx = idx
580
+ // seed any inner marks bound to this node so they ride on the modify's value (A.3, reverse)
581
+ const im = innerBySi.get(idx)
582
+ if (im !== undefined) {
583
+ for (const { innerKey, mark } of im) delta.addRootMark(openInner, mark.copy(innerKey))
584
+ innerBySi.delete(idx) // consumed - excluded from the post-walk fallback
585
+ }
586
+ }
587
+ // empty inline nodes (inlineLen 0) are zero-width in inlined coords - step over them. Only at a
588
+ // seg boundary (so === 0); `so` does not change while skipping.
589
+ const skipEmpty = () => {
590
+ if (so !== 0) return
591
+ while (si < segs.length && segs[si].isInline && segs[si].inlineLen === 0) {
592
+ structBefore += segs[si].structLen
593
+ si++
594
+ }
595
+ }
596
+ for (const op of db.children) {
597
+ hadChildOps = true
598
+ if (delta.$retainOp.check(op)) {
599
+ const fmt = op.format
600
+ const attr = op.attribution
601
+ let rem = op.retain
602
+ while (rem > 0) {
603
+ skipEmpty()
604
+ if (si >= segs.length) break
605
+ const s = segs[si]
606
+ const take = math.min(s.inlineLen - so, rem)
607
+ if (fmt !== undefined || attr !== undefined) {
608
+ // a formatted retain re-formats existing content: pass-through content takes it
609
+ // directly; content inside an inline node is reached through a modify on that node.
610
+ if (!s.isInline) {
611
+ flushModify()
612
+ alignA(structBefore + so)
613
+ a.retain(take, fmt, attr)
614
+ emittedStruct += take
615
+ } else {
616
+ if (openSegIdx !== si) {
617
+ flushModify()
618
+ openModifyFor(si)
619
+ }
620
+ alignInner(so)
621
+ openInner.retain(take, fmt, attr)
622
+ openInnerEmitted += take
623
+ }
624
+ }
625
+ so += take
626
+ rem -= take
627
+ if (so >= s.inlineLen) {
628
+ structBefore += s.structLen
629
+ si++
630
+ so = 0
631
+ }
632
+ }
633
+ } else if (delta.$textOp.check(op) || delta.$insertOp.check(op)) {
634
+ skipEmpty()
635
+ const s = segs[si]
636
+ if (s === undefined) {
637
+ flushModify()
638
+ alignA(structBefore)
639
+ a.insert(op.insert, op.format, op.attribution)
640
+ } else if (!s.isInline) {
641
+ flushModify()
642
+ alignA(structBefore + so)
643
+ a.insert(op.insert, op.format, op.attribution)
644
+ } else if (so === 0) {
645
+ // boundary preference: an insert at the start of an inline node lands in the root
646
+ flushModify()
647
+ alignA(structBefore)
648
+ a.insert(op.insert, op.format, op.attribution)
649
+ } else {
650
+ // strict interior: insert into the inline node
651
+ if (openSegIdx !== si) {
652
+ flushModify()
653
+ openModifyFor(si)
654
+ }
655
+ alignInner(so)
656
+ openInner.insert(op.insert, op.format, op.attribution)
657
+ }
658
+ // an insert does not advance the cursor (it adds content not in the original)
659
+ } else if (delta.$deleteOp.check(op)) {
660
+ let rem = op.delete
661
+ while (rem > 0) {
662
+ skipEmpty()
663
+ if (si >= segs.length) break
664
+ const s = segs[si]
665
+ if (!s.isInline) {
666
+ const k = math.min(s.inlineLen - so, rem)
667
+ flushModify()
668
+ alignA(structBefore + so)
669
+ a.delete(k)
670
+ emittedStruct += k
671
+ so += k
672
+ rem -= k
673
+ if (so >= s.inlineLen) {
674
+ structBefore += s.structLen
675
+ si++
676
+ so = 0
677
+ }
678
+ } else if (so === 0 && rem >= s.inlineLen) {
679
+ // the delete covers the whole node from its start - delete it structurally
680
+ flushModify()
681
+ alignA(structBefore)
682
+ a.delete(1)
683
+ emittedStruct += 1
684
+ rem -= s.inlineLen
685
+ structBefore += s.structLen
686
+ si++
687
+ so = 0
688
+ } else {
689
+ // strict interior delete: remove inside the inline node
690
+ const k = math.min(s.inlineLen - so, rem)
691
+ if (openSegIdx !== si) {
692
+ flushModify()
693
+ openModifyFor(si)
694
+ }
695
+ alignInner(so)
696
+ openInner.delete(k)
697
+ openInnerEmitted += k
698
+ so += k
699
+ rem -= k
700
+ if (so >= s.inlineLen) {
701
+ structBefore += s.structLen
702
+ si++
703
+ so = 0
704
+ }
705
+ }
706
+ }
707
+ } else if (delta.$modifyOp.check(op)) {
708
+ skipEmpty()
709
+ const s = segs[si]
710
+ if (s !== undefined && s.isInline) {
711
+ // modify targets an element inside the inline node
712
+ if (openSegIdx !== si) {
713
+ flushModify()
714
+ openModifyFor(si)
715
+ }
716
+ alignInner(so)
717
+ openInner.modify(delta.clone(op.value), op.format, op.attribution)
718
+ openInnerEmitted += 1
719
+ so += 1
720
+ if (so >= s.inlineLen) {
721
+ structBefore += s.structLen
722
+ si++
723
+ so = 0
724
+ }
725
+ } else if (s !== undefined) {
726
+ flushModify()
727
+ alignA(structBefore + so)
728
+ a.modify(delta.clone(op.value), op.format, op.attribution)
729
+ emittedStruct += 1
730
+ so += 1
731
+ if (so >= s.inlineLen) {
732
+ structBefore += s.structLen
733
+ si++
734
+ so = 0
735
+ }
736
+ }
737
+ }
738
+ }
739
+ flushModify()
740
+ // place the change's root marks. Pass-through / attr-key marks are structured root marks. Inner
741
+ // marks not consumed by a modify above are emitted as exact mark-only modifies for a pure cursor
742
+ // move, else collapsed to the inline node's structured position (best-effort).
743
+ for (const m of passMarks) delta.addRootMark(a, m)
744
+ if (innerBySi.size > 0) {
745
+ if (!hadChildOps) {
746
+ let cursor = emittedStruct
747
+ for (const [segIdx, marks] of [...innerBySi.entries()].sort((x, y) => x[0] - y[0])) {
748
+ if (segStart[segIdx] > cursor) { a.retain(segStart[segIdx] - cursor); cursor = segStart[segIdx] }
749
+ const mod = /** @type {any} */ (delta.create())
750
+ for (const { innerKey, mark } of marks) delta.addRootMark(mod, mark.copy(innerKey))
751
+ a.modify(mod.done(false))
752
+ cursor += 1
753
+ }
754
+ } else {
755
+ for (const [segIdx, marks] of innerBySi) {
756
+ for (const { mark } of marks) delta.addRootMark(a, mark.copy(segStart[segIdx]))
757
+ }
758
+ }
759
+ }
760
+ if (db.deleteMarks !== null) {
761
+ for (const id of db.deleteMarks) delta.deleteRootMark(a, id)
762
+ }
763
+ applySegsChange(this.segs, a, this.names)
764
+ a.done(false)
765
+ return createTransformResult(a, null)
766
+ }
767
+ }
768
+
769
+ /**
770
+ * Template for {@link InlineTransformer}. Typed loosely: the inlined output shape depends on the
771
+ * runtime `names` and document content, so the OUT side is `any`.
772
+ *
773
+ * @template {delta.DeltaConf} [IN=any]
774
+ * @extends {Template<IN, any>}
775
+ */
776
+ export class Inline extends Template {
777
+ /**
778
+ * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d
779
+ * @param {Array<string|null>} names the node names to inline (`null` selects anonymous nodes)
780
+ */
781
+ constructor ($d, names) {
782
+ super($d, /** @type {any} */ (delta.$deltaAny))
783
+ this.names = names
784
+ }
785
+
786
+ get name () { return 'lib0:inline:' + this.names.join(',') }
787
+
788
+ /**
789
+ * @return {Transformer<IN, any>}
790
+ */
791
+ init () {
792
+ return new InlineTransformer(this.$in, this.$out, this.names)
793
+ }
794
+ }
795
+
796
+ /**
797
+ * Inline child nodes whose name is in `names` by splicing each one's children into its parent (one
798
+ * level; e.g. with `[null]`: `<p>some<>text</></p>` <-> `<p>sometext</p>`). Use `null` for
799
+ * anonymous/typeless nodes, or a node name like `'b'`. Returns a reusable {@link Inline} template
800
+ * (a `project` hole, or `.init()` for a standalone transformer).
801
+ *
802
+ * @template {delta.DeltaConf} IN
803
+ * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d
804
+ * @param {Array<string|null>} names
805
+ * @return {Inline<IN>}
806
+ */
807
+ export const inline = ($d, names) => new Inline($d, names)
808
+
809
+ /**
810
+ * Convenience {@link Inline} factory that inlines anonymous ("null") child nodes
811
+ * (`<p>some<>text</></p>` <-> `<p>sometext</p>`) - i.e. `inline($d, [null])`.
812
+ *
813
+ * @template {delta.DeltaConf} IN
814
+ * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d
815
+ * @return {Inline<IN>}
816
+ */
817
+ /* @__NO_SIDE_EFFECTS__ */
818
+ export const inlineNullNodes = ($d) => inline($d, [null])