edytor 5.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +2 -2
  2. package/dist/cloudflare/room/context.d.ts +62 -74
  3. package/dist/crdt/doc/history.js +2 -3
  4. package/dist/crdt/doc/ops.d.ts +81 -0
  5. package/dist/crdt/doc/ops.js +1898 -0
  6. package/dist/crdt/edytor-doc.d.ts +156 -156
  7. package/dist/crdt/edytor-doc.js +14 -42
  8. package/dist/crdt/flow.d.ts +4 -46
  9. package/dist/crdt/flow.js +2 -262
  10. package/dist/crdt/index.d.ts +62 -74
  11. package/dist/crdt/operations.d.ts +1 -1
  12. package/dist/crdt/placement/resolve.d.ts +1 -1
  13. package/dist/crdt/protocols/awareness.js +46 -81
  14. package/dist/crdt/providers/index.d.ts +10 -22
  15. package/dist/crdt/providers/indexeddb.d.ts +6 -10
  16. package/dist/crdt/providers/indexeddb.js +10 -16
  17. package/dist/crdt/providers/room.d.ts +11 -0
  18. package/dist/crdt/providers/room.js +26 -12
  19. package/dist/crdt/providers/websocket.d.ts +6 -12
  20. package/dist/crdt/providers/websocket.js +8 -15
  21. package/dist/crdt/rangeDelete.d.ts +2 -47
  22. package/dist/crdt/rangeDelete.js +1 -169
  23. package/dist/crdt/text/index/build.d.ts +125 -0
  24. package/dist/crdt/text/index/build.js +2895 -0
  25. package/dist/crdt/text/index/shared.d.ts +2 -6
  26. package/dist/crdt/text/index/shared.js +2 -5
  27. package/dist/crdt/text/model.js +2 -3
  28. package/dist/crdt/text/runs.d.ts +9 -12
  29. package/dist/crdt/text/runs.js +2 -165
  30. package/dist/events/domTextOffset.d.ts +2 -0
  31. package/dist/events/domTextOffset.js +8 -0
  32. package/dist/export/forms.d.ts +50 -50
  33. package/dist/plugins/richtext/RichTextPlugin.svelte +63 -61
  34. package/dist/plugins/richtext/richTextOperations.js +26 -32
  35. package/dist/selection/selection.utils.js +5 -8
  36. package/dist/session/handles.js +3 -7
  37. package/dist/surface/observer.svelte.js +15 -30
  38. package/dist/surface/projector.svelte.js +2 -2
  39. package/dist/utils.d.ts +5 -0
  40. package/dist/utils.js +7 -0
  41. package/package.json +1 -1
  42. package/dist/crdt/doc/capability.d.ts +0 -56
  43. package/dist/crdt/doc/capability.js +0 -207
  44. package/dist/crdt/doc/content.d.ts +0 -16
  45. package/dist/crdt/doc/content.js +0 -77
  46. package/dist/crdt/doc/delete.d.ts +0 -15
  47. package/dist/crdt/doc/delete.js +0 -84
  48. package/dist/crdt/doc/layout.d.ts +0 -9
  49. package/dist/crdt/doc/layout.js +0 -178
  50. package/dist/crdt/doc/meta.d.ts +0 -19
  51. package/dist/crdt/doc/meta.js +0 -198
  52. package/dist/crdt/doc/moves.d.ts +0 -21
  53. package/dist/crdt/doc/moves.js +0 -274
  54. package/dist/crdt/doc/split-merge.d.ts +0 -10
  55. package/dist/crdt/doc/split-merge.js +0 -179
  56. package/dist/crdt/doc/steps.d.ts +0 -65
  57. package/dist/crdt/doc/steps.js +0 -311
  58. package/dist/crdt/text/index/cache.d.ts +0 -16
  59. package/dist/crdt/text/index/cache.js +0 -175
  60. package/dist/crdt/text/index/claims.d.ts +0 -18
  61. package/dist/crdt/text/index/claims.js +0 -203
  62. package/dist/crdt/text/index/fold.d.ts +0 -21
  63. package/dist/crdt/text/index/fold.js +0 -656
  64. package/dist/crdt/text/index/layout.d.ts +0 -22
  65. package/dist/crdt/text/index/layout.js +0 -258
  66. package/dist/crdt/text/index/placement.d.ts +0 -47
  67. package/dist/crdt/text/index/placement.js +0 -738
  68. package/dist/crdt/text/index/records.d.ts +0 -13
  69. package/dist/crdt/text/index/records.js +0 -139
  70. package/dist/crdt/text/index/report.d.ts +0 -18
  71. package/dist/crdt/text/index/report.js +0 -337
  72. package/dist/crdt/text/index/state.d.ts +0 -197
  73. package/dist/crdt/text/index/state.js +0 -116
  74. package/dist/crdt/text/index/streams.d.ts +0 -25
  75. package/dist/crdt/text/index/streams.js +0 -248
