@sobree/core 0.1.1 → 0.1.2

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 (37) hide show
  1. package/dist/doc/types.d.ts +40 -30
  2. package/dist/docx/shared/units.d.ts +3 -2
  3. package/dist/docx/shared/xml.d.ts +12 -0
  4. package/dist/editor/commands.d.ts +14 -0
  5. package/dist/editor/context.d.ts +71 -0
  6. package/dist/editor/dom.d.ts +21 -0
  7. package/dist/editor/events.d.ts +29 -0
  8. package/dist/editor/index.d.ts +85 -663
  9. package/dist/editor/internal/mutations.d.ts +1 -1
  10. package/dist/editor/ops/blocks.d.ts +42 -0
  11. package/dist/editor/ops/comments.d.ts +12 -0
  12. package/dist/editor/ops/parts.d.ts +71 -0
  13. package/dist/editor/ops/review.d.ts +67 -0
  14. package/dist/editor/ops/runs.d.ts +83 -0
  15. package/dist/editor/ops/trackedInput.d.ts +44 -0
  16. package/dist/editor/query.d.ts +22 -0
  17. package/dist/editor/revisionRuns.d.ts +56 -0
  18. package/dist/editor/selection.d.ts +34 -0
  19. package/dist/editor/types.d.ts +292 -0
  20. package/dist/editor/view/docRenderer/anchorPosition.d.ts +18 -0
  21. package/dist/editor/view/docRenderer/sectionFlow.d.ts +23 -0
  22. package/dist/editor/view/docRenderer/table.d.ts +11 -2
  23. package/dist/index.css +1 -1
  24. package/dist/index.js +6099 -6020
  25. package/dist/index.js.map +1 -1
  26. package/dist/paperStack/paginationV2/apply.d.ts +18 -0
  27. package/dist/paperStack/paginationV2/engine.d.ts +7 -0
  28. package/dist/paperStack/paginationV2/measure.d.ts +9 -0
  29. package/dist/paperStack/paginationV2/types.d.ts +273 -0
  30. package/dist/paperStack/paper.d.ts +31 -9
  31. package/dist/paperStack/paperStack.d.ts +13 -1
  32. package/dist/paperStack/paperZone.d.ts +35 -0
  33. package/dist/plugins/marks.d.ts +4 -4
  34. package/dist/ydoc/schema.d.ts +5 -0
  35. package/package.json +1 -1
  36. package/dist/__vite-browser-external-DYxpcVy9.js +0 -5
  37. package/dist/__vite-browser-external-DYxpcVy9.js.map +0 -1
@@ -1,266 +1,20 @@
1
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';
2
+ import { Range as ApiRange, BlockRef, EditError, EditResult, InlinePosition, Selection } from '../doc/api';
4
3
  import { RunPropertiesPatch } from '../doc/runs';
5
- import { Block, InlineRun, ParagraphAlignment, ParagraphProperties, SobreeDocument } from '../doc/types';
4
+ import { Block, InlineRun, SobreeDocument } from '../doc/types';
5
+ import { EmbedFontFaces, EmbedFontOptions } from '../fonts';
6
+ import { History } from '../history';
6
7
  import { BlockRegistry } from './internal/blockRegistry';
7
8
  import { countBlocks } from './internal/positionMap';
9
+ import { EditorSelection } from './selection';
8
10
  import { EditorTable } from './table';
9
- import { History } from '../history';
11
+ import { ApiRangeType, BlockInfo, ChangePayload, CommandBus, CommandDefinition, CommandSnapshot, EditorEvent, EditorEventPayload, EditorOptions, KeyDownPayload, OutlineItem, ParagraphPropertiesPatch, RevisionSpan, SelectionPayload, TrackChangesState, Unsubscribe, WrapTag } from './types';
10
12
  import * as Y from "yjs";
13
+ export { EditorCommands } from './commands';
14
+ export { EditorSelection } from './selection';
11
15
  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
