@sobree/core 0.1.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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/__vite-browser-external-DYxpcVy9.js +5 -0
  4. package/dist/__vite-browser-external-DYxpcVy9.js.map +1 -0
  5. package/dist/blob/cache.d.ts +69 -0
  6. package/dist/blob/fetch.d.ts +18 -0
  7. package/dist/blob/hash.d.ts +13 -0
  8. package/dist/blob/index.d.ts +33 -0
  9. package/dist/blob/memory.d.ts +2 -0
  10. package/dist/blob/types.d.ts +80 -0
  11. package/dist/createSobree.d.ts +132 -0
  12. package/dist/doc/api.d.ts +132 -0
  13. package/dist/doc/builders.d.ts +42 -0
  14. package/dist/doc/pageSetupBridge.d.ts +26 -0
  15. package/dist/doc/parts.d.ts +18 -0
  16. package/dist/doc/runs.d.ts +47 -0
  17. package/dist/doc/styles.d.ts +19 -0
  18. package/dist/doc/types.d.ts +800 -0
  19. package/dist/doc/walk.d.ts +30 -0
  20. package/dist/docx/export/contentTypes.d.ts +35 -0
  21. package/dist/docx/export/context.d.ts +59 -0
  22. package/dist/docx/export/document.d.ts +19 -0
  23. package/dist/docx/export/drawings.d.ts +10 -0
  24. package/dist/docx/export/headers.d.ts +19 -0
  25. package/dist/docx/export/index.d.ts +14 -0
  26. package/dist/docx/export/runs.d.ts +8 -0
  27. package/dist/docx/export/styles.d.ts +8 -0
  28. package/dist/docx/export/zip.d.ts +13 -0
  29. package/dist/docx/import/anchoredFrames.d.ts +34 -0
  30. package/dist/docx/import/comments.d.ts +3 -0
  31. package/dist/docx/import/document.d.ts +57 -0
  32. package/dist/docx/import/flowFrames.d.ts +11 -0
  33. package/dist/docx/import/footnotes.d.ts +3 -0
  34. package/dist/docx/import/headers.d.ts +50 -0
  35. package/dist/docx/import/index.d.ts +12 -0
  36. package/dist/docx/import/inlineFrames.d.ts +62 -0
  37. package/dist/docx/import/numbering.d.ts +2 -0
  38. package/dist/docx/import/paragraph.d.ts +24 -0
  39. package/dist/docx/import/paragraphs.d.ts +27 -0
  40. package/dist/docx/import/rels.d.ts +5 -0
  41. package/dist/docx/import/runs.d.ts +64 -0
  42. package/dist/docx/import/settings.d.ts +48 -0
  43. package/dist/docx/import/styles.d.ts +3 -0
  44. package/dist/docx/import/tables.d.ts +12 -0
  45. package/dist/docx/import/unzip.d.ts +13 -0
  46. package/dist/docx/shared/namespaces.d.ts +31 -0
  47. package/dist/docx/shared/pageSize.d.ts +27 -0
  48. package/dist/docx/shared/shading.d.ts +2 -0
  49. package/dist/docx/shared/units.d.ts +35 -0
  50. package/dist/docx/shared/xml.d.ts +29 -0
  51. package/dist/docx/types.d.ts +98 -0
  52. package/dist/editor/index.d.ts +1078 -0
  53. package/dist/editor/internal/blockRegistry.d.ts +91 -0
  54. package/dist/editor/internal/mutations.d.ts +63 -0
  55. package/dist/editor/internal/positionMap.d.ts +35 -0
  56. package/dist/editor/table.d.ts +96 -0
  57. package/dist/editor/view/docRenderer/anchorLayer.d.ts +26 -0
  58. package/dist/editor/view/docRenderer/block.d.ts +13 -0
  59. package/dist/editor/view/docRenderer/fontFallback.d.ts +28 -0
  60. package/dist/editor/view/docRenderer/index.d.ts +18 -0
  61. package/dist/editor/view/docRenderer/inline.d.ts +15 -0
  62. package/dist/editor/view/docRenderer/inlineFrame.d.ts +4 -0
  63. package/dist/editor/view/docRenderer/lists.d.ts +28 -0
  64. package/dist/editor/view/docRenderer/paragraph.d.ts +2 -0
  65. package/dist/editor/view/docRenderer/properties.d.ts +2 -0
  66. package/dist/editor/view/docRenderer/table.d.ts +15 -0
  67. package/dist/editor/view/docRenderer/units.d.ts +48 -0
  68. package/dist/editor/view/docSerialize/block.d.ts +14 -0
  69. package/dist/editor/view/docSerialize/index.d.ts +8 -0
  70. package/dist/editor/view/docSerialize/inline.d.ts +11 -0
  71. package/dist/editor/view/docSerialize/table.d.ts +12 -0
  72. package/dist/editor/view/imageResize.d.ts +16 -0
  73. package/dist/embed/floatingCorner.d.ts +44 -0
  74. package/dist/embed/viewport.d.ts +133 -0
  75. package/dist/fonts/embedAPI.d.ts +33 -0
  76. package/dist/fonts/emit.d.ts +24 -0
  77. package/dist/fonts/fontFaceRegistry.d.ts +20 -0
  78. package/dist/fonts/fsType.d.ts +36 -0
  79. package/dist/fonts/index.d.ts +19 -0
  80. package/dist/fonts/liveness.d.ts +2 -0
  81. package/dist/fonts/odttf.d.ts +33 -0
  82. package/dist/fonts/parse.d.ts +29 -0
  83. package/dist/fonts/types.d.ts +52 -0
  84. package/dist/headless.d.ts +168 -0
  85. package/dist/history/history.d.ts +100 -0
  86. package/dist/history/index.d.ts +4 -0
  87. package/dist/history/types.d.ts +54 -0
  88. package/dist/index.css +1 -0
  89. package/dist/index.d.ts +52 -0
  90. package/dist/index.js +10561 -0
  91. package/dist/index.js.map +1 -0
  92. package/dist/markdown/parse.d.ts +6 -0
  93. package/dist/pagination/cost.d.ts +32 -0
  94. package/dist/pagination/index.d.ts +2 -0
  95. package/dist/pagination/paginate.d.ts +10 -0
  96. package/dist/pagination/postConditions.d.ts +10 -0
  97. package/dist/pagination/types.d.ts +94 -0
  98. package/dist/paperStack/pageSetup.d.ts +42 -0
  99. package/dist/paperStack/paginationAdapter/buildItems.d.ts +19 -0
  100. package/dist/paperStack/paginationAdapter/distribute.d.ts +23 -0
  101. package/dist/paperStack/paginationAdapter/index.d.ts +18 -0
  102. package/dist/paperStack/paginationAdapter/paragraphLines.d.ts +23 -0
  103. package/dist/paperStack/paginationAdapter/splitList.d.ts +19 -0
  104. package/dist/paperStack/paginationAdapter/splitParagraph.d.ts +21 -0
  105. package/dist/paperStack/paginationAdapter/types.d.ts +30 -0
  106. package/dist/paperStack/paper.d.ts +107 -0
  107. package/dist/paperStack/paperStack.d.ts +245 -0
  108. package/dist/plugin.d.ts +24 -0
  109. package/dist/plugins/marks.d.ts +49 -0
  110. package/dist/plugins/sections.d.ts +15 -0
  111. package/dist/presence/attach.d.ts +48 -0
  112. package/dist/presence/awareness.d.ts +28 -0
  113. package/dist/presence/index.d.ts +19 -0
  114. package/dist/presence/overlay.d.ts +28 -0
  115. package/dist/presence/state.d.ts +36 -0
  116. package/dist/sobree.d.ts +211 -0
  117. package/dist/tokens.css +144 -0
  118. package/dist/util/selection.d.ts +13 -0
  119. package/dist/ydoc/apply.d.ts +68 -0
  120. package/dist/ydoc/index.d.ts +18 -0
  121. package/dist/ydoc/project.d.ts +41 -0
  122. package/dist/ydoc/runs.d.ts +51 -0
  123. package/dist/ydoc/schema.d.ts +123 -0
  124. package/dist/ydoc/seed.d.ts +45 -0
  125. package/dist/ydoc/textDiff.d.ts +59 -0
  126. package/dist/zoneEdit/index.d.ts +22 -0
  127. package/package.json +61 -0