@@ -1,738 +0,0 @@
1
- /**
2
- * Placements and the children index (maintained), the display
3
- * ownership the display reads (`ownShim`), and THE shown-kind rule
4
- * (`typeOf`).
5
- *
6
- * A structural change re-decides only what it can change: the display
7
- * edge of the blocks whose slot, record or mover changed (fork patch
8
- * YP22), and the display slot of every block whose slot walk
9
- * (`displaySlotOf`) reads one that changed — its stored subtree, through
10
- * the blocks it displays — patched into the parents' child lists. A
11
- * block's candidate edges are its live move records, strongest first, then
12
- * its home slot; it shows at the first that closes no cycle through the
13
- * accepted edges (`settle`): the later move wins, and the block whose edge
14
- * is the oldest of a cycle falls back to its next candidate, back where it
15
- * stood (its previous record, else home; the root's tail only when every
16
- * candidate closes a cycle), so a cycle resolves locally: no global pass.
17
- * The first build places every block the same way (their unique acyclic
18
- * acceptance, whatever the order; the tests' greedy rebuild,
19
- * `src/tests/oracles/placements.ts`, is what the self-checks compare). A
20
- * placed block's order key is found from its sequence neighbours
21
- * (`placement/keys.ts`), never by walking the parent's sequence; a rebuild
22
- * keys every list once, in its sequence's order. The layout rules re-run
23
- * only on the layouts a change reaches.
24
- */
25
- import { bySlot, childrenIndex, displaySlotOf, documentOrder } from '../../placement/model.js';
26
- import { denseKey, keyBetween } from '../../placement/keys.js';
27
- import { NO_SLOT, byTail, candidatesOf, cycleThrough, edgeParentOf, collectSlots, homeOf, registryOwners, ownerOfNode, positionsOfItem, slotOwner, slotsBefore } from '../../placement/resolve.js';
28
- import { cmpStamp, sameUnit } from '../../placement/slots.js';
29
- import { Y } from '../../engine.js';
30
- import { engineOps, firstItem } from '../../structs.js';
31
- import { DEAD, displayOf } from '../model.js';
32
- /** Placement maintenance of one doc's index, over its state, claims, streams and layout rules. */
33
- export const indexPlacement = (ix) => {
34
- const { doc, registry, blocks, displaysMap, ownerChanged, placementsMap, following, dirtyLists, leftLists, placementSeeds, stateSeeds, kindsOf, slotsByBlock, rawOrder, edges, fallen, rejected, ensureOwners, ownerOf, top, streamOf, streamsIn, streamAt, kindOf, lineKind, itemKind, rowKind, rowOfTable, cellKind, cellRank, dissolve, redissolve } = ix;
35
- const kept = engineOps(Y).isKeptReplaced;
36
- // ── placements and the children index (maintained) ────────────
37
- /** The document holds a layout or an item kind: `kids0` and `kidsMap` differ. */
38
- let layoutMode = false;
39
- /** The line kinds the roles declare, and those of the lines islands the document holds. */
40
- let lineKinds = new Set();
41
- let orderCache = null;
42
- let layoutBlocks = 0;
43
- let itemBlocks = 0;
44
- /** Tables, rows and cells (`table.*`): their rules run in the layout pass. */
45
- let tableBlocks = 0;
46
- const lineCounts = new Map();
47
- const ownShim = {
48
- ownerOf,
49
- hidden: (b) => ownerOf(b) !== b || ix.dissolved.has(b),
50
- childless: (b) => kindOf(b)?.childless === true,
51
- island: (b) => kindOf(b)?.island === true,
52
- container: (b) => kindOf(b)?.container === true,
53
- lined: (b) => lineKind(b) !== undefined,
54
- passes: (b) => ix.dissolved.has(b),
55
- sheds: (owner, child) => {
56
- // A layout shows only its items, a table its rows, a row its cells.
57
- const only = itemKind(owner) ?? rowKind(owner) ?? cellKind(owner);
58
- return only !== undefined && blocks.get(child)?.type !== only;
59
- },
60
- slotRank: (owner, id) => cellRank(owner, id),
61
- top,
62
- claimIndex: (holder, m) => blocks.get(holder)?.claims.findIndex((c) => c.m === m) ?? -1,
63
- streamOf,
64
- streamsIn,
65
- streamAt,
66
- display: (b) => displayOf(b, blocks, ownShim)
67
- };
68
- /** Re-count `id`'s kind facts; a fact the roles never declared forces a full pass. */
69
- const noteKind = (id) => {
70
- const old = kindsOf.get(id);
71
- const type = blocks.get(id)?.type;
72
- const now = type === undefined
73
- ? undefined
74
- : {
75
- layout: itemKind(id) !== undefined,
76
- item: ix.itemKinds.has(type),
77
- line: lineKind(id),
78
- table: rowKind(id) !== undefined,
79
- row: ix.rowKinds.has(type),
80
- cell: ix.cellKinds.has(type)
81
- };
82
- const tableish = (k) => k?.table || k?.row || k?.cell ? 1 : 0;
83
- tableBlocks += tableish(now) - tableish(old);
84
- if (old?.layout)
85
- layoutBlocks--;
86
- if (old?.item)
87
- itemBlocks--;
88
- if (old?.line !== undefined)
89
- lineCounts.set(old.line, lineCounts.get(old.line) - 1);
90
- if (now === undefined)
91
- kindsOf.delete(id);
92
- else
93
- kindsOf.set(id, now);
94
- if (now?.layout)
95
- layoutBlocks++;
96
- if (now?.item)
97
- itemBlocks++;
98
- if (now?.line !== undefined)
99
- lineCounts.set(now.line, (lineCounts.get(now.line) ?? 0) + 1);
100
- const item = itemKind(id);
101
- if (item !== undefined && !ix.itemKinds.has(item))
102
- ix.placementFull = true;
103
- if (now?.line !== undefined && !lineKinds.has(now.line))
104
- ix.placementFull = true;
105
- // A table of a kind whose row the index does not know yet: its rows and cells change kind facts.
106
- const rows = type === undefined ? undefined : rowOfTable(type);
107
- if (rows !== undefined && ix.rowKinds.get(rows[0]) !== rows[1])
108
- ix.placementFull = true;
109
- if (layoutMode !== layoutBlocks + itemBlocks + tableBlocks > 0)
110
- ix.placementFull = true;
111
- };
112
- /** The ownership the layout rules read: none of their decisions applied. */
113
- const own0 = {
114
- ...ownShim,
115
- hidden: (b) => ownerOf(b) !== b,
116
- passes: () => false
117
- };
118
- /** `id`'s slot in the index `own` reads (none: hidden, or under no live parent). */
119
- const slotIn = (own, id) => {
120
- const pl = placementsMap.get(id);
121
- if (pl === undefined || !blocks.has(id) || own.hidden(id))
122
- return undefined;
123
- const { parent, rank, reset } = displaySlotOf(own, placementsMap, pl, id);
124
- if (parent === DEAD)
125
- return undefined;
126
- return reset === null ? { parent, rank } : { parent, rank, reset };
127
- };
128
- /** The index of the first slot of `list` not before `x`. */
129
- const seek = (list, x) => {
130
- let lo = 0;
131
- let hi = list.length;
132
- while (lo < hi) {
133
- const mid = (lo + hi) >> 1;
134
- if (bySlot(list[mid], x) < 0)
135
- lo = mid + 1;
136
- else
137
- hi = mid;
138
- }
139
- return lo;
140
- };
141
- /**
142
- * Re-decide the slots of `affected` in `kids`/`slotMap` (read through
143
- * `own`), patching the child lists they leave or join (each list
144
- * copied once: a list a reader holds never changes under it). Returns
145
- * the blocks whose slot changed, with their parents before and after.
146
- */
147
- const patch = (kids, slotMap, own, affected) => {
148
- const changed = new Map();
149
- const copies = new Map();
150
- const list = (p) => {
151
- let l = copies.get(p);
152
- if (l === undefined)
153
- copies.set(p, (l = [...(kids.get(p) ?? [])]));
154
- return l;
155
- };
156
- for (const id of affected) {
157
- const old = slotMap.get(id);
158
- const next = slotIn(own, id);
159
- if (old?.parent === next?.parent &&
160
- old?.rank === next?.rank &&
161
- old?.reset === next?.reset &&
162
- (old === undefined) === (next === undefined))
163
- continue;
164
- if (old !== undefined) {
165
- const l = list(old.parent);
166
- const at = seek(l, { id, rank: old.rank });
167
- if (l[at]?.id === id)
168
- l.splice(at, 1);
169
- }
170
- if (next !== undefined) {
171
- const l = list(next.parent);
172
- const slot = next.reset === undefined
173
- ? { id, rank: next.rank }
174
- : { id, rank: next.rank, reset: next.reset };
175
- l.splice(seek(l, slot), 0, slot);
176
- slotMap.set(id, next);
177
- }
178
- else {
179
- slotMap.delete(id);
180
- if (kids === ix.kidsMap)
181
- leftLists.add(id);
182
- }
183
- changed.set(id, [old?.parent, next?.parent]);
184
- }
185
- for (const [p, l] of copies) {
186
- if (l.length === 0)
187
- kids.delete(p);
188
- else
189
- kids.set(p, l);
190
- if (kids === ix.kidsMap)
191
- dirtyLists.add(p);
192
- }
193
- return changed;
194
- };
195
- /** `seeds` and every block whose slot walk reads one of them: stored subtrees, through what each displays. */
196
- const reach = (seeds) => {
197
- const out = new Set();
198
- const stack = [...seeds];
199
- for (let x = stack.pop(); x !== undefined; x = stack.pop()) {
200
- if (out.has(x))
201
- continue;
202
- out.add(x);
203
- for (const c of rawOrder.get(x) ?? [])
204
- stack.push(c.id);
205
- for (const d of displaysMap.get(x) ?? [])
206
- if (d !== x)
207
- for (const c of rawOrder.get(d) ?? [])
208
- stack.push(c.id);
209
- }
210
- return out;
211
- };
212
- /** Whether `id` shows as its slot's kind (`following`): read from its slot and kinds. */
213
- const noteFollowing = (id) => {
214
- const slot = ix.slots.get(id);
215
- const follows = slot !== undefined &&
216
- (slot.reset !== undefined ||
217
- (slot.parent !== null && lineKind(slot.parent) !== undefined) ||
218
- lineKinds.has(blocks.get(id)?.type ?? ''));
219
- if (follows)
220
- following.add(id);
221
- else
222
- following.delete(id);
223
- };
224
- // ── edges, keys and acceptance ─────────────────────────────────
225
- /**
226
- * Whether the slot map has been read from the sequences: once, before the
227
- * first rebuild (a shell settled on the first fold asks for a home), then
228
- * the fold keeps it and every rebuild fills it again.
229
- */
230
- let slotsRead = false;
231
- /** What the first read collected, for a rebuild while no sequence changed since (one scan). */
232
- let collected = null;
233
- let collectedAt = -1;
234
- const readSlots = () => {
235
- slotsRead = true;
236
- slotsByBlock.clear();
237
- // Every registry node, its kept incarnations too (the records may not all be built yet).
238
- collected = collectSlots(doc, registryOwners(registry, kept));
239
- collectedAt = ix.seqEpoch;
240
- for (const [b, refs] of collected.slots)
241
- slotsByBlock.set(b, refs);
242
- };
243
- /** Put `ref` (`null`: none) where the slot map holds unit `u` of the slots naming id `b`. */
244
- const putSlot = (b, u, ref) => {
245
- const list = (slotsByBlock.get(b) ?? []).filter((x) => !sameUnit(x, u));
246
- if (ref !== null)
247
- list.push(ref);
248
- if (list.length === 0)
249
- slotsByBlock.delete(b);
250
- else
251
- slotsByBlock.set(b, list);
252
- };
253
- /** `id`'s home slot among the slots naming its registry id (`null`: none). */
254
- const homeFor = (id) => {
255
- if (!slotsRead)
256
- readSlots();
257
- return homeOf(registry, kept, slotsByBlock, id);
258
- };
259
- /**
260
- * Re-own the home slots of `node`'s sequence after the registry value it
261
- * stands for changed (a concurrent creation of one id won or lost, its
262
- * losing incarnation shown or not): each slot's parent is the node's owner
263
- * now, and its block re-placed; a node that is no block of the document
264
- * any more holds no slot (as a rebuild reads it).
265
- */
266
- const reslot = (node) => {
267
- if (!slotsRead)
268
- return; // the first read collects them as they are
269
- const owner = ownerOfNode(registry, kept, node);
270
- for (let it = firstItem(node); it !== null; it = it.right)
271
- for (const { value, unit, via } of positionsOfItem(doc, it)) {
272
- if (via !== null)
273
- continue; // a record's unit is another sequence's slot
274
- const b = value.b;
275
- putSlot(b, unit, owner === undefined ? null : { parent: owner, ...unit });
276
- for (const id of new Set([b, slotOwner(registry, kept, b, unit.c)])) {
277
- const rec = blocks.get(id);
278
- if (rec !== undefined)
279
- rec.home = homeOf(registry, kept, slotsByBlock, id);
280
- placementSeeds.add(id);
281
- }
282
- }
283
- };
284
- /** The slot items a rebuild collected (its homes' items, read once), while it places every block. */
285
- let known = null;
286
- /** `id`'s candidate edges, read from the engine: its live records, strongest first, then home (`[]`: no slot, or gone). */
287
- const candidatesFor = (id) => {
288
- const home = blocks.get(id)?.home ?? homeFor(id);
289
- return home === null ? [] : candidatesOf(doc, registry, kept, home, known?.get(home));
290
- };
291
- /** The owner of the sequence `id`'s slot shows in as the engine stands (its mover's, else home's; `null`: the root). */
292
- const edgeParent = (id) => {
293
- const home = blocks.get(id)?.home ?? homeFor(id);
294
- return home === null ? undefined : edgeParentOf(doc, registry, kept, home);
295
- };
296
- /** The raw children of `parent` in order (every block whose edge parent it is), kept sorted by key. */
297
- const rawList = (parent) => {
298
- let l = rawOrder.get(parent);
299
- if (l === undefined)
300
- rawOrder.set(parent, (l = []));
301
- return l;
302
- };
303
- /** Counts the placement writes: a key's walk that saw one happen under it walks again. */
304
- let placedEpoch = 0;
305
- /** Whether a placement is keyed as it is decided: not while a rebuild places every block (it keys them after, in one pass). */
306
- let keyed = true;
307
- const setPlacement = (id, pl) => {
308
- ix.foldStats.placed++;
309
- placedEpoch++;
310
- const old = placementsMap.get(id);
311
- if (keyed && old !== undefined) {
312
- const l = rawList(old.parent);
313
- const at = seek(l, { id, rank: old.rank });
314
- if (l[at]?.id === id)
315
- l.splice(at, 1);
316
- if (l.length === 0)
317
- rawOrder.delete(old.parent);
318
- }
319
- if (pl === undefined)
320
- placementsMap.delete(id);
321
- else {
322
- placementsMap.set(id, pl);
323
- if (!keyed)
324
- return;
325
- const l = rawList(pl.parent);
326
- const slot = { id, rank: pl.rank };
327
- l.splice(seek(l, slot), 0, slot);
328
- }
329
- };
330
- /**
331
- * The key `id` takes under `edge.parent`: between the key of the nearest
332
- * block shown where it stands before `id`'s place in the sequence (placed
333
- * first when it is a pending seed; walked past when it shows elsewhere)
334
- * and the next key of that list. Placing a pending neighbour can drop a
335
- * block the walk already passed back into that stretch (a cycle's
336
- * fallback, back where it stood): a walk under which a placement changed
337
- * walks again, so the key it returns reads one state.
338
- */
339
- const keyFor = (id, edge, place) => {
340
- for (;;) {
341
- const before = placedEpoch;
342
- const lrank = leftKey(id, edge, place);
343
- if (placedEpoch !== before)
344
- continue;
345
- const list = rawOrder.get(edge.parent) ?? [];
346
- const i = lrank === undefined ? 0 : seek(list, { id: '', rank: lrank }) + 1;
347
- return keyBetween(lrank, list[i]?.rank);
348
- }
349
- };
350
- /** The key of the nearest block shown before `id`'s place at `edge`, under its parent (`undefined`: none). */
351
- const leftKey = (id, edge, place) => {
352
- const home = blocks.get(id)?.home ?? homeFor(id);
353
- for (const e of slotsBefore(doc, edge, home)) {
354
- const b = slotOwner(registry, kept, e.value.b, e.unit.c);
355
- // `id` itself (its place before this one) keys nothing.
356
- if (b === id)
357
- continue;
358
- // A slot that is not its block's home (a second creation of one id) places nothing.
359
- const h = blocks.get(b)?.home ?? homeFor(b);
360
- if (h === null || !sameUnit(h, e.unit))
361
- continue;
362
- place(b);
363
- const pl = placementsMap.get(b);
364
- const shows = edges.get(b);
365
- if (pl !== undefined &&
366
- pl.parent === edge.parent &&
367
- !rejected.has(b) &&
368
- shows != null &&
369
- shows.via === e.via)
370
- return pl.rank;
371
- }
372
- return undefined;
373
- };
374
- /** The key of `id` at the root's tail, among the rejected by stamp then id, after the last top-level block. */
375
- const tailKey = (id, stamp) => {
376
- const tail = [...rejected]
377
- .filter((x) => x !== id)
378
- .map((x) => ({ id: x, stamp: edges.get(x)?.stamp ?? NO_SLOT }))
379
- .sort(byTail);
380
- const list = rawOrder.get(null) ?? [];
381
- const me = { id, stamp };
382
- let at = 0;
383
- while (at < tail.length && byTail(tail[at], me) < 0)
384
- at++;
385
- const prev = at > 0
386
- ? placementsMap.get(tail[at - 1].id)?.rank
387
- : list[list.length - tail.length - 1]?.rank;
388
- const next = at < tail.length ? placementsMap.get(tail[at].id)?.rank : undefined;
389
- return keyBetween(prev, next);
390
- };
391
- /** Show `id` at the root's tail: every edge closes a cycle, or none. */
392
- const reject = (id, edge) => {
393
- edges.set(id, edge);
394
- if (placementsMap.has(id))
395
- setPlacement(id, undefined);
396
- fallen.delete(id);
397
- rejected.add(id);
398
- const rank = keyed ? tailKey(id, edge?.stamp ?? NO_SLOT) : '';
399
- setPlacement(id, { parent: null, rank });
400
- };
401
- /** The same display edge: the same sequence, the same record, the same stamp. */
402
- const sameEdge = (a, b) => a.parent === b.parent && a.via === b.via && cmpStamp(a.stamp, b.stamp) === 0;
403
- /** Whether `edge` (of `id`) is a place `id` may take now: a live parent, and no cycle it would be the oldest edge of. */
404
- const admits = (id, edge) => {
405
- const parent = edge.parent;
406
- if (parent === undefined || (parent !== null && !blocks.has(parent)))
407
- return { ok: false, drop: null };
408
- const cycle = cycleThrough(placementsMap, ownerOf, id, parent);
409
- if (cycle === null)
410
- return { ok: true, drop: null };
411
- let [min, minStamp] = [id, edge.stamp];
412
- for (const m of cycle) {
413
- const e = m === id ? edge : edges.get(m);
414
- if (m !== id && e && cmpStamp(e.stamp, minStamp) < 0)
415
- [min, minStamp] = [m, e.stamp];
416
- }
417
- return min === id ? { ok: false, drop: null } : { ok: true, drop: min };
418
- };
419
- /**
420
- * Place `id` at its strongest candidate edge from `from` on that closes no
421
- * cycle it would be the oldest edge of (the later move wins). When the
422
- * edge closes a cycle through an older edge, that edge's block is dropped
423
- * to its next candidate (back where it stood before that move), and so on
424
- * down its records to home; a block every candidate of which fails shows
425
- * at the root's tail. `place` places a pending neighbour first. Returns
426
- * the blocks whose placement changed.
427
- */
428
- const settle = (id, place, from = 0) => {
429
- const changed = [id];
430
- const cands = candidatesFor(id);
431
- for (let n = from; n < cands.length; n++) {
432
- const edge = cands[n];
433
- // The key first: its walk places the pending neighbours, so the cycle
434
- // check reads them placed (a neighbour placed after it could close a
435
- // cycle through `id` that no check saw).
436
- const rank = keyed ? keyFor(id, edge, place) : '';
437
- const { ok, drop } = admits(id, edge);
438
- if (!ok)
439
- continue;
440
- edges.set(id, edge);
441
- rejected.delete(id);
442
- if (n > 0)
443
- fallen.add(id);
444
- else
445
- fallen.delete(id);
446
- if (placementsMap.has(id))
447
- setPlacement(id, undefined);
448
- setPlacement(id, { parent: edge.parent, rank });
449
- if (drop !== null) {
450
- const now = edges.get(drop);
451
- const at = now ? candidatesFor(drop).findIndex((c) => sameEdge(c, now)) : -1;
452
- changed.push(...settle(drop, () => { }, at + 1));
453
- }
454
- return changed;
455
- }
456
- reject(id, cands[0] ?? null);
457
- return changed;
458
- };
459
- /** Whether a candidate stronger than `id`'s accepted edge would take it now. */
460
- const canRise = (id) => {
461
- const cands = candidatesFor(id);
462
- const now = edges.get(id);
463
- const at = rejected.has(id) || !now ? cands.length : cands.findIndex((c) => sameEdge(c, now));
464
- for (let n = 0; n < (at < 0 ? cands.length : at); n++)
465
- if (admits(id, cands[n]).ok)
466
- return true;
467
- return false;
468
- };
469
- /**
470
- * Try every block below its strongest edge again (a changed edge may have
471
- * broken its cycle, or made another the one to drop): those that rise join
472
- * `moved`.
473
- */
474
- const retryFallen = (moved) => {
475
- for (let round = 0; round < fallen.size + rejected.size + 2; round++) {
476
- let changed = false;
477
- const below = [...rejected, ...fallen]
478
- .map((x) => ({ id: x, stamp: candidatesFor(x)[0]?.stamp ?? NO_SLOT }))
479
- .sort((a, b) => -byTail(a, b));
480
- for (const { id } of below) {
481
- if (!canRise(id))
482
- continue;
483
- for (const x of settle(id, () => { }))
484
- moved.add(x);
485
- changed = true;
486
- }
487
- if (!changed)
488
- break;
489
- }
490
- };
491
- /**
492
- * Everything rebuilt from the sequences (the first build, roles): every
493
- * block placed as an edit places one (`settle`, then `retryFallen`) —
494
- * the one acyclic acceptance, whatever the order the blocks come in —
495
- * then keyed in one pass over each sequence's places (dense keys in its
496
- * order), the root's tail after the last top-level block.
497
- */
498
- const rebuildPlacements = () => {
499
- ix.placementFull = false;
500
- placementSeeds.clear();
501
- stateSeeds.clear();
502
- ownerChanged.clear();
503
- const declared = ix.roles?.declared();
504
- ix.itemKinds = new Set(declared?.items);
505
- for (const b of blocks.keys()) {
506
- const item = itemKind(b);
507
- if (item !== undefined)
508
- ix.itemKinds.add(item);
509
- }
510
- lineKinds = new Set(declared?.lines);
511
- ix.rowKinds = new Map(declared?.rows);
512
- for (const b of blocks.keys()) {
513
- const type = blocks.get(b)?.type;
514
- const rows = type === undefined ? undefined : rowOfTable(type);
515
- if (rows !== undefined && rowKind(b) !== undefined)
516
- ix.rowKinds.set(rows[0], rows[1]);
517
- }
518
- ix.cellKinds = new Set(ix.rowKinds.values());
519
- kindsOf.clear();
520
- [layoutBlocks, itemBlocks, tableBlocks] = [0, 0, 0];
521
- lineCounts.clear();
522
- for (const b of blocks.keys())
523
- noteKind(b);
524
- for (const line of lineCounts.keys())
525
- lineKinds.add(line);
526
- ix.placementFull = false;
527
- // The first read's slots while no sequence changed since (one scan).
528
- const { slots, items, places } = collected !== null && collectedAt === ix.seqEpoch
529
- ? collected
530
- : collectSlots(doc, blocks.values());
531
- collected = null;
532
- slotsByBlock.clear();
533
- for (const [b, refs] of slots)
534
- slotsByBlock.set(b, refs);
535
- slotsRead = true;
536
- placementsMap.clear();
537
- edges.clear();
538
- rawOrder.clear();
539
- fallen.clear();
540
- rejected.clear();
541
- [keyed, known] = [false, items];
542
- for (const id of blocks.keys())
543
- settle(id, () => { });
544
- if (fallen.size + rejected.size > 0)
545
- retryFallen(new Set());
546
- [keyed, known] = [true, null];
547
- // The lists in key order: a block where its edge shows it, in its sequence's order.
548
- for (const [owner, list] of places)
549
- for (const e of list) {
550
- const b = slotOwner(registry, kept, e.value.b, e.unit.c);
551
- const pl = placementsMap.get(b);
552
- const h = blocks.get(b)?.home;
553
- if (pl?.parent !== owner || rejected.has(b) || !h || !sameUnit(h, e.unit))
554
- continue;
555
- if (edges.get(b)?.via !== e.via)
556
- continue;
557
- const l = rawList(owner);
558
- pl.rank = denseKey(l.length);
559
- l.push({ id: b, rank: pl.rank });
560
- }
561
- // The root's tail, keyed as an edit keys a rejected block, in its order.
562
- const tail = [...rejected].map((x) => ({ id: x, stamp: edges.get(x)?.stamp ?? NO_SLOT }));
563
- rejected.clear();
564
- for (const { id } of tail.sort(byTail))
565
- reject(id, edges.get(id) ?? null);
566
- layoutMode = layoutBlocks + itemBlocks + tableBlocks > 0;
567
- ix.dissolved = new Set();
568
- const index = (map) => {
569
- const at = new Map();
570
- for (const [parent, list] of map)
571
- for (const { id, rank, reset } of list)
572
- at.set(id, reset === undefined ? { parent, rank } : { parent, rank, reset });
573
- return at;
574
- };
575
- ix.kids0 = childrenIndex(placementsMap, own0);
576
- ix.slots0 = index(ix.kids0);
577
- if (layoutMode) {
578
- ix.dissolved = dissolve(ix.kids0);
579
- ix.kidsMap = childrenIndex(placementsMap, ownShim);
580
- ix.slots = index(ix.kidsMap);
581
- }
582
- else
583
- [ix.kidsMap, ix.slots] = [ix.kids0, ix.slots0];
584
- following.clear();
585
- for (const id of ix.slots.keys())
586
- noteFollowing(id);
587
- orderCache = null;
588
- ix.kidsVersion++;
589
- ix.allListsDirty = true;
590
- };
591
- const ensurePlacements = () => {
592
- ensureOwners();
593
- if (ix.placementFull)
594
- return rebuildPlacements();
595
- if (placementSeeds.size + stateSeeds.size + ownerChanged.size === 0)
596
- return;
597
- const moved = new Set();
598
- const seeds = new Set(placementSeeds);
599
- const done = new Set();
600
- /** Place one seed (its left neighbour first, when it is a seed too). */
601
- const place = (id) => {
602
- if (done.has(id) || !seeds.has(id))
603
- return;
604
- done.add(id);
605
- if (!blocks.has(id)) {
606
- if (placementsMap.has(id)) {
607
- setPlacement(id, undefined);
608
- edges.delete(id);
609
- fallen.delete(id);
610
- rejected.delete(id);
611
- moved.add(id);
612
- }
613
- return;
614
- }
615
- const top = candidatesFor(id)[0] ?? null;
616
- const old = edges.get(id);
617
- // The same edge, placed at its strongest: its key still orders it (its neighbours' keys are theirs).
618
- if (top !== null &&
619
- old &&
620
- !rejected.has(id) &&
621
- !fallen.has(id) &&
622
- placementsMap.has(id) &&
623
- sameEdge(top, old))
624
- return;
625
- for (const x of settle(id, place))
626
- moved.add(x);
627
- };
628
- for (const id of [...seeds])
629
- place(id);
630
- // A changed claim owner can close a cycle through a display edge that did not change.
631
- for (const o of ownerChanged)
632
- for (const c of [...(rawOrder.get(o) ?? [])]) {
633
- const edge = edges.get(c.id);
634
- if (!edge || rejected.has(c.id) || edge.parent === undefined)
635
- continue;
636
- if (cycleThrough(placementsMap, ownerOf, c.id, edge.parent) !== null)
637
- for (const x of settle(c.id, () => { }))
638
- moved.add(x);
639
- }
640
- if (rejected.size + fallen.size > 0 && (moved.size > 0 || ownerChanged.size > 0))
641
- retryFallen(moved);
642
- const seeds2 = new Set([...moved, ...stateSeeds, ...ownerChanged]);
643
- const kinds = [...stateSeeds];
644
- placementSeeds.clear();
645
- stateSeeds.clear();
646
- ownerChanged.clear();
647
- const affected = reach(seeds2);
648
- let touched = affected;
649
- const changed = patch(ix.kids0, ix.slots0, own0, affected);
650
- let any = changed.size > 0;
651
- if (layoutMode) {
652
- const flips = redissolve(changed, kinds);
653
- const again = flips.size > 0 ? reach([...affected, ...flips]) : affected;
654
- any = patch(ix.kidsMap, ix.slots, ownShim, again).size > 0 || any;
655
- touched = again;
656
- }
657
- for (const id of touched)
658
- noteFollowing(id);
659
- if (any) {
660
- orderCache = null;
661
- ix.kidsVersion++;
662
- }
663
- };
664
- /**
665
- * A plain block (the document's default kind) directly in a container
666
- * whose item is a kind of its own that renders content (a list) shows
667
- * as that item — the read-time side of the facade's `fitted`: a race
668
- * (a peer's lift of an item another peer's outdent moves, a concurrent
669
- * undo of a retype) or an explicit write never shows a bare paragraph
670
- * in a list. Anywhere else it shows `plain`.
671
- */
672
- const itemOf = (under, plain) => {
673
- const parent = typeOf(under);
674
- const { container, child } = ix.roles.of(parent);
675
- return container && ix.roles.of(child).rendersContent ? child : plain;
676
- };
677
- /**
678
- * The kind `id` displays as — THE shown-kind rule, read from its slot
679
- * and the role table. A block directly in an island declared `lines`
680
- * shows its line kind. A block of a line kind anywhere else shows its
681
- * display parent's default child — an undo can put a line a peer
682
- * retyped back in its island, or leave one a peer moved away outside
683
- * it — and so does a block displayed out of an island
684
- * or a container (a list) that still has its default child
685
- * kind, unless an outer container shows it as one of its
686
- * items (a nested list's item). A block that renders
687
- * content never shows as a kind that does not: a line kind
688
- * then shows the document's default kind. A block stored as the
689
- * document's default kind directly in a list shows as its item
690
- * (`itemOf`). Any other shows its stored kind (a retype shows). Read from the stored kinds
691
- * at call time: a retype rebuilds no placement.
692
- */
693
- const typeOf = (id) => {
694
- const stored = blocks.get(id)?.type ?? 'unknown';
695
- const slot = ix.slots.get(id);
696
- const roles = ix.roles;
697
- if (roles === null || slot === undefined)
698
- return stored;
699
- const { parent: under, reset } = slot;
700
- const line = under === null || lineKinds.size === 0 ? undefined : lineKind(under);
701
- if (line !== undefined)
702
- return line;
703
- const lined = lineKinds.has(stored);
704
- const from = reset === undefined ? undefined : (blocks.get(reset)?.type ?? null);
705
- const plain = roles.of(null).child;
706
- if (!lined && (from === undefined || stored !== roles.of(from).child))
707
- return stored === plain && under !== null ? itemOf(under, stored) : stored;
708
- // Out of a removed list, inside an outer list of its kind: still an item. A
709
- // container whose item is the document's default kind (a column) holds no
710
- // items of its own: a paragraph under one is no list's item.
711
- if (!lined && typeof from === 'string' && roles.of(from).container)
712
- for (let u = under; u !== null; u = ix.slots.get(u)?.parent ?? null) {
713
- const t = roles.of(typeOf(u));
714
- if (t.container && t.child === stored && stored !== plain)
715
- return stored;
716
- }
717
- const kind = roles.of(under === null ? null : typeOf(under)).child;
718
- if (roles.of(kind).rendersContent || !roles.of(stored).rendersContent)
719
- return kind;
720
- // Never a line kind outside its island, even where the slot's kind shows nothing.
721
- return lined ? plain : stored;
722
- };
723
- /** The document order, cached until a child list changes. */
724
- const order = () => (orderCache ??= documentOrder(ix.kidsMap));
725
- return {
726
- ownShim,
727
- own0,
728
- noteKind,
729
- homeFor,
730
- putSlot,
731
- edgeParent,
732
- noteFollowing,
733
- ensurePlacements,
734
- reslot,
735
- typeOf,
736
- order
737
- };
738
- };