- }
16
+ export type { ApiRangeType, BlockInfo, ChangePayload, CommandBus, CommandDefinition, CommandSnapshot, EditorEvent, EditorEventPayload, EditorOptions, KeyDownPayload, OutlineItem, ParagraphPropertiesPatch, RevisionSpan, SelectionPayload, TrackChangesState, Unsubscribe, WrapTag, };
17
+ export type { CellRef, InsertAt, InsertColumnOpts, InsertRowOpts, MergeCellsOpts, } from './types';
264
18
  export { runsLength } from '../doc/runs';
265
19
  export type { RunPropertiesPatch };
266
20
  /**
@@ -349,7 +103,9 @@ export declare class Editor {
349
103
  private dragOverListener;
350
104
  private dropListener;
351
105
  private revision;
352
- private readonly listeners;
106
+ /** The editor's observable event surface (change / selection / keydown
107
+ * / track-changes-change). See `./events.ts`. */
108
+ private readonly events;
353
109
  /**
354
110
  * Authoring mode for revisions. Off by default — mutations apply
355
111
  * plainly. On, `insertRun` / `deleteRange` route through
@@ -358,26 +114,13 @@ export declare class Editor {
358
114
  */
359
115
  private trackChanges;
360
116
  /**
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.
117
+ * Track-changes authoring input handler — routes tracked-mode
118
+ * `beforeinput` / IME composition / paste through the typed API so
119
+ * resulting runs carry revision markers. Holds the IME composition
120
+ * snapshot + warn-once state. Built in the constructor. See
121
+ * `ops/trackedInput`.
366
122
  */
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;
123
+ private trackedInput;
381
124
  private compositionStartListener;
382
125
  private compositionEndListener;
383
126
  /** Document-level `selectionchange` listener. Funnels every cursor
@@ -408,7 +151,20 @@ export declare class Editor {
408
151
  * and `emitChangeNow` sync only when this flag is set.
409
152
  */
410
153
  private domDirty;
154
+ /**
155
+ * Kernel seam handed to the behaviour modules (`ops/*`, `query`). Built
156
+ * once in the constructor; closes over this instance's privates so the
157
+ * `commit` pipeline / lock checks stay private to the class. See
158
+ * `./context.ts`.
159
+ */
160
+ private readonly ctx;
411
161
  constructor(host: HTMLElement, options?: EditorOptions);
162
+ /**
163
+ * Assemble the {@link EditorContext} the behaviour modules operate on.
164
+ * Closes over `this` so the kernel methods (`commit`, `checkRefs`, …)
165
+ * stay private to the class while modules get a curated surface.
166
+ */
167
+ private buildContext;
412
168
  /**
413
169
  * Re-project the Y.Doc into `this.doc`, sync the BlockRegistry to
414
170
  * the projected ids, re-render the DOM, and fire `change`. Called
@@ -421,21 +177,11 @@ export declare class Editor {
421
177
  * is owned by the caller. Missing hashes stay out of `rawParts`;
422
178
  * the renderer handles missing parts gracefully (placeholder).
423
179
  */
424
- private resolveCachedPartRefsInto;
425
180
  /**
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).
181
+ * Wait for every currently-referenced binary part to be available in
182
+ * the local cache. Useful before `toDocx()` so the exported file
183
+ * contains all images / fonts. Resolves immediately when no
184
+ * `blobStore` is configured (bytes are always inline).
439
185
  */
440
186
  ensurePartsLoaded(): Promise<void>;
441
187
  /**
@@ -455,6 +201,20 @@ export declare class Editor {
455
201
  * operations that Y.UndoManager turns into a single stack item.
456
202
  */
457
203
  private applyDocument;