@@ -0,0 +1,1078 @@
1
+ import { BlobCache, BlobStore } from '../blob';
2
+ import { BlockRef, EditError, EditResult, InlinePosition, Range as ApiRange, Selection } from '../doc/api';
3
+ import { EmbedFontFaces, EmbedFontOptions } from '../fonts';
4
+ import { RunPropertiesPatch } from '../doc/runs';
5
+ import { Block, InlineRun, ParagraphAlignment, ParagraphProperties, SobreeDocument } from '../doc/types';
6
+ import { BlockRegistry } from './internal/blockRegistry';
7
+ import { countBlocks } from './internal/positionMap';
8
+ import { EditorTable } from './table';
9
+ import { History } from '../history';
10
+ import * as Y from "yjs";
11
+ export type { BlockRef, EditError, EditResult, InlinePosition, Selection };
12
+ export type ApiRangeType = ApiRange;
13
+ /**
14
+ * One logical tracked change — a maximal run of consecutive inline
15
+ * runs that all carry a `revision` marker by the same author.
16
+ * `getRevisions()` returns these; pass `range` straight to
17
+ * `acceptRevision` / `rejectRevision`.
18
+ *
19
+ * `kinds` is the set of revision types in the span: `["ins"]` or
20
+ * `["del"]` for a plain change, both for a delete-then-insert
21
+ * replacement (which accepts/rejects as a single unit).
22
+ */
23
+ export interface RevisionSpan {
24
+ range: ApiRange;
25
+ author?: string;
26
+ kinds: ("ins" | "del")[];
27
+ /** ISO date of the span's first revision run, if recorded. */
28
+ date?: string;
29
+ /**
30
+ * Discriminator between revision levels:
31
+ * `"inline"` (default for backwards compat) — the span covers
32
+ * `ins`/`del` text runs inside a block. Pass `range` to
33
+ * `acceptRevision` / `rejectRevision`.
34
+ * `"paragraph"` — the span flags the *paragraph mark* itself on
35
+ * `range.from.block`. The range covers offset `[0, length]` of
36
+ * the block so it still selects the right element for UIs, but
37
+ * accept/reject must go through `acceptParagraphRevision` /
38
+ * `rejectParagraphRevision`.
39
+ * `"format"` — the span flags a tracked format change
40
+ * (`<w:rPrChange>`) on contiguous runs by the same author.
41
+ * `kinds` always reports `["ins"]` (the marker is binary: a
42
+ * format change exists or not). Pass `range` to
43
+ * `acceptFormatRevision` / `rejectFormatRevision`.
44
+ */
45
+ level?: "inline" | "paragraph" | "format";
46
+ }
47
+ /**
48
+ * Track-changes mode. When `enabled` is true, the editor reinterprets
49
+ * authoring mutations as tracked revisions rather than direct edits:
50
+ *
51
+ * - `insertRun` stamps `revision: { type: "ins", author }` on the
52
+ * inserted run instead of merging it in plainly.
53
+ * - `deleteRange` stamps `revision: { type: "del", author }` on the
54
+ * plain text runs in range instead of dropping them. A run that's
55
+ * already an `ins` *by the same author* is dropped instead — the
56
+ * author cancelling their own pending insert — and runs already
57
+ * carrying a peer's revision are left untouched (the API user must
58
+ * resolve those via `acceptRevision` / `rejectRevision` first).
59
+ * - All other mutations (`applyRunProperties`, block-level ops, etc.)
60
+ * pass through unchanged in this first cut; format-change tracking
61
+ * (`<w:rPrChange>`) and paragraph-mark tracking will land later.
62
+ *
63
+ * `author` is the human-readable name written into the revision
64
+ * marker. Optional — falls back to no `author` field, mirroring the
65
+ * Word semantics for anonymous-author tracked changes.
66
+ *
67
+ * This is the *authoring* side of the review feature. `getRevisions`
68
+ * / `acceptRevision` / `rejectRevision` are the *consumption* side
69
+ * and work the same regardless of this flag.
70
+ */
71
+ export interface TrackChangesState {
72
+ enabled: boolean;
73
+ author?: string;
74
+ }
75
+ export type { CellRef, InsertAt, InsertColumnOpts, InsertRowOpts, MergeCellsOpts, } from './table';
76
+ /**
77
+ * Payload delivered to `change` subscribers. Plain data — safe to
78
+ * JSON-stringify and ship over a wire. `rawParts` is stripped from
79
+ * `document` so the payload never carries binary Uint8Arrays.
80
+ */
81
+ export interface ChangePayload {
82
+ doc: SobreeDocument;
83
+ /**
84
+ * @deprecated Use `doc` instead. This alias is kept for backwards
85
+ * compatibility within the pre-1.0 line and will be removed before
86
+ * v1. Same reference as `doc`.
87
+ */
88
+ document: SobreeDocument;
89
+ revision: number;
90
+ documentVersion: number;
91
+ }
92
+ /** Summary of a top-level block, for `getBlocks()` and list-style UIs. */
93
+ export interface BlockInfo {
94
+ /** Current position in the body (unstable across edits). */
95
+ index: number;
96
+ /** Stable id — pair with `version` to form a `BlockRef`. */
97
+ id: string;
98
+ /** Bumps on every modification of this block. */
99
+ version: number;
100
+ kind: Block["kind"];
101
+ styleId?: string;
102
+ alignment?: ParagraphAlignment;
103
+ /** Plain-text preview. */
104
+ text: string;
105
+ /** Total character-length of the block's content (see `runsLength`). */
106
+ length: number;
107
+ }
108
+ /** Outline entry — one per heading in document order. */
109
+ export interface OutlineItem {
110
+ level: number;
111
+ text: string;
112
+ blockIndex: number;
113
+ block: BlockRef;
114
+ }
115
+ export type ParagraphPropertiesPatch = {
116
+ [K in keyof ParagraphProperties]?: ParagraphProperties[K] | undefined;
117
+ };
118
+ export type WrapTag = "sup" | "sub" | "strong" | "em" | "u" | "s" | "mark";
119
+ export type EditorEvent = "change" | "selection" | "keydown" | "track-changes-change";
120
+ export type EditorEventPayload = {
121
+ change: ChangePayload;
122
+ selection: SelectionPayload;
123
+ keydown: KeyDownPayload;
124
+ "track-changes-change": TrackChangesState;
125
+ };
126
+ export type Unsubscribe = () => void;
127
+ /**
128
+ * Payload delivered to `selection` subscribers. Fires whenever the live
129
+ * DOM selection changes — typing, clicking, arrow-key navigation, focus
130
+ * loss, programmatic restore. Subscribers should subscribe through the
131
+ * editor rather than `document.addEventListener("selectionchange")`
132
+ * directly so cleanup is centralised and the editor can later add
133
+ * dedup / throttling.
134
+ *
135
+ * `selection` is the model shape (`null` when focus is outside the
136
+ * editor). The convenience fields below mirror what `EditorSelection`
137
+ * exposes for ergonomics — read whichever one you need.
138
+ */
139
+ export interface SelectionPayload {
140
+ selection: Selection;
141
+ range: ApiRange | null;
142
+ caret: InlinePosition | null;
143
+ block: BlockRef | null;
144
+ }
145
+ /**
146
+ * Payload delivered to `keydown` subscribers. Fires for every key press
147
+ * inside the editor host. The editor binds NO shortcuts itself —
148
+ * plugins map keys to API calls via `preventDefault()` (stops the
149
+ * browser's default action) and `stopPropagation()` (stops the chain
150
+ * of remaining subscribers). Subscribers fire in registration order.
151
+ */
152
+ export interface KeyDownPayload {
153
+ /** `KeyboardEvent.key` — `"b"`, `"Enter"`, `"ArrowLeft"`, … (lowercased for letters). */
154
+ key: string;
155
+ /** `KeyboardEvent.code` — `"KeyB"`, `"Enter"`, `"ArrowLeft"`, … (layout-independent). */
156
+ code: string;
157
+ ctrl: boolean;
158
+ shift: boolean;
159
+ alt: boolean;
160
+ meta: boolean;
161
+ /** Stop the browser's default for this key (insertion, navigation, …). */
162
+ preventDefault(): void;
163
+ /** Stop further subscribers from receiving this key. */
164
+ stopPropagation(): void;
165
+ /** Underlying DOM event — for advanced needs (`isComposing`, repeat, …). */
166
+ originalEvent: KeyboardEvent;
167
+ }
168
+ /**
169
+ * A registered command — a named, callable unit of editor work that
170
+ * plugins coordinate around. Same definition gets reached by a
171
+ * keyboard shortcut, a toolbar click, a programmatic call from an
172
+ * agent, or an MCP request.
173
+ *
174
+ * The `run` function is the one place the work happens. `isActive` and
175
+ * `isAvailable` let UI plugins paint toggle / disabled state without
176
+ * understanding what the command actually does.
177
+ */
178
+ export interface CommandDefinition<Args = void> {
179
+ /** Dotted, namespaced — `"mark.toggle.bold"`, `"section.insertBreak"`, … */
180
+ name: string;
181
+ /** Short human label for tooltips / command palettes. */
182
+ title?: string;
183
+ /** Perform the work. Should be idempotent w.r.t. selection (a second
184
+ * invocation on an already-bold selection clears bold, etc.). */
185
+ run: (args: Args) => void;
186
+ /** True when the command represents an active state (mark already on,
187
+ * block is already a heading, …). Drives toolbar `is-active`. */
188
+ isActive?: () => boolean;
189
+ /** False when the command can't run (e.g. selection is wrong shape).
190
+ * Defaults to true. Drives toolbar `disabled`. */
191
+ isAvailable?: () => boolean;
192
+ }
193
+ /** Snapshot of one registered command — what `commands.list()` returns. */
194
+ export interface CommandSnapshot {
195
+ name: string;
196
+ title: string;
197
+ isActive: boolean;
198
+ isAvailable: boolean;
199
+ }
200
+ /**
201
+ * Registry every plugin uses to talk to every other plugin. The
202
+ * editor owns it; plugins register commands on attach and unregister
203
+ * on detach. Keyboard plugins, toolbar plugins, and a future MCP
204
+ * adapter all share the same dispatch path: `editor.commands.execute(name)`.
205
+ */
206
+ export interface CommandBus {
207
+ /** Register a command. Returns an unsubscribe that removes it. */
208
+ register<Args = void>(def: CommandDefinition<Args>): () => void;
209
+ /** Run a registered command. No-op (with a warning) if unknown. */
210
+ execute<Args = void>(name: string, args?: Args): void;
211
+ /** Snapshot every registered command — for command palettes,
212
+ * toolbars rendering toggle states, accessibility audits. */
213
+ list(): CommandSnapshot[];
214
+ /** Whether the named command is currently registered. */
215
+ has(name: string): boolean;
216
+ }
217
+ export interface EditorOptions {
218
+ initialDocument?: SobreeDocument;
219
+ changeDebounceMs?: number;
220
+ /**
221
+ * Elements whose children are editable blocks, in document order. Called
222
+ * fresh each time — the list can grow/shrink (e.g. during pagination).
223
+ */
224
+ contentHosts?: () => HTMLElement[];
225
+ /**
226
+ * Y.Doc backing the document. Optional — if absent, the editor creates
227
+ * one internally. Embedders pass their own when they need to attach
228
+ * a provider (`y-websocket`, `y-indexeddb`, `y-webrtc`, …) for
229
+ * persistence or collaboration.
230
+ *
231
+ * When supplied, the editor checks whether the Y.Doc already has body
232
+ * content. If empty, it seeds from `initialDocument`. If non-empty
233
+ * (Phase 2+: a peer joined an active room), the existing Y.Doc state
234
+ * wins and `initialDocument` is ignored. See `editor.ydoc` for the
235
+ * public escape hatch.
236
+ */
237
+ ydoc?: Y.Doc;
238
+ /**
239
+ * Optional content-hashed `BlobStore` for binary parts (images, fonts).
240
+ *
241
+ * Without one (default): bytes live inline in the Y.Doc's `parts`
242
+ * Y.Map and replicate to every peer through Y updates. Fine for
243
+ * small docs.
244
+ *
245
+ * With one (Phase 3.2+): the editor hashes binary parts, uploads
246
+ * the bytes to the store, and writes only the hash into the Y.Doc's
247
+ * `partRefs` Y.Map. Y updates stay small regardless of image size.
248
+ * The editor maintains a local `BlobCache` that synchronously serves
249
+ * already-fetched bytes to the renderer; `editor.ensurePartsLoaded()`
250
+ * is the async hook for explicit pre-fetching (e.g. before
251
+ * `toDocx()`).
252
+ *
253
+ * See `@sobree/core/blob` for the interface + reference impls
254
+ * (`inMemoryBlobStore`, `fetchBlobStore`).
255
+ */
256
+ blobStore?: BlobStore;
257
+ /**
258
+ * Initial track-changes mode. When omitted, the editor starts in
259
+ * direct-edit mode (`{ enabled: false }`) and embedders can flip it
260
+ * later via `editor.setTrackChanges`. See `TrackChangesState`.
261
+ */
262
+ trackChanges?: TrackChangesState;
263
+ }
264
+ export { runsLength } from '../doc/runs';
265
+ export type { RunPropertiesPatch };
266
+ /**
267
+ * Public editor surface.
268
+ *
269
+ * Two entry points for every operation:
270
+ * - Core methods take `BlockRef` / `InlinePosition` / `Range` and
271
+ * return `EditResult`. These are the wire-callable API — same
272
+ * contract for in-process toolbars, headless Y peers (HeadlessSobree),
273
+ * and future MCP wrappers.
274
+ * - "AtSelection" sugar reads the live DOM selection, builds the
275
+ * position/range for you, and delegates to the core. Use these in
276
+ * in-process UI code.
277
+ *
278
+ * Mutations enforce optimistic locking via block `version` numbers.
279
+ * Conflicts return `{ ok: false, error: { code: "optimistic-lock", … } }`
280
+ * rather than throwing.
281
+ */
282
+ export declare class Editor {
283
+ readonly host: HTMLElement;
284
+ readonly selection: EditorSelection;
285
+ /**
286
+ * Ergonomic table operations — row/column insert-delete, cell merge /
287
+ * unmerge, cell-level properties. Every method returns an `EditResult`
288
+ * and inherits optimistic-lock checking via `replaceBlock`.
289
+ */
290
+ readonly table: EditorTable;
291
+ /**
292
+ * Named-command registry — the coordination point between plugins.
293
+ * Plugins register commands on attach and unregister on detach;
294
+ * keyboard / toolbar / agent / MCP all dispatch through `execute()`.
295
+ */
296
+ readonly commands: CommandBus;
297
+ /**
298
+ * Y.Doc backing the document. The Editor's `this.doc` field is a
299
+ * cached projection of this Y.Doc — every local mutation mirrors
300
+ * into the Y.Doc inside a transact (`origin: "local"`). Embedders
301
+ * read this for escape-hatch wiring (providers, dev tools, custom
302
+ * persistence).
303
+ *
304
+ * Phase 1a: Y.Doc is a faithful mirror of `this.doc` but the Editor
305
+ * still treats `this.doc` as the in-memory truth. Phase 1b inverts
306
+ * this — Y.Doc becomes the truth and `this.doc` becomes a pure
307
+ * cache invalidated by Y observers (which is what enables Phase 2
308
+ * providers to drive the editor from outside).
309
+ */
310
+ readonly ydoc: Y.Doc;
311
+ /**
312
+ * Optional content-hashed blob layer (Phase 3.2+). When set, binary
313
+ * parts (images, fonts) go through this rather than living inline
314
+ * in the Y.Doc. `null` means "use the inline parts path" — today's
315
+ * default. See `EditorOptions.blobStore`.
316
+ */
317
+ readonly blobStore: BlobStore | null;
318
+ /**
319
+ * Local cache for blob bytes — synchronously fetchable for the
320
+ * renderer, populated by `blobStore.put` (local writes) or
321
+ * `blobStore.get` (background fetches). `null` when no blobStore
322
+ * is configured.
323
+ */
324
+ readonly blobCache: BlobCache | null;
325
+ /** Most recent projection's partRefs map (path → hash). Used by
326
+ * `ensurePartsLoaded` and by the `onResolved` callback to know
327
+ * which paths reference a freshly-arrived blob. */
328
+ private lastPartRefs;
329
+ /**
330
+ * Part paths whose bytes are in flight to the BlobStore — the
331
+ * editor wrote them synchronously to `doc.rawParts` (so the local
332
+ * renderer has them), but the async hash + upload + partRef write
333
+ * is still pending. Mirror skips these so they don't end up inline
334
+ * in the Y.Doc.
335
+ */
336
+ private readonly pendingPartRefMigrations;
337
+ /** Listener that re-projects + re-renders on remote Y.Doc updates.
338
+ * Removed on `destroy()`. */
339
+ private ydocUpdateListener;
340
+ private doc;
341
+ private readonly registry;
342
+ private readonly debounceMs;
343
+ private readonly getContentHosts;
344
+ private debounceHandle;
345
+ private detachImageResize;
346
+ private inputListener;
347
+ private beforeInputListener;
348
+ private pasteListener;
349
+ private dragOverListener;
350
+ private dropListener;
351
+ private revision;
352
+ private readonly listeners;
353
+ /**
354
+ * Authoring mode for revisions. Off by default — mutations apply
355
+ * plainly. On, `insertRun` / `deleteRange` route through
356
+ * `applyTrackChangesToInsert` / `applyTrackChangesToDelete`. See the
357
+ * `TrackChangesState` type docblock for the full semantics.
358
+ */
359
+ private trackChanges;
360
+ /**
361
+ * One-shot warning set for tracked-mode beforeinput types we don't
362
+ * (yet) route through the API — paragraph splits, paste, IME, etc.
363
+ * We let the browser handle them untracked rather than swallowing
364
+ * the keystroke, but log the inputType the first time we see it so
365
+ * the gap is visible during development.
366
+ */
367
+ private trackedInputWarned;
368
+ /**
369
+ * Active IME composition state (`compositionstart` → `compositionend`).
370
+ * `null` outside composition or in non-tracked mode.
371
+ *
372
+ * Snapshot-then-restore is the only practical way to track IME-typed
373
+ * text: we can't intercept `beforeinput` during composition without
374
+ * breaking input methods on most platforms, so we let the browser
375
+ * mutate the DOM natively during composition, then on `compositionend`
376
+ * we roll back to the pre-composition AST and re-insert the final
377
+ * composed string (`event.data`) through `insertRun` — which stamps
378
+ * the `revision: ins` marker per `TrackChangesState`.
379
+ */
380
+ private composition;
381
+ private compositionStartListener;
382
+ private compositionEndListener;
383
+ /** Document-level `selectionchange` listener. Funnels every cursor
384
+ * movement (typing, click, arrows, programmatic restore) into the
385
+ * `selection` event so plugins don't each register their own. */
386
+ private selectionChangeListener;
387
+ /** Host-level `keydown` listener. Funnels every key press into the
388
+ * `keydown` event for plugins (mark shortcuts, navigation, etc.). */
389
+ private keydownListener;
390
+ /** Cached last-seen per-block JSON strings, for diff-based version bumps. */
391
+ private lastSerialisedBlocks;
392
+ /** Tracks `@font-face` registrations for the document's embedded fonts. */
393
+ private readonly fontFaces;
394
+ /**
395
+ * Undo / redo orchestrator. Snapshots the document at every commit
396
+ * + at the start of each typing session (coalesced). Memory-efficient:
397
+ * snapshots store doc references, not deep copies — the immutable-block
398
+ * model means consecutive snapshots share most blocks. Public API is
399
+ * `editor.history.undo() / redo() / clear() / depth() / on(...)`.
400
+ */
401
+ readonly history: History;
402
+ /**
403
+ * True when DOM mutations since the last sync were user-driven (typing,
404
+ * paste, drag-drop image). False right after we render from AST — the
405
+ * DOM is then a projection of `this.doc`, and reading it back can't
406
+ * tell us anything the AST doesn't already know, while losing any
407
+ * fidelity the serializer drops (column widths, vAlign, …). `getDocument`
408
+ * and `emitChangeNow` sync only when this flag is set.
409
+ */
410
+ private domDirty;
411
+ constructor(host: HTMLElement, options?: EditorOptions);
412
+ /**
413
+ * Re-project the Y.Doc into `this.doc`, sync the BlockRegistry to
414
+ * the projected ids, re-render the DOM, and fire `change`. Called
415
+ * when a remote provider applies an update we didn't initiate.
416
+ */
417
+ private adoptYDocState;
418
+ /**
419
+ * Resolve any `partRefs` hashes that are currently cached into the
420
+ * provided document's `rawParts`. In-place mutation — the document
421
+ * is owned by the caller. Missing hashes stay out of `rawParts`;
422
+ * the renderer handles missing parts gracefully (placeholder).
423
+ */
424
+ private resolveCachedPartRefsInto;
425
+ /**
426
+ * Callback fired by the BlobCache when a background fetch lands.
427
+ * Walks `lastPartRefs` to find which paths reference this hash,
428
+ * patches `this.doc.rawParts`, and re-renders so the user sees
429
+ * the part appear.
430
+ */
431
+ private onBlobResolved;
432
+ /**
433
+ * Wait for every currently-referenced binary part to be available
434
+ * in the local cache. Useful before `toDocx()` so the exported
435
+ * file contains all images / fonts.
436
+ *
437
+ * Returns a resolved Promise immediately when no `blobStore` is
438
+ * configured (today's default — bytes are always inline).
439
+ */
440
+ ensurePartsLoaded(): Promise<void>;
441
+ /**
442
+ * Current document as SobreeDocument.
443
+ *
444
+ * Syncs from the DOM only if the user typed since the last render. If
445
+ * the latest change came from an API call, returns the in-memory AST
446
+ * verbatim — the DOM is a projection of it and reading back would
447
+ * throw away properties the renderer doesn't surface.
448
+ */
449
+ getDocument(): SobreeDocument;
450
+ /** Replace the document. Fires `change` synchronously. */
451
+ setDocument(doc: SobreeDocument): void;
452
+ /**
453
+ * Internal apply path shared by `setDocument` and any other
454
+ * full-replace caller. The Y.Doc mirror produces tracked Y
455
+ * operations that Y.UndoManager turns into a single stack item.
456
+ */
457
+ private applyDocument;
458
+ /**
459
+ * Drop entries from `rawParts` that nothing in the AST references.
460
+ * Useful after deleting images (or font embeds — Phase 3) to keep the
461
+ * in-memory doc lean. Idempotent; reports the keys that were removed.
462
+ *
463
+ * Not auto-invoked — `exportDocx` already filters at packaging time,
464
+ * so callers only need this when they're keeping the doc around
465
+ * in-memory across many edits.
466
+ */
467
+ pruneUnusedParts(): {
468
+ kept: number;
469
+ pruned: string[];
470
+ };
471
+ /**
472
+ * Embed a TTF/OTF font into the document. Thin wrapper around
473
+ * `embedFontIntoDoc()` from the fonts module — handles the
474
+ * setDocument round so the renderer + `@font-face` registry pick
475
+ * up the new face automatically.
476
+ *
477
+ * Refuses (with a warning) when the font's OS/2 `fsType` field
478
+ * marks it as restricted, unless `opts.allowRestricted` is true.
479
+ * Pass any subset of {regular, bold, italic, boldItalic}; missing
480
+ * faces are simply not embedded.
481
+ */
482
+ embedFont(name: string, faces: EmbedFontFaces, opts?: EmbedFontOptions): {
483
+ warnings: string[];
484
+ };
485
+ /**
486
+ * Drop a font declaration by name. The associated font parts in
487
+ * `rawParts` aren't immediately removed — call `pruneUnusedParts()`
488
+ * (or just export) to GC them.
489
+ */
490
+ removeEmbeddedFont(name: string): void;
491
+ /** Monotonic counter bumped on each `change` event. */
492
+ getRevision(): number;
493
+ /** Monotonic document-wide version (bumps on any mutation). */
494
+ getDocumentVersion(): number;
495
+ /** Render current document to an HTML string. */
496
+ toHtml(): string;
497
+ getBlocks(): BlockInfo[];
498
+ getBlock(index: number): BlockInfo;
499
+ /** Same summary, looked up by stable id. Returns `null` if unknown. */
500
+ getBlockById(id: string): BlockInfo | null;
501
+ getOutline(): OutlineItem[];
502
+ /** Replace the block at `target`'s index with `block`. */
503
+ replaceBlock(target: BlockRef, block: Block): EditResult<BlockRef>;
504
+ /**
505
+ * Insert `block` before the target block. Returns the new ref.
506
+ *
507
+ * In track-changes mode (see `setTrackChanges`), if `block` is a
508
+ * paragraph it gets stamped with `revision: { type: "ins", author }`
509
+ * on its properties — the same paragraph-mark semantics as
510
+ * `splitBlock`. Non-paragraph blocks (table, section_break) don't
511
+ * carry the marker in v1 and insert plain.
512
+ */
513
+ insertBlockBefore(target: BlockRef, block: Block): EditResult<BlockRef>;
514
+ /**
515
+ * Insert `block` after the target block. Returns the new ref.
516
+ * Tracked-mode behaviour matches `insertBlockBefore`.
517
+ */
518
+ insertBlockAfter(target: BlockRef, block: Block): EditResult<BlockRef>;
519
+ /**
520
+ * Delete the target block.
521
+ *
522
+ * In track-changes mode, paragraph blocks aren't removed — their
523
+ * `properties.revision` is stamped `del` (the renderer shows the
524
+ * paragraph mark with a strikethrough ¶ glyph; the body text stays
525
+ * visible). If the paragraph carries the *current author's* pending
526
+ * `ins` marker (a paragraph the user themselves just created), the
527
+ * block is removed outright — cancelling an un-committed insert,
528
+ * matching the inline `deleteRange` semantics. Non-paragraph blocks
529
+ * (tables, section breaks) bypass tracking in v1 — they remove plainly.
530
+ */
531
+ deleteBlock(target: BlockRef): EditResult<void>;
532
+ /**
533
+ * Stamp `revision: ins` on a paragraph block if tracked mode is on
534
+ * and the block doesn't already carry one. Helper for
535
+ * `insertBlockBefore` / `insertBlockAfter`. Non-paragraph blocks
536
+ * pass through unchanged.
537
+ */
538
+ private stampInsertedBlockIfTracked;
539
+ /** Merge a patch into each target paragraph's properties. */
540
+ applyBlockProperties(targets: BlockRef[], patch: ParagraphPropertiesPatch): EditResult<void>;
541
+ /**
542
+ * Apply run-level properties across `range`.
543
+ *
544
+ * In track-changes mode (see `setTrackChanges`), each text run in
545
+ * `range` that doesn't already carry a `revisionFormat` snapshot
546
+ * gets one — capturing the run's properties *before* the patch is
547
+ * applied. Repeated tracked edits don't overwrite the snapshot, so
548
+ * a reject always returns the run to its pre-tracking state.
549
+ * Non-text runs and runs with no concrete properties pass through
550
+ * unchanged.
551
+ */
552
+ applyRunProperties(range: ApiRange, patch: RunPropertiesPatch, opts?: {
553
+ expect?: Record<string, number>;
554
+ }): EditResult<void>;
555
+ /** Wrap the runs in `range` with semantic formatting. */
556
+ wrapRange(range: ApiRange, tag: WrapTag, opts?: {
557
+ expect?: Record<string, number>;
558
+ }): EditResult<void>;
559
+ /**
560
+ * Insert a run at `at`. Splits the run list at the offset.
561
+ *
562
+ * In track-changes mode (see `setTrackChanges`), the run is stamped
563
+ * with `revision: { type: "ins", author }` before insertion (unless
564
+ * it already carries a `revision` — caller-provided revisions are
565
+ * never overwritten).
566
+ */
567
+ insertRun(at: InlinePosition, run: InlineRun): EditResult<BlockRef>;
568
+ /**
569
+ * Split a paragraph at `at`. The runs before the offset stay on the
570
+ * original block; the runs after move into a fresh paragraph that's
571
+ * inserted immediately after. The new block inherits the original
572
+ * paragraph's properties (alignment, style, indent, …) so the visual
573
+ * shape of the split is what the user expects from pressing Enter.
574
+ *
575
+ * In track-changes mode (see `setTrackChanges`), the new paragraph's
576
+ * `properties.revision` is stamped `{ type: "ins", author }` — the
577
+ * "this paragraph break is a tracked insert" marker. The original
578
+ * paragraph is left clean; only the *new* paragraph carries the mark,
579
+ * mirroring how Word stores `<w:rPr><w:ins/></w:rPr>` inside `<w:pPr>`.
580
+ *
581
+ * `at.offset` is clamped to `[0, block-length]`. A split at offset 0
582
+ * inserts an empty paragraph *before* the cursor; a split at the
583
+ * block's full length inserts an empty paragraph *after*.
584
+ *
585
+ * Returns the ref of the *new* (second) block — callers typically
586
+ * place the caret at its offset 0.
587
+ */
588
+ splitBlock(at: InlinePosition): EditResult<BlockRef>;
589
+ /**
590
+ * Insert an image at `at`. The bytes are stored in `doc.rawParts` under
591
+ * a fresh `word/media/imageN.{ext}` path; a `DrawingRun` referencing
592
+ * that path is inserted at the position.
593
+ *
594
+ * When a `blobStore` is configured (Phase 3.2+), the bytes also
595
+ * migrate in the background: hashed, uploaded to the store, and a
596
+ * `partRefs[partPath] = hash` entry written to the Y.Doc. Once the
597
+ * migration lands, the Y.Doc's inline `parts[partPath]` is cleared —
598
+ * so the bytes ride the side-channel, not the Y update stream.
599
+ * The local renderer keeps reading `doc.rawParts[partPath]`
600
+ * throughout (the value is stable from the moment `insertImage`
601
+ * returns).
602
+ */
603
+ insertImage(at: InlinePosition, bytes: Uint8Array, opts: {
604
+ mime: string;
605
+ widthPx?: number;
606
+ heightPx?: number;
607
+ altText?: string;
608
+ }): EditResult<BlockRef>;
609
+ /**
610
+ * Delete the content inside `range`. Supports both single-block and
611
+ * cross-paragraph ranges.
612
+ *
613
+ * In track-changes mode (see `setTrackChanges`), the deletion is
614
+ * *recorded* rather than applied:
615
+ * - plain runs are stamped with `revision: { type: "del", author }`;
616
+ * - a run already marked as the same author's pending `ins` is
617
+ * dropped (the author cancelling their own un-committed insert
618
+ * — no audit trail because it was never committed);
619
+ * - runs carrying a peer's revision (any author other than the
620
+ * current one) are left untouched: the API user should resolve
621
+ * those via `acceptRevision` / `rejectRevision` first.
622
+ *
623
+ * **Cross-paragraph behaviour**: in tracked mode every paragraph
624
+ * *after* the first one in the range gets its paragraph-mark
625
+ * stamped `del` too, so `acceptAllRevisions` later collapses the
626
+ * range into a single paragraph. In non-tracked mode the
627
+ * intermediate paragraphs are removed outright and the first +
628
+ * last blocks merge into one.
629
+ */
630
+ deleteRange(range: ApiRange, opts?: {
631
+ expect?: Record<string, number>;
632
+ }): EditResult<void>;
633
+ /**
634
+ * Tracked cross-paragraph delete. Walks each paragraph in the range:
635
+ * stamps `del` on the affected runs (first-block tail / intermediate
636
+ * full / last-block head), and on every paragraph *after the first*
637
+ * stamps the paragraph-mark `del` so `acceptAllRevisions` later
638
+ * merges them all into the first block. Single commit.
639
+ */
640
+ private deleteRangeAcrossBlocksTracked;
641
+ /**
642
+ * Non-tracked cross-paragraph delete. Keeps the head of the first
643
+ * block + the tail of the last block, splices them into the first
644
+ * block as one paragraph, and removes everything in between.
645
+ */
646
+ private deleteRangeAcrossBlocksPlain;
647
+ /**
648
+ * Read the current track-changes mode. Defaults to `{ enabled: false }`
649
+ * — the editor mutates the document plainly. See `setTrackChanges`.
650
+ *
651
+ * Returns a fresh copy each call, so callers can mutate the returned
652
+ * object without affecting editor state.
653
+ */
654
+ getTrackChanges(): TrackChangesState;
655
+ /**
656
+ * Switch authoring mode. When `enabled`, subsequent `insertRun` and
657
+ * `deleteRange` calls produce tracked revisions instead of direct
658
+ * mutations (see `TrackChangesState`'s docblock for the full rules).
659
+ *
660
+ * Fires `track-changes-change` if the state actually changes
661
+ * (idempotent — re-setting the same enabled+author is a no-op).
662
+ * Listeners receive the new state; embedders typically forward it
663
+ * to a toolbar pill / mode badge.
664
+ */
665
+ setTrackChanges(state: TrackChangesState): void;
666
+ /**
667
+ * Route a tracked-mode `beforeinput` event through the typed API so
668
+ * the resulting runs carry revision markers. Returns `true` if the
669
+ * event was consumed (caller should `preventDefault`), `false` to
670
+ * let the browser handle it natively (untracked).
671
+ *
672
+ * **Handled inputTypes** (the 95% case — typing and deleting text):
673
+ *
674
+ * - `insertText`, `insertReplacementText` — typed characters,
675
+ * dictation / autocomplete replacements.
676
+ * - `deleteContentBackward`, `deleteContentForward` — Backspace
677
+ * and Delete keys, including over a selection.
678
+ * - `deleteWordBackward`, `deleteWordForward` — Option-Backspace
679
+ * style word deletions; the browser collapses to the right
680
+ * selection range before firing, we just delete it.
681
+ * - `deleteByCut` — Cmd-X. The clipboard side has already been
682
+ * populated by the browser; we mark the source range deleted.
683
+ *
684
+ * **Unhandled** (return `false`, fall through, warn-once):
685
+ *
686
+ * - `insertParagraph` / `insertLineBreak` — block split / soft
687
+ * break. Word tracks these as `<w:ins>` on the paragraph mark;
688
+ * the corresponding AST mutation (split-block as a tracked op)
689
+ * hasn't landed yet.
690
+ * - `insertFromPaste` — paste is handled at the `paste` event level
691
+ * in `onPaste` (not here), where tracked-mode plain-text paste is
692
+ * already routed through `insertRun` / `splitBlock`. Rich-paste
693
+ * (HTML) in tracked mode falls back to plain text by design.
694
+ * - `insertCompositionText` — handled separately via
695
+ * `compositionstart` / `compositionend` listeners
696
+ * (`handleCompositionStart` / `handleCompositionEnd`). We let the
697
+ * IME render natively during composition, then on end we restore
698
+ * the pre-composition AST and re-insert the final composed string
699
+ * through this `insertRun` path.
700
+ *
701
+ * Falling through means the keystroke still works (no broken UX in
702
+ * tracked mode), it just doesn't get a revision marker. The console
703
+ * warning makes the gap visible.
704
+ *
705
+ * Caret restoration: after every routed mutation we set the model
706
+ * selection to where the caret would have ended up on the equivalent
707
+ * direct-edit operation. Because tracked-mode `deleteRange` leaves
708
+ * the runs in place (marked `del`), offsets don't shift — caret math
709
+ * is straightforward.
710
+ */
711
+ private handleTrackedInput;
712
+ /**
713
+ * Snapshot the pre-composition AST + caret so `handleCompositionEnd`
714
+ * can roll back the browser's native IME mutations and re-insert the
715
+ * final composed string through the tracked-mode `insertRun`. No-op
716
+ * when tracked mode is off — IME falls through to the browser as
717
+ * always (untracked, but functional).
718
+ */
719
+ private handleCompositionStart;
720
+ /**
721
+ * Commit a tracked IME composition. Restores the AST to its
722
+ * pre-composition snapshot, re-renders, then inserts `event.data`
723
+ * through `insertRun` at the captured caret — so the final composed
724
+ * string lands as a tracked `ins` instead of as plain text from the
725
+ * browser's native IME commit.
726
+ *
727
+ * Bails out (and clears state) if tracked mode was toggled off
728
+ * mid-composition or the snapshot is missing; the browser's native
729
+ * commit then stands as-is (untracked, but functional).
730
+ */
731
+ private handleCompositionEnd;
732
+ /**
733
+ * Resolve the position to insert at when the user types over the
734
+ * current selection. For a caret, that's the caret itself; for a
735
+ * range, we delete the range first (which in tracked mode marks the
736
+ * runs `del` but keeps them in place — so the `from` offset is still
737
+ * the right insertion point afterwards). Returns `null` if the
738
+ * selection spans blocks or the delete failed.
739
+ */
740
+ private markedRangeForReplace;
741
+ /**
742
+ * Range a Backspace-style key should delete. Range-selection wins
743
+ * if there is one (just delete the selection). Otherwise step one
744
+ * character left of the caret — at offset 0 we have nothing to do
745
+ * (and Word does nothing in that position too, in v1; cross-block
746
+ * backspace is a follow-up).
747
+ */
748
+ private rangeForBackwardDelete;
749
+ /** Forward-delete equivalent of `rangeForBackwardDelete`. */
750
+ private rangeForForwardDelete;
751
+ /** Re-lookup the block by id to get a fresh `BlockRef` (current version). */
752
+ private refreshedPosition;
753
+ /** Place the caret at `(blockId, offset)` using a fresh block ref. */
754
+ private placeCaret;
755
+ /**
756
+ * True when the current DOM selection's caret sits inside an `<ins>`,
757
+ * `<del>`, or `<span.sobree-revision-format>` wrapper — the markup
758
+ * the renderer emits for tracked revisions. Used by `beforeinput` in
759
+ * mode-off to detect when we have to take over the insert path: if
760
+ * we don't, the browser's contenteditable inserts the new character
761
+ * INSIDE the wrapper and the post-input DOM-sync stamps it with the
762
+ * wrapper's revision marker (an edit the user explicitly opted out
763
+ * of tracking).
764
+ *
765
+ * Returns `false` for a normal caret in plain text, in a `<strong>`
766
+ * / `<em>` / etc. — those wrappers *should* inherit (formatting),
767
+ * unlike revision wrappers which encode "edit history."
768
+ */
769
+ private caretInsideRevisionWrapper;
770
+ /**
771
+ * Accept the tracked changes inside `range`: insertions become
772
+ * permanent (the revision marker is stripped, text kept), deletions
773
+ * are applied (the deleted text is dropped). Runs in `range` with no
774
+ * revision marker pass through untouched, so it's safe to pass a
775
+ * range slightly wider than the change.
776
+ */
777
+ acceptRevision(range: ApiRange, opts?: {
778
+ expect?: Record<string, number>;
779
+ }): EditResult<void>;
780
+ /**
781
+ * Reject the tracked changes inside `range`: insertions are removed
782
+ * (the inserted text is dropped), deletions are restored (the
783
+ * revision marker is stripped, deleted text kept). The inverse of
784
+ * `acceptRevision`.
785
+ */
786
+ rejectRevision(range: ApiRange, opts?: {
787
+ expect?: Record<string, number>;
788
+ }): EditResult<void>;
789
+ /**
790
+ * Accept tracked format changes inside `range`: each text run with a
791
+ * `revisionFormat` snapshot drops the snapshot; the current
792
+ * `properties` stay. Runs without one pass through.
793
+ */
794
+ acceptFormatRevision(range: ApiRange, opts?: {
795
+ expect?: Record<string, number>;
796
+ }): EditResult<void>;
797
+ /**
798
+ * Reject tracked format changes inside `range`: each run reverts its
799
+ * `properties` to `revisionFormat.before`. Inverse of
800
+ * `acceptFormatRevision`.
801
+ */
802
+ rejectFormatRevision(range: ApiRange, opts?: {
803
+ expect?: Record<string, number>;
804
+ }): EditResult<void>;
805
+ /**
806
+ * Accept the paragraph-mark revision on `target`.
807
+ *
808
+ * Per the semantics in `ParagraphProperties.revision`'s docblock:
809
+ * - `ins` → strip the marker; the paragraph break stays permanent.
810
+ * - `del` → merge this paragraph's content into the *previous*
811
+ * paragraph; the paragraph break is consumed.
812
+ *
813
+ * Returns `range-empty`-coded failure if the block has no paragraph
814
+ * revision, and `invalid-state` if a `del` accept would require
815
+ * merging with a non-paragraph (table, section break) — those merges
816
+ * aren't well-defined yet.
817
+ */
818
+ acceptParagraphRevision(target: BlockRef): EditResult<void>;
819
+ /**
820
+ * Reject the paragraph-mark revision on `target`.
821
+ * - `ins` → merge this paragraph into the *previous* one (the split
822
+ * introduced by the tracked Enter is undone).
823
+ * - `del` → strip the marker; the paragraph break stays.
824
+ */
825
+ rejectParagraphRevision(target: BlockRef): EditResult<void>;
826
+ /**
827
+ * Concatenate `body[index]`'s runs onto the end of `body[index-1]`
828
+ * and remove `body[index]`. The previous block must be a paragraph;
829
+ * otherwise we bail with `invalid-state`. Used by accept/reject of
830
+ * paragraph-mark revisions where the decision means "this paragraph
831
+ * break should not exist".
832
+ */
833
+ private mergeWithPrevious;
834
+ /**
835
+ * Strip the `revision` marker from `body[index]`'s paragraph
836
+ * properties, leaving the block (and its content) in place.
837
+ * Fallback for merge-impossible cases — see `mergeWithPrevious`'s
838
+ * `index <= 0` branch and `applyAllRevisions`' second pass.
839
+ */
840
+ private stripParagraphMarker;
841
+ /**
842
+ * Flag `body[index]`'s paragraph break as a tracked deletion —
843
+ * stamp `properties.revision = { type: "del", author }`. Used by
844
+ * `handleTrackedInput` for the Backspace-at-start-of-paragraph
845
+ * keystroke.
846
+ *
847
+ * Two short-circuits:
848
+ * - If the paragraph already carries the *same author's* pending
849
+ * `ins` (the user is backspacing into a split they themselves
850
+ * just made), drop the marker and merge into the previous
851
+ * paragraph — cancelling an un-committed insert rather than
852
+ * layering del on top of ins.
853
+ * - If the paragraph carries some OTHER revision (peer's ins, an
854
+ * existing del), leave it alone with a no-op success. The
855
+ * reviewer should resolve those via accept/reject first.
856
+ */
857
+ private markParagraphBreakForDelete;
858
+ /**
859
+ * Enumerate every logical tracked change in the document.
860
+ *
861
+ * Consecutive revision-bearing runs by the same author coalesce into
862
+ * one `RevisionSpan` — so a delete-then-insert replacement is one
863
+ * span, and two unrelated insertions in a paragraph (separated by
864
+ * plain text) are two. Each span's `range` carries fresh, versioned
865
+ * `BlockRef`s, ready to hand to `acceptRevision` / `rejectRevision`.
866
+ *
867
+ * Call after each `change` — the ranges are positional, so re-query
868
+ * rather than caching across edits.
869
+ */
870
+ getRevisions(): RevisionSpan[];
871
+ /**
872
+ * Walk one paragraph and append its revision spans to `out`. Used by
873
+ * `getRevisions` for both top-level paragraphs (where `ref` is the
874
+ * paragraph's own BlockRef) and for paragraphs inside table cells
875
+ * (where `ref` is the *containing table's* BlockRef as a sentinel —
876
+ * cell paragraphs don't have their own registry entry yet).
877
+ *
878
+ * Emits the same three-level span shape as the inline walker:
879
+ * paragraph-mark first, then coalesced inline ins/del spans, then
880
+ * coalesced format-change spans.
881
+ */
882
+ private collectParagraphRevisions;
883
+ /**
884
+ * Accept every tracked change in the document. With `opts.author`,
885
+ * accept only that author's changes. One commit for the whole sweep.
886
+ */
887
+ acceptAllRevisions(opts?: {
888
+ author?: string;
889
+ }): EditResult<void>;
890
+ /** Reject every tracked change (optionally filtered by author). */
891
+ rejectAllRevisions(opts?: {
892
+ author?: string;
893
+ }): EditResult<void>;
894
+ private applyAllRevisions;
895
+ /**
896
+ * Walk a table's cell paragraphs and apply the accept/reject
897
+ * decision to inline + format + paragraph-mark revisions inside.
898
+ * Returns `{ next, changed }` so the caller knows whether to bump
899
+ * the table block. Paragraph-mark del within a cell falls back to
900
+ * strip-the-marker rather than attempting a cross-cell-paragraph
901
+ * merge — that structural edit is out of v1 scope for tables.
902
+ */
903
+ private sweepTableCellRevisions;
904
+ /** Mark comment `id` resolved (`Comment.done = true`). */
905
+ resolveComment(id: number): EditResult<void>;
906
+ /** Re-open a resolved comment `id` (`Comment.done = false`). */
907
+ reopenComment(id: number): EditResult<void>;
908
+ private setCommentDone;
909
+ setBlockPropertiesAtSelection(patch: ParagraphPropertiesPatch): EditResult<void>;
910
+ setRunPropertiesAtSelection(patch: RunPropertiesPatch): EditResult<void>;
911
+ wrapSelection(tag: WrapTag): EditResult<void>;
912
+ insertImageAtSelection(bytes: Uint8Array, opts: {
913
+ mime: string;
914
+ widthPx?: number;
915
+ heightPx?: number;
916
+ altText?: string;
917
+ }): EditResult<BlockRef>;
918
+ /**
919
+ * Unwrap span ancestors intersecting the selection, up to the block.
920
+ * Best-effort DOM-level cleanup — preserves the current in-place UX
921
+ * without re-rendering.
922
+ */
923
+ clearInlineFormattingAtSelection(): void;
924
+ on<E extends EditorEvent>(event: E, cb: (p: EditorEventPayload[E]) => void): Unsubscribe;
925
+ destroy(): void;
926
+ /** @internal */
927
+ _hosts(): HTMLElement[];
928
+ /** @internal */
929
+ _registry(): BlockRegistry;
930
+ /** @internal */
931
+ _blockElementAt(index: number): HTMLElement | null;
932
+ /**
933
+ * Parallel array of live block ids (same length as `doc.body`), used
934
+ * by the renderer to stamp `data-block-id` onto every block element.
935
+ * Lets external tools (block tools, embedders) locate a block's DOM
936
+ * element after the body is re-rendered from scratch.
937
+ */
938
+ private blockIdsArray;
939
+ private checkRefs;
940
+ private checkRange;
941
+ /**
942
+ * Apply a runs transform to the runs covered by `range`. Returns
943
+ * `EditResult<void>`. Handles both single- and multi-block ranges.
944
+ * Assumes locks have already been checked.
945
+ */
946
+ private mutateRunsInRange;
947
+ /**
948
+ * Apply a mutation to `this.doc`, update the registry, re-render, fire
949
+ * change. Returns the affected refs (post-bump).
950
+ */
951
+ private commit;
952
+ /**
953
+ * Ensure `this.doc` reflects the latest edits. If the DOM has been
954
+ * dirtied by user typing / paste / drop, pull the latest content out
955
+ * of it and bump affected block versions. If the last mutation came
956
+ * from the API, the AST is already current — skip the (lossy)
957
+ * DOM-to-AST round-trip.
958
+ */
959
+ private ensureCurrent;
960
+ private syncFromDom;
961
+ /**
962
+ * Schedule a DOM-driven change emit. Called from the `input` listener
963
+ * when the user types — the DOM is the source of truth and we sync the
964
+ * AST from it before notifying listeners.
965
+ */
966
+ private scheduleChange;
967
+ /**
968
+ * Emit a `change` event using the current in-memory AST verbatim. Do
969
+ * NOT sync from DOM — callers that need a DOM sync should call it
970
+ * explicitly (user-typing path does). API mutations have already
971
+ * rendered their AST into the DOM and must not let the lossy DOM-read
972
+ * overwrite properties the renderer doesn't surface
973
+ * (column widths, verticalAlign, table properties, …).
974
+ */
975
+ private emitChangeNow;
976
+ /**
977
+ * Snapshot of the live block ids in body order — used both as the
978
+ * input to `applyDocumentToYDoc` (so each Y.Map carries its stable
979
+ * id) and as the `blockIdsArray()` the renderer uses to set the
980
+ * `data-block-id` attribute.
981
+ */
982
+ private allBlockIds;
983
+ /**
984
+ * Mirror the current `this.doc` into the Y.Doc as a single
985
+ * transaction. The diff is performed by `applyDocumentToYDoc`,
986
+ * which matches blocks by id so concurrent edits to *different*
987
+ * blocks merge cleanly via the Y.Array CRDT.
988
+ *
989
+ * Origin is `"local"` so a future Y observer can distinguish locally-
990
+ * driven mutations (already rendered) from remote ones (need re-render).
991
+ */
992
+ private mirrorToYDoc;
993
+ /**
994
+ * Returns the set of part paths that mirror should NOT write
995
+ * inline — they're (or will soon be) tracked via the partRefs
996
+ * Y.Map instead. Returns `undefined` when there's nothing to skip
997
+ * (the common no-BlobStore case) so the mirror takes its
998
+ * fastest path.
999
+ */
1000
+ private computePartPathSkipSet;
1001
+ /**
1002
+ * Background migrate inline part bytes into the BlobStore. Called
1003
+ * by mutators (`insertImage`, `embedFont`) when a `BlobStore` is
1004
+ * configured. The local `doc.rawParts` keeps its inline copy so
1005
+ * the renderer stays synchronous; the Y.Doc gets a `partRefs`
1006
+ * entry referencing the BlobStore content hash, and any stale
1007
+ * `parts` entry is deleted.
1008
+ *
1009
+ * Robust against errors: an upload failure logs and leaves the
1010
+ * path in the pending set so a future call can retry. The local
1011
+ * renderer is unaffected (bytes are still in `doc.rawParts`).
1012
+ */
1013
+ private migratePartToBlobStore;
1014
+ /**
1015
+ * Compose the current selection into a {@link SelectionPayload} and
1016
+ * dispatch to subscribers. Called from the document-level
1017
+ * `selectionchange` listener attached in the constructor; safe to fire
1018
+ * even when no subscribers exist (the early-return keeps it cheap).
1019
+ */
1020
+ private fireSelection;
1021
+ /**
1022
+ * Normalise a DOM `KeyboardEvent` into a {@link KeyDownPayload} and
1023
+ * dispatch to subscribers in registration order. Subscribers can
1024
+ * `preventDefault()` (browser default) and / or `stopPropagation()`
1025
+ * (further subscribers). The editor itself binds no shortcuts —
1026
+ * everything goes through plugins.
1027
+ */
1028
+ private fireKeyDown;
1029
+ private summariseBlock;
1030
+ private onPaste;
1031
+ /**
1032
+ * Insert `text` at the current selection in track-changes mode, with
1033
+ * each `\n` becoming a `splitBlock`. Used by `onPaste` for plain-text
1034
+ * paste; could be reused for tracked drop (a follow-up). Splits the
1035
+ * line list once up-front and walks it so each insertRun lands at the
1036
+ * caret of the *current* paragraph (which may be a fresh one from a
1037
+ * preceding splitBlock).
1038
+ */
1039
+ private pasteTrackedText;
1040
+ private onDragOver;
1041
+ private onDrop;
1042
+ private insertImageFromFile;
1043
+ }
1044
+ /**
1045
+ * Read and write the caret / selection in model terms. Lives on
1046
+ * `editor.selection`. Wraps `window.getSelection()` so callers never
1047
+ * touch the DOM directly.
1048
+ */
1049
+ export declare class EditorSelection {
1050
+ private readonly editor;
1051
+ constructor(editor: Editor);
1052
+ /** Current selection as a model `Selection`. Returns `null` when focus is outside. */
1053
+ get(): Selection;
1054
+ /** Apply a model selection to the DOM. */
1055
+ set(sel: Selection): boolean;
1056
+ /** Shortcut: current selection as a `Range`, or `null` when collapsed/absent. */
1057
+ currentRange(): ApiRange | null;
1058
+ /** Shortcut: the caret position (collapses a range to its `from`). */
1059
+ currentCaret(): InlinePosition | null;
1060
+ /** Shortcut: ref of the block containing the caret. */
1061
+ currentBlock(): BlockRef | null;
1062
+ /** Legacy: current block index (for code still using indices). */
1063
+ currentBlockIndex(): number | null;
1064
+ }
1065
+ /**
1066
+ * Default {@link CommandBus} implementation. Plain in-memory map; no
1067
+ * editor coupling beyond the closure plugins use when registering.
1068
+ * Replacing it would mean swapping a field on Editor — the rest of
1069
+ * the surface stays the same.
1070
+ */
1071
+ export declare class EditorCommands implements CommandBus {
1072
+ private readonly commands;
1073
+ register<Args = void>(def: CommandDefinition<Args>): () => void;
1074
+ execute<Args = void>(name: string, args?: Args): void;
1075
+ list(): CommandSnapshot[];
1076
+ has(name: string): boolean;
1077
+ }
1078
+ export { countBlocks };