204
+ /**
205
+ * Re-render the current `doc` into the content hosts. Syncs
206
+ * `@font-face` registrations BEFORE rendering so newly-embedded fonts
207
+ * are available to the render pass. No selection restore, no change
208
+ * emit — callers sequence those.
209
+ */
210
+ private renderCurrent;
211
+ /**
212
+ * Soft-revert to a doc `snapshot` and re-render. Resets the
213
+ * serialised-block cache + dom-dirty flag; no registry reset, mirror, or
214
+ * change emit. Used to undo native IME mutations before a tracked
215
+ * re-insert (see `ops/trackedInput`).
216
+ */
217
+ private restoreSnapshot;
458
218
  /**
459
219
  * Drop entries from `rawParts` that nothing in the AST references.
460
220
  * Useful after deleting images (or font embeds — Phase 3) to keep the
@@ -469,15 +229,10 @@ export declare class Editor {
469
229
  pruned: string[];
470
230
  };
471
231
  /**
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.
232
+ * Embed a TTF/OTF font into the document. Refuses (with a warning)
233
+ * restricted fonts unless `opts.allowRestricted` is true. Pass any
234
+ * subset of {regular, bold, italic, boldItalic}; missing faces are
235
+ * simply not embedded.
481
236
  */
482
237
  embedFont(name: string, faces: EmbedFontFaces, opts?: EmbedFontOptions): {
483
238
  warnings: string[];
@@ -502,40 +257,19 @@ export declare class Editor {
502
257
  /** Replace the block at `target`'s index with `block`. */
503
258
  replaceBlock(target: BlockRef, block: Block): EditResult<BlockRef>;
504
259
  /**
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.
260
+ * Insert `block` before the target block. Returns the new ref. In
261
+ * track-changes mode a paragraph block is stamped `revision: ins`;
262
+ * non-paragraph blocks insert plain.
512
263
  */
513
264
  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
- */
265
+ /** Insert `block` after the target block. Returns the new ref. */
518
266
  insertBlockAfter(target: BlockRef, block: Block): EditResult<BlockRef>;
519
267
  /**
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.
268
+ * Delete the target block. In track-changes mode paragraph blocks are
269
+ * stamped `del` (kept visible) rather than removed; tables / section
270
+ * breaks remove plainly.
530
271
  */
531
272
  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
273
  /** Merge a patch into each target paragraph's properties. */
540
274
  applyBlockProperties(targets: BlockRef[], patch: ParagraphPropertiesPatch): EditResult<void>;
541
275
  /**
@@ -557,48 +291,21 @@ export declare class Editor {
557
291
  expect?: Record<string, number>;
558
292
  }): EditResult<void>;
559
293
  /**
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).
294
+ * Insert a run at `at`. In track-changes mode the run is stamped
295
+ * `revision: ins` unless it already carries one.
566
296
  */
567
297
  insertRun(at: InlinePosition, run: InlineRun): EditResult<BlockRef>;
568
298
  /**
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.
299
+ * Split a paragraph at `at`; runs after the offset move into a new
300
+ * paragraph inserted after, inheriting the original's properties.
301
+ * Returns the new (second) block's ref. In track-changes mode the new
302
+ * paragraph is stamped `revision: ins`.
587
303
  */
588
304
  splitBlock(at: InlinePosition): EditResult<BlockRef>;
589
305
  /**
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).
306
+ * Insert an image at `at`. Bytes are stored in `doc.rawParts` and a
307
+ * `DrawingRun` is inserted; with a `blobStore` configured the bytes
308
+ * migrate to the store in the background.
602
309
  */
603
310
  insertImage(at: InlinePosition, bytes: Uint8Array, opts: {
604
311
  mime: string;
@@ -630,20 +337,6 @@ export declare class Editor {
630
337
  deleteRange(range: ApiRange, opts?: {
631
338
  expect?: Record<string, number>;
632
339
  }): 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
340
  /**
648
341
  * Read the current track-changes mode. Defaults to `{ enabled: false }`
649
342
  * — the editor mutates the document plainly. See `setTrackChanges`.
@@ -663,110 +356,6 @@ export declare class Editor {
663
356
  * to a toolbar pill / mode badge.
664
357
  */
665
358
  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
359
  /**
771
360
  * Accept the tracked changes inside `range`: insertions become
772
361
  * permanent (the revision marker is stripped, text kept), deletions
@@ -777,112 +366,38 @@ export declare class Editor {
777
366
  acceptRevision(range: ApiRange, opts?: {
778
367
  expect?: Record<string, number>;
779
368
  }): 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
- */
369
+ /** Reject the tracked changes inside `range`. Inverse of `acceptRevision`. */
786
370
  rejectRevision(range: ApiRange, opts?: {
787
371
  expect?: Record<string, number>;
788
372
  }): 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
- */
373
+ /** Accept tracked format changes inside `range` (drop the snapshot). */
794
374
  acceptFormatRevision(range: ApiRange, opts?: {
795
375
  expect?: Record<string, number>;
796
376
  }): EditResult<void>;
797
- /**
798
- * Reject tracked format changes inside `range`: each run reverts its
799
- * `properties` to `revisionFormat.before`. Inverse of
800
- * `acceptFormatRevision`.
801
- */
377
+ /** Reject tracked format changes inside `range` (revert to `before`). */
802
378
  rejectFormatRevision(range: ApiRange, opts?: {
803
379
  expect?: Record<string, number>;
804
380
  }): EditResult<void>;
805
381
  /**
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.
382
+ * Accept the paragraph-mark revision on `target`. `ins` strips the
383
+ * marker (the break stays); `del` merges this paragraph into the
384
+ * previous one.
817
385
  */
818
386
  acceptParagraphRevision(target: BlockRef): EditResult<void>;
819
387
  /**
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.
388
+ * Reject the paragraph-mark revision on `target`. `ins` undoes the
389
+ * split (merge into previous); `del` strips the marker.
824
390
  */
825
391
  rejectParagraphRevision(target: BlockRef): EditResult<void>;
826
392
  /**
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.
393
+ * Enumerate every logical tracked change. Consecutive same-author
394
+ * revision runs coalesce into one `RevisionSpan` with fresh versioned
395
+ * refs. Re-query after each `change`.
869
396
  */
870
397
  getRevisions(): RevisionSpan[];
871
398
  /**
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.
399
+ * Accept every tracked change in the document (optionally filtered by
400
+ * `opts.author`). One commit for the whole sweep.
886
401
  */
887
402
  acceptAllRevisions(opts?: {
888
403
  author?: string;
@@ -891,21 +406,10 @@ export declare class Editor {
891
406
  rejectAllRevisions(opts?: {
892
407
  author?: string;
893
408
  }): 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
409
  /** Mark comment `id` resolved (`Comment.done = true`). */
905
410
  resolveComment(id: number): EditResult<void>;
906
411
  /** Re-open a resolved comment `id` (`Comment.done = false`). */
907
412
  reopenComment(id: number): EditResult<void>;
908
- private setCommentDone;
909
413
  setBlockPropertiesAtSelection(patch: ParagraphPropertiesPatch): EditResult<void>;
910
414
  setRunPropertiesAtSelection(patch: RunPropertiesPatch): EditResult<void>;
911
415
  wrapSelection(tag: WrapTag): EditResult<void>;
@@ -917,8 +421,8 @@ export declare class Editor {
917
421
  }): EditResult<BlockRef>;
918
422
  /**
919
423
  * 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.
424
+ * Best-effort DOM-level cleanup — preserves the in-place UX without a
425
+ * re-render.
922
426
  */
923
427
  clearInlineFormattingAtSelection(): void;
924
428
  on<E extends EditorEvent>(event: E, cb: (p: EditorEventPayload[E]) => void): Unsubscribe;
@@ -938,12 +442,6 @@ export declare class Editor {
938
442
  private blockIdsArray;
939
443
  private checkRefs;
940
444
  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
445
  /**
948
446
  * Apply a mutation to `this.doc`, update the registry, re-render, fire
949
447
  * change. Returns the affected refs (post-bump).
@@ -990,27 +488,6 @@ export declare class Editor {
990
488
  * driven mutations (already rendered) from remote ones (need re-render).
991
489
  */
992
490
  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
491
  /**
1015
492
  * Compose the current selection into a {@link SelectionPayload} and
1016
493
  * dispatch to subscribers. Called from the document-level
@@ -1018,61 +495,6 @@ export declare class Editor {
1018
495
  * even when no subscribers exist (the early-return keeps it cheap).
1019
496
  */
1020
497
  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
498
  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
499
  }
1078
500
  export { countBlocks };