@docx-editor.dev/core 0.0.1-placeholder → 2.0.1

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 (116) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +92 -2
  3. package/THIRD_PARTY_NOTICES.md +18 -0
  4. package/dist/automation.cjs +1 -0
  5. package/dist/automation.d.cts +74 -0
  6. package/dist/automation.d.ts +74 -0
  7. package/dist/automation.js +1 -0
  8. package/dist/binding.cjs +1 -0
  9. package/dist/binding.d.cts +82 -0
  10. package/dist/binding.d.ts +82 -0
  11. package/dist/binding.js +1 -0
  12. package/dist/chunk-5KSCB2OZ.js +2 -0
  13. package/dist/chunk-BSC44TIT.js +5 -0
  14. package/dist/chunk-DLPL5DAG.js +12 -0
  15. package/dist/chunk-DOEIARE2.js +2 -0
  16. package/dist/chunk-G7YCQBZE.cjs +5 -0
  17. package/dist/chunk-HBL3NNH5.cjs +1 -0
  18. package/dist/chunk-HR7K3W6A.js +1 -0
  19. package/dist/chunk-HVAV2JSN.js +11 -0
  20. package/dist/chunk-MYH2HCUJ.cjs +2 -0
  21. package/dist/chunk-NSM75TN6.cjs +1 -0
  22. package/dist/chunk-PHDVKUWZ.js +1 -0
  23. package/dist/chunk-QADYGK5L.cjs +12 -0
  24. package/dist/chunk-REZUWYB7.js +1 -0
  25. package/dist/chunk-RNBHTPW7.js +10 -0
  26. package/dist/chunk-UD2ZXLFI.cjs +2 -0
  27. package/dist/chunk-VYAVK2AH.cjs +1 -0
  28. package/dist/chunk-YUK46A2N.cjs +11 -0
  29. package/dist/chunk-ZMIX42J7.cjs +10 -0
  30. package/dist/chunk-ZT55Q52J.cjs +2 -0
  31. package/dist/chunk-ZUALAR4M.js +2 -0
  32. package/dist/content-control-walk-CbTqHq6O.d.cts +56 -0
  33. package/dist/content-control-walk-D06GijR0.d.ts +56 -0
  34. package/dist/contracts/document.cjs +1 -0
  35. package/dist/contracts/document.d.cts +246 -0
  36. package/dist/contracts/document.d.ts +246 -0
  37. package/dist/contracts/document.js +0 -0
  38. package/dist/contracts/editor.cjs +1 -0
  39. package/dist/contracts/editor.d.cts +11 -0
  40. package/dist/contracts/editor.d.ts +11 -0
  41. package/dist/contracts/editor.js +1 -0
  42. package/dist/contracts/interaction.cjs +1 -0
  43. package/dist/contracts/interaction.d.cts +11 -0
  44. package/dist/contracts/interaction.d.ts +11 -0
  45. package/dist/contracts/interaction.js +0 -0
  46. package/dist/contracts/modules.cjs +1 -0
  47. package/dist/contracts/modules.d.cts +90 -0
  48. package/dist/contracts/modules.d.ts +90 -0
  49. package/dist/contracts/modules.js +1 -0
  50. package/dist/contracts/types.cjs +1 -0
  51. package/dist/contracts/types.d.cts +452 -0
  52. package/dist/contracts/types.d.ts +452 -0
  53. package/dist/contracts/types.js +0 -0
  54. package/dist/editor-C1QRi1v4.d.ts +1671 -0
  55. package/dist/editor-D0UDock4.d.cts +1671 -0
  56. package/dist/editor.cjs +1 -0
  57. package/dist/editor.css +6017 -0
  58. package/dist/editor.d.cts +271 -0
  59. package/dist/editor.d.ts +271 -0
  60. package/dist/editor.js +1 -0
  61. package/dist/hf-references-BdjsQNE_.d.ts +71 -0
  62. package/dist/hf-references-CgEOSw3i.d.cts +71 -0
  63. package/dist/image-resources-BJnTpG66.d.ts +195 -0
  64. package/dist/image-resources-BpIydXtU.d.cts +195 -0
  65. package/dist/index-CrW-IxXt.d.cts +2515 -0
  66. package/dist/index-ZqBEOrTV.d.ts +2515 -0
  67. package/dist/index.cjs +1 -0
  68. package/dist/index.d.cts +22 -0
  69. package/dist/index.d.ts +22 -0
  70. package/dist/index.js +1 -0
  71. package/dist/layout.cjs +1 -0
  72. package/dist/layout.d.cts +2316 -0
  73. package/dist/layout.d.ts +2316 -0
  74. package/dist/layout.js +1 -0
  75. package/dist/note-nodes-BPSTVDMr.d.ts +108 -0
  76. package/dist/note-nodes-C6DLxlvj.d.cts +108 -0
  77. package/dist/note-properties-CnmCoory.d.cts +68 -0
  78. package/dist/note-properties-m75hmP70.d.ts +68 -0
  79. package/dist/ooxml-package-CR5us6ad.d.cts +354 -0
  80. package/dist/ooxml-package-D_CtywTW.d.ts +354 -0
  81. package/dist/ooxml-tree-BU0e3DVi.d.cts +871 -0
  82. package/dist/ooxml-tree-BU0e3DVi.d.ts +871 -0
  83. package/dist/output.cjs +1 -0
  84. package/dist/output.d.cts +158 -0
  85. package/dist/output.d.ts +158 -0
  86. package/dist/output.js +1 -0
  87. package/dist/protocol-CAkOBBGE.d.cts +1201 -0
  88. package/dist/protocol-oxHaWmt4.d.ts +1201 -0
  89. package/dist/review-support-DS8DYX2a.d.cts +326 -0
  90. package/dist/review-support-Fy3vdIea.d.ts +326 -0
  91. package/dist/revision-projection-72ZApe-g.d.cts +84 -0
  92. package/dist/revision-projection-DHAkcD_L.d.ts +84 -0
  93. package/dist/semantic-cell-selection-CZjBSmMv.d.cts +427 -0
  94. package/dist/semantic-cell-selection-DsmHmLJ5.d.ts +427 -0
  95. package/dist/semantic-interaction-D-Dszxrn.d.cts +187 -0
  96. package/dist/semantic-interaction-Dy-dCUFF.d.ts +187 -0
  97. package/dist/semantic-paint-drawings-BA3z9jl8.d.ts +21 -0
  98. package/dist/semantic-paint-drawings-D7qbB0E2.d.cts +21 -0
  99. package/dist/semantic-records-CJR3BvHs.d.cts +1432 -0
  100. package/dist/semantic-records-CWodBJsz.d.ts +1432 -0
  101. package/dist/shaped-measurer-B-TAtcQU.d.ts +613 -0
  102. package/dist/shaped-measurer-CNIT_svZ.d.cts +613 -0
  103. package/dist/store.cjs +1 -0
  104. package/dist/store.d.cts +2247 -0
  105. package/dist/store.d.ts +2247 -0
  106. package/dist/store.js +1 -0
  107. package/dist/tree-op-types-CwOeiaQ8.d.ts +1431 -0
  108. package/dist/tree-op-types-DMiW6nIH.d.cts +1431 -0
  109. package/dist/tree-package-store-D5S7VKs5.d.ts +708 -0
  110. package/dist/tree-package-store-DOKMtCnh.d.cts +708 -0
  111. package/dist/tree-session-CiUel5Kz.d.ts +542 -0
  112. package/dist/tree-session-D47_imvR.d.cts +542 -0
  113. package/dist/tree-store-CxifqwIv.d.cts +273 -0
  114. package/dist/tree-store-hLtjeyWE.d.ts +273 -0
  115. package/package.json +105 -15
  116. package/index.js +0 -1
@@ -0,0 +1,1671 @@
1
+ import { DocEdits, DocQueries, DocQueryResults, ContentControlSummary } from './contracts/document.cjs';
2
+ import { g as ReviewRevisionKind, c as ReviewCommentItem, b as ReviewRevisionItem, d as ReviewCustomItem } from './review-support-DS8DYX2a.cjs';
3
+ import { a as SemanticSelection, S as SemanticPosition } from './semantic-interaction-D-Dszxrn.cjs';
4
+ import { ExecErrorCode, ColorValue, Watermark, DocAnchor, ExecResult, ContentControlFilter, DocRange, RunFormatting, Revision, Rect, Unsubscribe } from './contracts/types.cjs';
5
+ import { S as SourceCrop, b as ImageWrapTarget, c as DrawingKind, d as DrawingPositionInput, e as DrawingLocks } from './tree-op-types-DMiW6nIH.cjs';
6
+ import { S as SupportedImageMime, I as ImageResourceState } from './image-resources-BpIydXtU.cjs';
7
+
8
+ /**
9
+ * `@docx-editor.dev/core/contracts/interaction` — semantic addressing and interaction outcomes.
10
+ *
11
+ * Semantic identities and targets: the vocabulary a caller uses to say WHICH text it means,
12
+ * independent of layout, DOM or any editing engine's positions. Plus the outcome type an
13
+ * interaction attempt answers with. That is the whole module.
14
+ *
15
+ * It used to be 599 lines. An "interaction frame" lived here — a revision-tagged projection
16
+ * of display, page geometry, caret and selection overlays, focus, composition and
17
+ * accessibility, plus a typed pointer-intent dispatch protocol and a render IR of glyph
18
+ * runs — and every one of those declarations was consumed by exactly nothing. They
19
+ * described an architecture the engine does not use: the paginated surface owns pointer
20
+ * interaction internally and paints through `Editor.attach`, so no frame was ever published
21
+ * and no intent ever dispatched.
22
+ * THREE of them (`CaretGeometry`, `HitTestOptions`, `ShapedCluster`) also collided by name
23
+ * with the REAL, differently-shaped types in `layout/`, so the published contract carried a
24
+ * second definition of a live concept and `import { ShapedCluster }` meant different things
25
+ * depending on which module you reached for.
26
+ * `InteractionFrameId` and a frame-tagged `SemanticSelection` outlived that cut. Both were
27
+ * the same mistake: the id was a placeholder nothing ever minted (`focus` returned a literal
28
+ * `{ value: 0 }`), and the selection could not be CONSTRUCTED without one, which made it a
29
+ * fourth name-collision with the real `SemanticSelection` in `layout/` that a caller could
30
+ * never actually satisfy.
31
+ *
32
+ * CONTRACT ONLY — declarations, not an implementation.
33
+ *
34
+ * @packageDocumentation
35
+ * @public
36
+ */
37
+
38
+ /** Bidi/grapheme affinity for a text caret or hit target. */
39
+ type InteractionAffinity = 'upstream' | 'downstream';
40
+ /**
41
+ * Model-derived stable identity within a scope. Positions resolve through this
42
+ * index, not accumulated display-item lengths or editing-engine coordinates.
43
+ */
44
+ interface SemanticIdentity {
45
+ readonly storyId: string;
46
+ readonly blockId: string;
47
+ }
48
+ /** A PM-free semantic caret, range endpoint, or atomic selection target. */
49
+ type SemanticTarget = {
50
+ readonly kind: 'text';
51
+ readonly scope: ViewScope;
52
+ readonly identity: SemanticIdentity;
53
+ readonly graphemeOffset: number;
54
+ readonly affinity: InteractionAffinity;
55
+ } | {
56
+ readonly kind: 'atomic';
57
+ readonly scope: ViewScope;
58
+ readonly objectId: string;
59
+ };
60
+ /** Typed rejection for pending, read-only, invalid, or unsupported interaction. */
61
+ type InteractionOutcomeCode = 'pendingLayout' | 'pendingSelection' | 'readOnly' | 'invalidTarget' | 'unsupported';
62
+ /**
63
+ * The result of one interaction attempt.
64
+ *
65
+ * A rejection carries the ENGINE's own `reason`, which is what lets a caller surface why an
66
+ * interaction was refused instead of guessing.
67
+ */
68
+ type InteractionOutcome<T> = {
69
+ readonly ok: true;
70
+ readonly value: T;
71
+ } | {
72
+ readonly ok: false;
73
+ readonly code: InteractionOutcomeCode;
74
+ readonly reason: string;
75
+ };
76
+
77
+ /**
78
+ * Header/footer and footnote/endnote public contract types for the Editor facade.
79
+ *
80
+ * Separated from `editor.ts` so the facade file stays under the max-lines gate while
81
+ * furniture/notes APIs remain one cohesive vocabulary. Re-exported from `editor.ts`
82
+ * so consumers keep importing `@docx-editor.dev/core/contracts/editor`.
83
+ */
84
+ /** Furniture variant selected by section title-page / even-and-odd flags. */
85
+ type FurnitureVariant = 'default' | 'first' | 'even';
86
+ /** Header/footer editing state: which region is being edited, if any. */
87
+ interface HeaderFooterState {
88
+ readonly editing: 'header' | 'footer' | null;
89
+ readonly sectionIndex: number;
90
+ /** Furniture variant in effect on the page used to enter the scope. */
91
+ readonly variant?: FurnitureVariant;
92
+ /** Relationship id of the open story (`EditorScope.rId`). */
93
+ readonly rId?: string;
94
+ /** Package part name of the open story. */
95
+ readonly partName?: string;
96
+ /**
97
+ * Whether the resolved part is inherited from a preceding section ("Same as Previous")
98
+ * rather than declared on this section.
99
+ */
100
+ readonly inherited?: boolean;
101
+ /** Section `w:titlePg` — first-page furniture is distinct when true. */
102
+ readonly titlePage?: boolean;
103
+ /** Document `w:evenAndOddHeaders` — even-page furniture is distinct when true. */
104
+ readonly evenAndOddHeaders?: boolean;
105
+ /** Section header distance from sheet edge, twips (`w:pgMar w:header`). */
106
+ readonly headerDistanceTwips?: number;
107
+ /** Section footer distance from sheet edge, twips (`w:pgMar w:footer`). */
108
+ readonly footerDistanceTwips?: number;
109
+ }
110
+ /** Footnote vs endnote — the public Editor vocabulary. */
111
+ type NoteKind = 'footnote' | 'endnote';
112
+ /** Authored note-numbering fields as Word's properties dialog writes them. */
113
+ interface AuthoredNoteNumbering {
114
+ readonly pos?: string;
115
+ readonly numFmt?: string;
116
+ readonly numStart?: number;
117
+ readonly numRestart?: string;
118
+ }
119
+ /** Resolved note-numbering fields after document/section cascade. */
120
+ interface ResolvedNoteNumbering {
121
+ readonly pos: string;
122
+ readonly numFmt: string;
123
+ readonly numStart: number;
124
+ readonly numRestart: string;
125
+ }
126
+ /** One note kind's resolved + authored properties for the caret section. */
127
+ interface NotePropertiesSide {
128
+ readonly resolved: ResolvedNoteNumbering;
129
+ readonly documentAuthored?: AuthoredNoteNumbering;
130
+ readonly sectionAuthored?: AuthoredNoteNumbering;
131
+ }
132
+ /** Resolved and authored note properties for the caret section — properties dialog read-model. */
133
+ interface NotePropertiesState {
134
+ readonly sectionIndex: number;
135
+ readonly footnote: NotePropertiesSide;
136
+ readonly endnote: NotePropertiesSide;
137
+ }
138
+ /** Optional slot targeting shared by remove / link / unlink furniture commands. */
139
+ interface HeaderFooterSlotArgs {
140
+ position?: 'header' | 'footer';
141
+ /** Prefer over `firstPage` / `evenPage` when selecting a furniture variant. */
142
+ variant?: FurnitureVariant;
143
+ firstPage?: boolean;
144
+ evenPage?: boolean;
145
+ sectionIndex?: number;
146
+ }
147
+ /**
148
+ * Header/footer lifecycle and page-field commands on {@link EditorCommands}.
149
+ *
150
+ * Kept as a mixin so `editor.ts` can extend it without inlining the furniture vocabulary.
151
+ */
152
+ interface EditorHeaderFooterCommands {
153
+ /**
154
+ * Open a header or footer for editing, materialising an empty one if the section has
155
+ * none — which is what a double-click on the header band means in Word.
156
+ *
157
+ * Prefer `variant` when selecting first/even/default furniture. `firstPage` /
158
+ * `evenPage` remain supported for existing callers (`firstPage: true` ≡ `variant:
159
+ * 'first'`; `evenPage: true` ≡ `variant: 'even'`). Creating a missing `first` part
160
+ * also enables section `w:titlePg` in the same undo unit; creating a missing `even`
161
+ * part also enables document `w:evenAndOddHeaders` in the same undo unit.
162
+ */
163
+ editHeaderFooter: {
164
+ position: 'header' | 'footer';
165
+ variant?: FurnitureVariant;
166
+ firstPage?: boolean;
167
+ evenPage?: boolean;
168
+ sectionIndex?: number;
169
+ };
170
+ /** Leave header/footer editing and return to the body. */
171
+ exitHeaderFooter: Record<never, never>;
172
+ /**
173
+ * Delete a declared header/footer reference (and GC the part when orphaned). When the
174
+ * editor is already in furniture scope, omitted fields default to the active story.
175
+ */
176
+ removeHeaderFooter: HeaderFooterSlotArgs;
177
+ /**
178
+ * Turn on "Same as Previous" for a section's furniture slot (drop its declared ref).
179
+ * Refused on the first section. Omitted fields default to the active story when scoped.
180
+ */
181
+ linkHeaderFooterToPrevious: HeaderFooterSlotArgs;
182
+ /**
183
+ * Turn off "Same as Previous": clone the inherited part into a declared reference.
184
+ * When the active scope was the inherited rId, the editor rebinds to the clone.
185
+ */
186
+ unlinkHeaderFooterFromPrevious: HeaderFooterSlotArgs;
187
+ /**
188
+ * Section/document furniture options: `titlePg` and header/footer distances on a
189
+ * section; `evenAndOddHeaders` document-wide in settings.
190
+ */
191
+ setHeaderFooterOptions: {
192
+ sectionIndex?: number;
193
+ titlePage?: boolean;
194
+ evenAndOddHeaders?: boolean;
195
+ headerDistanceTwips?: number;
196
+ footerDistanceTwips?: number;
197
+ };
198
+ /**
199
+ * Insert an allowlisted page-number field at the caret. Only valid while a header or
200
+ * footer scope is open. `PAGE_X_OF_Y` writes PAGE + " of " + NUMPAGES as one undo unit.
201
+ */
202
+ insertPageField: {
203
+ field: 'PAGE' | 'NUMPAGES' | 'SECTIONPAGES' | 'PAGE_X_OF_Y';
204
+ };
205
+ }
206
+ /**
207
+ * Footnote/endnote lifecycle and properties commands on {@link EditorCommands}.
208
+ */
209
+ interface EditorNoteCommands {
210
+ /** Insert a footnote or endnote reference at the caret (body only). */
211
+ insertNote: {
212
+ noteKind: NoteKind;
213
+ };
214
+ /** Delete a note and its body reference together. */
215
+ deleteNote: {
216
+ noteKind: NoteKind;
217
+ noteId: number;
218
+ };
219
+ /** Convert one note to the other kind. */
220
+ convertNote: {
221
+ fromKind: NoteKind;
222
+ noteId: number;
223
+ };
224
+ /** Convert every note of one kind to the other in document order (one undo step). */
225
+ convertAllNotes: {
226
+ fromKind: NoteKind;
227
+ };
228
+ /**
229
+ * Footnote and endnote properties for the section — numbering format, restart rule
230
+ * and position, as Word's dialog offers them.
231
+ */
232
+ setNoteProperties: {
233
+ scope?: 'document' | 'section';
234
+ sectionIndex?: number;
235
+ footnote?: {
236
+ numFmt?: string;
237
+ numRestart?: string;
238
+ position?: string;
239
+ numStart?: number;
240
+ };
241
+ endnote?: {
242
+ numFmt?: string;
243
+ numRestart?: string;
244
+ position?: string;
245
+ numStart?: number;
246
+ };
247
+ };
248
+ }
249
+
250
+ /** Crop edge in UI percent (0–100). @public */
251
+ interface ImageCropPercent {
252
+ readonly left: number;
253
+ readonly top: number;
254
+ readonly right: number;
255
+ readonly bottom: number;
256
+ }
257
+ /** Crop edge in OOXML permille (0–100000). */
258
+ interface ImageCropPermille {
259
+ readonly left: number;
260
+ readonly top: number;
261
+ readonly right: number;
262
+ readonly bottom: number;
263
+ }
264
+ /** Projection fraction (0–1) → UI percent (0–100). */
265
+ declare function cropPercentFromSourceCrop(crop: SourceCrop): ImageCropPercent;
266
+ /** UI percent (0–100) → projection fraction (0–1). */
267
+ declare function sourceCropFromCropPercent(crop: ImageCropPercent): SourceCrop;
268
+ /** UI percent → OOXML permille. */
269
+ declare function cropPermilleFromPercent(percent: number): number;
270
+ /** OOXML permille → UI percent. */
271
+ declare function cropPercentFromPermille(permille: number): number;
272
+ /** UI percent crop → permille for tree ops / a:srcRect. */
273
+ declare function cropPermilleFromCropPercent(crop: ImageCropPercent): ImageCropPermille;
274
+ /** Permille crop → UI percent. */
275
+ declare function cropPercentFromCropPermille(crop: ImageCropPermille): ImageCropPercent;
276
+ /**
277
+ * Whether a crop is expressible: every edge finite and in range, and opposite edges not
278
+ * overlapping.
279
+ *
280
+ * Opposite edges matter — a left plus right crop summing past 100% describes a negative width,
281
+ * which DrawingML has no way to store.
282
+ */
283
+ declare function validateImageCropPercent(crop: ImageCropPercent): boolean;
284
+
285
+ /**
286
+ * `@docx-editor.dev/core/contracts/editor` — the `Editor` contract adapters are written against.
287
+ *
288
+ * Commands go through `can` before `exec`; queries answer against the live, laid-out document.
289
+ * Type-only where it can be, so an adapter can name the whole surface without importing the
290
+ * engine.
291
+ *
292
+ * CONTRACT ONLY — declarations, not an implementation.
293
+ *
294
+ * @packageDocumentation
295
+ * @public
296
+ */
297
+
298
+ /**
299
+ * An opaque handle to a loaded document — its stable identity and current
300
+ * revision. The canonical authored state is the engine's `PackageModel`, NOT a
301
+ * simplified tree; advanced automation (`DocxEditor.run(handle, …)`) addresses a
302
+ * document through this handle rather than a serialized structure. Kept
303
+ * deliberately minimal and open so it can carry more identity later without a
304
+ * breaking change.
305
+ */
306
+ interface DocumentHandle {
307
+ /** The document's current store revision. */
308
+ readonly revision: number;
309
+ }
310
+ /**
311
+ * What `createDocxEditor`/`load` accept as a document: raw DOCX bytes, or an existing
312
+ * in-memory `DocumentHandle` (shared/handed off). The engine is byte-native
313
+ * (`PackageModel` is canonical); there is intentionally no structured-tree input,
314
+ * which would be lossy against the canonical package.
315
+ */
316
+ type DocumentSource = ArrayBuffer | Uint8Array | DocumentHandle;
317
+ /** A concrete font face requested by authored document content. */
318
+ interface FontFaceRequest {
319
+ readonly family: string;
320
+ readonly weight: number;
321
+ readonly style: 'normal' | 'italic';
322
+ }
323
+ /** Immutable, byte-backed font face supplied to layout and browser paint. */
324
+ interface FontSource {
325
+ readonly request: FontFaceRequest;
326
+ readonly id: string;
327
+ readonly bytes: Uint8Array;
328
+ readonly hash: string;
329
+ readonly faceIndex: number;
330
+ readonly availability?: 'available' | 'forbidden';
331
+ }
332
+ /** An explicit authored-font substitution. No implicit platform fallback is performed. */
333
+ interface FontSourceSubstitution {
334
+ readonly from: FontFaceRequest;
335
+ readonly to: FontFaceRequest;
336
+ }
337
+ /**
338
+ * Public font source configuration sampled when an adapter mounts. It must be immutable for that
339
+ * editor lifetime; remount the adapter to replace bytes or substitutions atomically.
340
+ */
341
+ interface FontConfiguration {
342
+ readonly epoch: number;
343
+ readonly maxFontBytes: number;
344
+ readonly sources: readonly FontSource[];
345
+ readonly substitutions?: readonly FontSourceSubstitution[];
346
+ readonly defaultFont: {
347
+ readonly family: string;
348
+ readonly sizeHalfPoints: number;
349
+ };
350
+ readonly language?: string;
351
+ }
352
+ /**
353
+ * Why a font was not admitted.
354
+ *
355
+ * Distinguished rather than collapsed because the responses differ: `overLimit` and `malformed`
356
+ * are the caller's own bytes, while `forbidden` and `hashMismatch` mean the source was not what
357
+ * it claimed and the load should not be retried.
358
+ */
359
+ type EditorFontErrorCode = 'initializationFailed' | 'missing' | 'forbidden' | 'overLimit' | 'malformed' | 'hashMismatch' | 'metadataMismatch' | 'fontFaceLoadFailed' | 'unsupportedFaceIndex' | 'missingFont' | 'hashInvalid' | 'fontMismatch' | 'unsupportedFace' | 'loadFailed';
360
+ /** Typed adapter error reported both through `onFontError` and accessible alert UI. */
361
+ declare class EditorFontError extends Error {
362
+ readonly name: string;
363
+ readonly code: EditorFontErrorCode;
364
+ readonly request?: FontFaceRequest;
365
+ readonly diagnostic?: string;
366
+ constructor(code: EditorFontErrorCode, message: string, details?: {
367
+ readonly request?: FontFaceRequest;
368
+ readonly diagnostic?: string;
369
+ readonly cause?: unknown;
370
+ });
371
+ }
372
+ /**
373
+ * The payload of the `change` event / `onChange`. It carries revision + identity
374
+ * deltas, NOT serialized bytes: serializing a whole DOCX on every keystroke would
375
+ * be prohibitive for large documents. Call `save()` to get bytes on demand.
376
+ */
377
+ interface DocumentChange {
378
+ /** The store revision after this change. */
379
+ readonly revision: number;
380
+ /** Block ids created/deleted/edited by this change, when the engine reports them. */
381
+ readonly created?: readonly string[];
382
+ readonly deleted?: readonly string[];
383
+ readonly dirty?: readonly string[];
384
+ }
385
+ /**
386
+ * The editor is N+1 editing views: one body plus one per header/footer
387
+ * relationship, plus footnotes, text boxes, and other addressable regions.
388
+ * Commands must say which one they target, or they silently hit the wrong
389
+ * surface when a header is focused.
390
+ *
391
+ * Intentionally open-ended: this set is expected to grow (notes, frames, and
392
+ * whatever regions later prove addressable), so treat it as non-exhaustive
393
+ * rather than a closed enum.
394
+ */
395
+ type EditorScope = {
396
+ kind: 'body';
397
+ } | {
398
+ kind: 'headerFooter';
399
+ rId: string;
400
+ }
401
+ /**
402
+ * A footnote/endnote region.
403
+ *
404
+ * `id` encodes kind + signed note id as `footnote:<id>` or `endnote:<id>`
405
+ * (e.g. `footnote:2`). Use `formatNoteScopeId` / `parseNoteScopeId` from the
406
+ * store package. Do not invent a parallel `{ noteKind, noteId }` scope arm.
407
+ */
408
+ | {
409
+ kind: 'note';
410
+ id: string;
411
+ }
412
+ /** A text box or floating frame with its own content, addressed by id. */
413
+ | {
414
+ kind: 'frame';
415
+ id: string;
416
+ }
417
+ /** Read-only aggregate across every view. Valid for queries, not for writes. */
418
+ | {
419
+ kind: 'all';
420
+ };
421
+ /** A concrete editing view — every scope except the read-only `all` aggregate. */
422
+ type ViewScope = Exclude<EditorScope, {
423
+ kind: 'all';
424
+ }>;
425
+ /**
426
+ * A selection endpoint, in either vocabulary the engine resolves.
427
+ *
428
+ * {@link DocAnchor} addresses a paragraph by its `w14:paraId`, which is what an LLM or an
429
+ * automation script can name in a payload. {@link SemanticPosition} addresses it by the
430
+ * paragraph id the painted surface and the ops already use, with a UTF-16 offset inside it.
431
+ *
432
+ * Neither subsumes the other. A paraId survives being written to a file and read back, so it
433
+ * is the one an out-of-process caller can hold; but it is OPTIONAL in the document, and
434
+ * `ParagraphSummary.paraId` says so — a paragraph the file gave no `w14:paraId` cannot be
435
+ * reached by a `DocAnchor` at all. The paragraph id reaches every paragraph.
436
+ */
437
+ type EditorPosition = DocAnchor | SemanticPosition;
438
+ /**
439
+ * A selection, in one endpoint vocabulary or the other.
440
+ *
441
+ * The two do NOT mix: a range is a pair of paraId anchors, or a semantic anchor/head pair,
442
+ * and `can()` refuses anything else with the engine's own reason. Spelled as two arms rather
443
+ * than `{ from: EditorPosition; to: EditorPosition }` for exactly that reason — the looser
444
+ * shape would type a mixed pair the engine rejects at runtime.
445
+ */
446
+ type EditorSelection = SemanticSelection | {
447
+ from: DocAnchor;
448
+ to: DocAnchor;
449
+ };
450
+ /**
451
+ * Whether a command would be accepted, and why not when it would not.
452
+ *
453
+ * The `reason` is the ENGINE's own words, which is what lets disabled chrome explain itself
454
+ * instead of guessing — see `toolbarCommandState`.
455
+ */
456
+ type CanResult = {
457
+ ok: true;
458
+ } | {
459
+ ok: false;
460
+ code: ExecErrorCode;
461
+ reason: string;
462
+ };
463
+ /**
464
+ * The engine's whole public surface: load and save, execute and query, observe and subscribe.
465
+ *
466
+ * Commands go through `can` before `exec` — the same check chrome uses to decide whether a
467
+ * control is enabled, so a button and the engine never disagree about what is possible.
468
+ *
469
+ * `snapshot()` is version-cached: the same reference until state actually moves, with
470
+ * reference-stable sub-objects, which is what makes it safe as a `useSyncExternalStore` source.
471
+ *
472
+ * @example
473
+ * ```ts
474
+ * const editor = createDocxEditor({ container });
475
+ * editor.load(bytes);
476
+ * if (editor.can({ type: 'toggleBold' }).ok) editor.exec({ type: 'toggleBold' });
477
+ * const bytesOut = await editor.save();
478
+ * ```
479
+ */
480
+ interface Editor {
481
+ /** Load a new document (DOCX bytes or a handle), replacing the current one. */
482
+ load(document: DocumentSource): void;
483
+ /** Serialize the current canonical document to DOCX bytes — on demand, never per keystroke. */
484
+ save(): Promise<ArrayBuffer>;
485
+ /** An opaque handle to the current document (identity + revision). Replaces the former
486
+ * structured `getDocument()`; the canonical state is the engine `PackageModel`, not a tree. */
487
+ getDocumentHandle(): DocumentHandle;
488
+ exec(command: EditorCommand, options?: {
489
+ scope?: EditorScope;
490
+ }): ExecResult;
491
+ /** Dry run: reports whether `exec` would apply. Never reports `changed`. */
492
+ can(command: EditorCommand, options?: {
493
+ scope?: EditorScope;
494
+ }): CanResult;
495
+ /**
496
+ * Dry run for byte commands that require {@link Editor.executeImageCommand}.
497
+ * Generic {@link Editor.can} on `insertImage` / `replaceImage` refuses with an async-path
498
+ * reason; this method answers whether async execution can proceed right now.
499
+ */
500
+ canExecuteImageCommand(command: Extract<EditorCommand, {
501
+ type: 'insertImage' | 'replaceImage';
502
+ }>, options?: {
503
+ scope?: EditorScope;
504
+ }): CanResult;
505
+ /** Insert or replace picture bytes as one package undo unit. */
506
+ executeImageCommand(command: Extract<EditorCommand, {
507
+ type: 'insertImage' | 'replaceImage';
508
+ }>): Promise<ExecResult>;
509
+ /**
510
+ * Whether a formatting command is currently APPLIED at the selection — distinct from
511
+ * `can`, which answers whether it may run.
512
+ *
513
+ * A toolbar must show that bold is on, and the repo's own guidance is that controls
514
+ * reflect live editor state rather than being static. `can` cannot answer this, and the
515
+ * legacy adapter answered it by reading a ProseMirror `EditorState` directly, which the
516
+ * greenfield architecture forbids in adapters.
517
+ *
518
+ * The derivation EXISTS for marks and alignment: `toggleMark` bold/italic/underline/
519
+ * strike answers from the snapshot's selection formatting (Word's agreement rule — true
520
+ * only when the WHOLE selection carries the mark), and `setAlignment` compares the
521
+ * command's `align` (with justify↔`both` mapped the way `exec` writes it) to the
522
+ * selection's paragraph alignment. Every other command still returns `false`: it must
523
+ * never return a value it has not actually derived from canonical state, and `false` is
524
+ * the honest answer while a derivation does not exist.
525
+ */
526
+ isActive(command: EditorCommand, options?: {
527
+ scope?: EditorScope;
528
+ }): boolean;
529
+ /** Paragraph/character styles defined by the document, for the style picker. */
530
+ getDocumentStyles(): readonly {
531
+ readonly styleId: string;
532
+ readonly name: string;
533
+ readonly type: string;
534
+ /**
535
+ * How the style looks, so a picker can show each entry in its own face rather than a
536
+ * list of identical rows. Presentation only, and already bounded for a CSS sink.
537
+ */
538
+ readonly preview: {
539
+ readonly fontFamily: string | null;
540
+ readonly fontSizePt: number | null;
541
+ readonly bold: boolean;
542
+ readonly italic: boolean;
543
+ readonly color: string | null;
544
+ };
545
+ }[];
546
+ /** Font families the document actually uses, for the font picker. */
547
+ getDocumentFonts(): readonly string[];
548
+ /**
549
+ * Every font family the editor can offer: the configured catalog (the default face,
550
+ * the Word-name families the substitution map stands in for, and host-registered
551
+ * source families) merged with {@link getDocumentFonts}. Never empty — a brand-new
552
+ * document offers the configured catalog rather than a dead picker.
553
+ */
554
+ getAvailableFonts(): readonly string[];
555
+ /**
556
+ * The document theme's ten picker colours (`a:clrScheme`) in Word's column order
557
+ * (Background 1, Text 1, Background 2, Text 2, Accent 1-6), each a six-digit hex
558
+ * without '#'. Empty when the document has no complete scheme — the picker then
559
+ * falls back to a default palette.
560
+ */
561
+ getDocumentThemeColors(): readonly {
562
+ readonly slot: string;
563
+ readonly hex: string;
564
+ }[];
565
+ /** Heading outline for the navigation panel, in document order. */
566
+ getOutline(): readonly {
567
+ readonly text: string;
568
+ readonly level: number;
569
+ readonly blockId: string;
570
+ }[];
571
+ /** Comment threads anchored in the document. */
572
+ getComments(): readonly {
573
+ readonly id: string;
574
+ readonly text: string;
575
+ readonly resolved: boolean;
576
+ }[];
577
+ /** Formatting at the current selection, for toolbar value display (font, size, colour,
578
+ * alignment, list state). `null` when nothing is selected or nothing is derivable. */
579
+ getSelectionFormatting(): {
580
+ readonly fontFamily?: string;
581
+ readonly fontSizeHalfPoints?: number;
582
+ readonly styleId?: string;
583
+ readonly alignment?: string;
584
+ readonly bold?: boolean;
585
+ readonly italic?: boolean;
586
+ readonly underline?: boolean;
587
+ } | null;
588
+ /** Find matches for a query, for the find/replace dialog. */
589
+ findMatches(query: string, options?: {
590
+ readonly matchCase?: boolean;
591
+ readonly wholeWord?: boolean;
592
+ }): readonly TextMatch[];
593
+ /**
594
+ * Move the selection to a found match — what a find dialog's next/previous do.
595
+ *
596
+ * Separate from `findMatches` because finding is a read and selecting is a write, and
597
+ * a caller may want the count without moving the caret.
598
+ */
599
+ selectMatch(match: TextMatch): ExecResult;
600
+ /** The image at the selection, for the image toolbar and transform controls. */
601
+ getSelectedImage(): SelectedImageState | null;
602
+ /** The table containing the selection, for the table toolbar. `null` outside a table. */
603
+ getSelectedTable(): {
604
+ readonly blockId: string;
605
+ readonly rowCount: number;
606
+ readonly columnCount: number;
607
+ readonly cell: {
608
+ readonly row: number;
609
+ readonly column: number;
610
+ } | null;
611
+ } | null;
612
+ /** Live rectangular cell selection, if any. `null` when the caret is not in a cell rectangle. */
613
+ getTableCellSelection(): {
614
+ readonly tableId: string;
615
+ readonly rows: {
616
+ readonly from: number;
617
+ readonly to: number;
618
+ };
619
+ readonly columns: {
620
+ readonly from: number;
621
+ readonly to: number;
622
+ };
623
+ readonly cellIds: readonly string[];
624
+ } | null;
625
+ /** Update table furniture aria labels without remounting the editor. */
626
+ setTableInteractionLabel(resolver: (key: 'table.insertRowBelow' | 'table.insertColumnRight') => string): void;
627
+ /** Section page setup — size, orientation and margins — for the page-setup dialog. */
628
+ getPageSetup(): PageSetup | null;
629
+ /** The document watermark, for the watermark dialog. */
630
+ getWatermark(): {
631
+ readonly kind: 'text' | 'image';
632
+ readonly text?: string;
633
+ } | null;
634
+ /** Header/footer editing state: which region is being edited, if any. */
635
+ getHeaderFooterState(): HeaderFooterState | null;
636
+ /** Resolved and authored note properties for the caret section — properties dialog read-model. */
637
+ getNotePropertiesState(): NotePropertiesState | null;
638
+ /** Plain-text note preview for hover chrome. */
639
+ getNotePreviewText(scopeId: string): string | null;
640
+ /** Tracked changes in the document — body AND header/footer stories. */
641
+ getTrackedChanges(): readonly {
642
+ readonly id: string;
643
+ readonly kind: string;
644
+ readonly author?: string;
645
+ /** Which story holds the change, so a consumer can group or filter by region. */
646
+ readonly story?: 'body' | 'header' | 'footer';
647
+ }[];
648
+ /**
649
+ * Every pending decision in the document, with where its card belongs.
650
+ *
651
+ * Derived from the document TREE, not from what is painted: a queue derived from laid-out
652
+ * spans empties by half the moment the reader switches to a resolved display mode, and the
653
+ * changes that vanished become unreachable from the surface meant to resolve them.
654
+ *
655
+ * `anchorY` comes from layout records. A surface must not measure painted DOM for it — that
656
+ * is a repaint behind the document and fails outright during pagination.
657
+ */
658
+ getReviewItems(query?: ReviewItemQuery): readonly ReviewItemPlacement[];
659
+ /**
660
+ * Custom-node definitions registered through `createDocxEditor({ modules })`, in
661
+ * registration order.
662
+ *
663
+ * OPAQUE to the engine — the capability package that defined them narrows them back.
664
+ * Published so chrome components can default to the registered definitions instead of
665
+ * every surface taking the same `nodes` array and drifting.
666
+ */
667
+ getCustomNodeDefinitions(): readonly unknown[];
668
+ /**
669
+ * How edits are written: directly, as suggestions, or not at all.
670
+ *
671
+ * Runtime state, unlike the construction-time `DocxEditorConfig.mode` — a reader who switches
672
+ * to Viewing and back expects the same document, not a remount.
673
+ */
674
+ getEditingMode(): DocumentEditingMode;
675
+ setEditingMode(mode: DocumentEditingMode): ExecResult;
676
+ /**
677
+ * Whether the review pane is showing its cards.
678
+ *
679
+ * Engine-owned, like zoom: the toolbar toggles it, the rail renders from it, and the
680
+ * document shifts to make room for it. Three consumers, one answer.
681
+ */
682
+ isReviewPaneOpen(): boolean;
683
+ /**
684
+ * Layout points to CSS pixels, zoom included.
685
+ *
686
+ * Published because {@link ReviewItemPlacement.anchorY} is in the engine's own unit, and a
687
+ * host that re-derived the factor would own a second copy of the points-to-pixels rule.
688
+ * The first copy drifted the moment it was written: a rail that used zoom alone put every
689
+ * card at three quarters of its true height.
690
+ */
691
+ getRenderScale(): number;
692
+ /**
693
+ * A counter that changes exactly when {@link getReviewItems} would return something new.
694
+ *
695
+ * Lets a subscriber re-derive on a real change rather than on every event.
696
+ */
697
+ getReviewRevision(): number;
698
+ /**
699
+ * Comment on the current selection.
700
+ *
701
+ * Anchored to the RETAINED range when there is one, so a compose box that took focus does
702
+ * not lose the words it is about — that is what retention exists for. Refused on a
703
+ * collapsed caret: a comment with no range has nothing to point at, and Word writes none.
704
+ */
705
+ addComment(text: string, author?: string): ExecResult;
706
+ /**
707
+ * Where a comment on the current selection would sit, in the same space as
708
+ * {@link ReviewItemPlacement.anchorY}, or null when nothing is selected.
709
+ *
710
+ * Published so a host can place an "add a comment" affordance beside the selected text
711
+ * without deriving document geometry, which is the one thing an adapter must not do.
712
+ */
713
+ getSelectionPlacement(): {
714
+ readonly anchorY: number;
715
+ readonly pageIndex: number;
716
+ } | null;
717
+ /** Card to document: select the item's range and scroll to it. `null` clears the active item. */
718
+ setActiveReviewItem(key: string | null): void;
719
+ /**
720
+ * Accept or reject the revision behind a card.
721
+ *
722
+ * Every site carrying the revision's `(id, author, date)` triple resolves in ONE transaction
723
+ * and one undo step: a tracked row insertion is `w:trPr/w:ins` on the row plus `w:cellIns`
724
+ * on each cell, and resolving them separately would leave the row half-tracked.
725
+ *
726
+ * Refused for a card whose kind the engine cannot resolve structurally, which is why
727
+ * `readOnly` is on the item — a surface should not offer the button in the first place.
728
+ */
729
+ acceptReviewItem(key: string): ExecResult;
730
+ rejectReviewItem(key: string): ExecResult;
731
+ /**
732
+ * Discard the item behind a card: the destructive half of the review verbs.
733
+ *
734
+ * On a COMMENT it deletes the thread — the `w:comment` body, the `w15:commentEx` record and
735
+ * the story's range markers, in one transaction and therefore one undo step. Word deletes a
736
+ * conversation rather than one remark, and a reply whose parent is gone has nothing left to
737
+ * answer.
738
+ *
739
+ * On a REVISION it rejects the change, which is what discarding a suggestion means: the
740
+ * proposal goes away and the document reads as it did before. Deliberately the same verb, so
741
+ * a surface can put one "remove this" affordance on every card instead of branching.
742
+ *
743
+ * Refused on a custom node's card, which is informational, and on a revision kind the engine
744
+ * cannot resolve — the same rule {@link rejectReviewItem} follows.
745
+ */
746
+ deleteReviewItem(key: string): ExecResult;
747
+ /**
748
+ * Reply to a review item.
749
+ *
750
+ * Against a comment this is a threaded reply. Against a REVISION it is a comment anchored
751
+ * over that revision's range: OOXML gives `w:ins` and `w:del` no body and no thread, so
752
+ * there is nowhere else for the text to live.
753
+ *
754
+ * The author is AMBIENT — `DocxEditorConfig.author`, the way the rest of the authored commands
755
+ * source it — and the argument overrides it for one call. `CT_Comment` makes `@w:author`
756
+ * required, so a reply with neither is refused rather than written as an empty attribute.
757
+ */
758
+ replyToReviewItem(key: string, text: string, author?: string): ExecResult;
759
+ setActiveScope(scope: ViewScope): void;
760
+ getActiveScope(): ViewScope;
761
+ query<K extends keyof EditorQueries>(query: {
762
+ type: K;
763
+ } & EditorQueries[K], options?: {
764
+ scope?: EditorScope;
765
+ }): EditorQueryResults[K];
766
+ snapshot(options?: {
767
+ scope?: EditorScope;
768
+ }): EditorSnapshot;
769
+ getTotalPages(): number;
770
+ /**
771
+ * One-based page at the caret (default), or at the centre of the mounted scroll viewport.
772
+ * Viewport mode falls back to the caret when no measurable viewport is attached.
773
+ */
774
+ getCurrentPage(mode?: 'viewport' | 'caret'): number;
775
+ /**
776
+ * Display scale of the painted pages. 1 is 100%.
777
+ *
778
+ * Zoom is ENGINE-OWNED rather than a host prop, so the toolbar's zoom control, the
779
+ * scale a host paints at, and the factor hit testing divides by cannot disagree —
780
+ * a host that scaled its own transform without telling the engine would land every
781
+ * click at the wrong content point.
782
+ */
783
+ /**
784
+ * Scroll a page or a block into view. Returns false when the target does not exist or
785
+ * the host has no scroll container — a caller can tell "not found" from "scrolled".
786
+ */
787
+ scrollToPage(pageNumber: number): boolean;
788
+ scrollToBlock(blockId: string): boolean;
789
+ getZoom(): number;
790
+ /**
791
+ * Set the display scale. Values outside a sane range are rejected rather than
792
+ * clamped silently, so a caller learns its input was refused.
793
+ */
794
+ setZoom(zoom: number): ExecResult;
795
+ /** Page boxes in stack coordinates, each with the text area the engine laid out.
796
+ * `contentBox` is the page inset by the section margin — rulers draw margin zones from
797
+ * it instead of assuming a default. The engine's margin is uniform on all four sides
798
+ * today, so this must not be presented as per-side fidelity it does not have.
799
+ *
800
+ * Empty before the first layout, which is the honest answer rather than a guessed page. */
801
+ getPageGeometry(): readonly {
802
+ index: number;
803
+ box: Rect;
804
+ contentBox: Rect;
805
+ }[];
806
+ /** Replaces the module-scope cache-invalidation calls adapters make today. */
807
+ relayout(options?: {
808
+ sync?: boolean;
809
+ }): void;
810
+ focus(scope?: EditorScope): InteractionOutcome<void>;
811
+ destroy(): void;
812
+ on<E extends keyof EditorEvents>(event: E, handler: EditorEvents[E]): Unsubscribe;
813
+ }
814
+ /**
815
+ * Open by declaration merging: extensions contribute keys from `core/plugin`.
816
+ * `exec` resolves `{ type, ... }` through the extension registry, which is
817
+ * already the production dispatch path.
818
+ */
819
+ /**
820
+ * In the editor a command targets the current selection unless told otherwise,
821
+ * and authoring is ambient — the author comes from `DocxEditorConfig`/the session,
822
+ * the way the Office JS API sources it from context — so the document layer's
823
+ * required `target` and `author` both become optional here.
824
+ */
825
+ type EditorCommandShape<T> = {
826
+ [K in keyof T]: Omit<T[K], 'target' | 'author'> & (T[K] extends {
827
+ target: infer G;
828
+ } ? {
829
+ target?: G;
830
+ } : unknown) & (T[K] extends {
831
+ author: infer A;
832
+ } ? {
833
+ author?: A;
834
+ } : unknown);
835
+ };
836
+
837
+ /**
838
+ * Narrows what `getReviewItems` returns.
839
+ *
840
+ * Both fields exist to keep the review rail cheap: filtering revision kinds is how a host hides
841
+ * structural cards it has no UI for, and `placement: false` skips the layout pass entirely when
842
+ * only metadata is wanted.
843
+ */
844
+ interface ReviewItemQuery {
845
+ readonly excludeRevisionKinds?: readonly ReviewRevisionKind[];
846
+ /** When false, skip layout geometry; metadata is unchanged and anchors are null. Default true. */
847
+ readonly placement?: boolean;
848
+ }
849
+ /**
850
+ * What every review card carries, whatever kind of decision it represents.
851
+ *
852
+ * Presentation-ready by design: author, initials, date and text are derived by the ENGINE,
853
+ * because deriving them means walking runs and reading `w15:commentsEx`. An adapter doing that
854
+ * walk would put document derivation in the host and would have to be written once per
855
+ * framework.
856
+ */
857
+ interface ReviewItemPlacementBase {
858
+ /** Stable and unique per DECISION — a revision with three ranges is one entry. */
859
+ readonly key: string;
860
+ /** The engine's own id for the comment, the revision, or the custom node. */
861
+ readonly id: string;
862
+ readonly author: string;
863
+ /** Initials for an avatar: `@w:initials` when the file carries one, else from the name. */
864
+ readonly initials: string;
865
+ /** `@w:date`, absent when the file omits it — Word does when date stamping is off. */
866
+ readonly date?: string;
867
+ /**
868
+ * The comment's body, the words the revision covers, or the custom card's detail.
869
+ *
870
+ * PLAIN TEXT, and it must be rendered as text: a `.docx` is a zip of XML an attacker
871
+ * controls end to end, so this string is untrusted and never markup.
872
+ */
873
+ readonly text: string;
874
+ /**
875
+ * Replies to this item, in document order.
876
+ *
877
+ * Comments AND revisions carry them: OOXML gives `w:ins` and `w:del` no body, so replying
878
+ * to a tracked change writes a comment over the change's own range, and the reply belongs
879
+ * inside the card for the change rather than beside it.
880
+ */
881
+ readonly replyIds: readonly string[];
882
+ /**
883
+ * True when the engine cannot resolve this kind structurally, so accept and reject must
884
+ * not be offered. A card offering a button the engine will refuse is worse than one that
885
+ * explains why it cannot.
886
+ */
887
+ readonly readOnly: boolean;
888
+ /** Document-space Y of the anchor, or null when the item has no resolvable range. */
889
+ readonly anchorY: number | null;
890
+ readonly pageIndex: number | null;
891
+ readonly isActive: boolean;
892
+ }
893
+ /** A comment thread's card. @public */
894
+ interface ReviewCommentPlacement extends ReviewItemPlacementBase {
895
+ readonly kind: 'comment';
896
+ /** Whether `w15:commentsEx` marks the thread done. */
897
+ readonly resolved: boolean;
898
+ /** The comment this replies to, absent at the top of a thread. */
899
+ readonly parentId?: string;
900
+ /**
901
+ * The REVISION this comment answers, absent unless it does.
902
+ *
903
+ * A surface listing top-level cards must skip these as well as the ones with a
904
+ * {@link parentId}: the card is rendered inside the change it answers, and a rail that
905
+ * only checked `parentId` drew the reply twice.
906
+ */
907
+ readonly parentRevisionId?: string;
908
+ readonly item: ReviewCommentItem;
909
+ }
910
+ /** A tracked change's card. @public */
911
+ interface ReviewRevisionPlacement extends ReviewItemPlacementBase {
912
+ readonly kind: 'revision';
913
+ /** Which decision this is. */
914
+ readonly revisionKind: ReviewRevisionKind;
915
+ /**
916
+ * The words a REPLACEMENT removes, when {@link revisionKind} is `'replace'`.
917
+ *
918
+ * Paired with {@link ReviewItemPlacementBase.text}, which holds the words it puts in
919
+ * their place, so a card can say `Replaced "x" with "y"` — one decision, the way Word
920
+ * presents it.
921
+ */
922
+ readonly replacedText?: string;
923
+ readonly item: ReviewRevisionItem;
924
+ }
925
+ /** A custom node's card (`defineCustomNode` with a `reviewCard` hook). @public */
926
+ interface ReviewCustomPlacement extends ReviewItemPlacementBase {
927
+ readonly kind: 'custom';
928
+ readonly item: ReviewCustomItem;
929
+ }
930
+ /**
931
+ * A DISCRIMINATED union on {@link ReviewItemPlacementBase.kind}: narrowing the kind
932
+ * narrows `item` and the kind-specific fields with it, so a consumer never writes the
933
+ * `placement.kind === 'custom' && placement.item.kind === 'custom'` double check.
934
+ */
935
+ type ReviewItemPlacement = ReviewCommentPlacement | ReviewRevisionPlacement | ReviewCustomPlacement;
936
+ /**
937
+ * How a keystroke reaches the document.
938
+ *
939
+ * `'suggesting'` changes what an edit MEANS rather than whether it is allowed: typing writes
940
+ * `w:ins` and deleting writes `w:del` over the words it would have removed, so every change
941
+ * arrives as a proposal somebody else accepts or rejects.
942
+ */
943
+ type DocumentEditingMode = 'editing' | 'suggesting' | 'viewing';
944
+ /** Which cell edges a table border command targets. */
945
+ type TableBorderTarget = 'all' | 'outside' | 'inside' | 'none' | 'top' | 'bottom' | 'left' | 'right';
946
+ /** Concrete scopes that apply a complete border spec. */
947
+ type TableBorderEdgeTarget = Exclude<TableBorderTarget, 'none'>;
948
+ /**
949
+ * Allowlisted OOXML table border line styles.
950
+ *
951
+ * Kept identical to `store/table-border-style.ts`; `table-border-style-parity.test-d.ts`
952
+ * fails if the contract and store vocabularies drift.
953
+ */
954
+ type TableBorderStyle = 'single' | 'dashed' | 'dotted' | 'double' | 'triple' | 'thick';
955
+ /** Complete border spec for {@link EditorCommands.setTableBorders}. Size is in eighths of a point. */
956
+ interface TableBorderSpec {
957
+ readonly style: TableBorderStyle;
958
+ readonly size: number;
959
+ readonly color: ColorValue;
960
+ }
961
+ /** Vertical placement of content inside selected table cells. @public */
962
+ type TableCellVerticalAlignment = 'top' | 'center' | 'bottom';
963
+ /**
964
+ * Adjacent grid columns addressed by an internal divider resize gesture.
965
+ *
966
+ * `sourceRevision` is captured from the store revision when the target is built.
967
+ * Commit MUST refuse when it does not equal the current store revision, even if an older layout
968
+ * remains published for geometry.
969
+ */
970
+ interface TableColumnDividerResizeTarget {
971
+ readonly sourceRevision: number;
972
+ readonly tableId: string;
973
+ readonly leftGridColumnId: string;
974
+ readonly rightGridColumnId: string;
975
+ readonly isHeaderRepeat: boolean;
976
+ }
977
+ /**
978
+ * Last grid column and table width addressed by an outer-right-edge resize gesture.
979
+ *
980
+ * `sourceRevision` is captured from the store revision when the target is built.
981
+ * Commit MUST refuse when it does not equal the current store revision, even if an older layout
982
+ * remains published for geometry.
983
+ */
984
+ interface TableRightEdgeResizeTarget {
985
+ readonly sourceRevision: number;
986
+ readonly tableId: string;
987
+ readonly gridColumnId: string;
988
+ readonly isHeaderRepeat: boolean;
989
+ }
990
+ /** Explicit row occurrence for furniture/context commands. */
991
+ interface TableRowOccurrenceTarget {
992
+ readonly sourceRevision: number;
993
+ readonly tableId: string;
994
+ readonly rowId: string;
995
+ readonly isHeaderRepeat: boolean;
996
+ }
997
+ /** Explicit column occurrence for furniture/context commands. */
998
+ interface TableColumnOccurrenceTarget {
999
+ readonly sourceRevision: number;
1000
+ readonly tableId: string;
1001
+ readonly gridColumnId: string;
1002
+ readonly isHeaderRepeat: boolean;
1003
+ }
1004
+ /**
1005
+ * Every command the editor accepts, keyed by name with its payload as the value.
1006
+ *
1007
+ * Extends the document-level {@link DocEdits} vocabulary with the things only a LIVE editor has:
1008
+ * selection, view state, chrome modes, header/footer and note editing. An interface rather than a
1009
+ * closed union so an extension can widen it by declaration merging.
1010
+ */
1011
+ interface EditorCommands extends EditorCommandShape<DocEdits>, EditorHeaderFooterCommands, EditorNoteCommands {
1012
+ /** Switch how edits are written. A view command: it changes no document state. */
1013
+ setEditingMode: {
1014
+ mode: DocumentEditingMode;
1015
+ };
1016
+ /**
1017
+ * Show or hide the review pane.
1018
+ *
1019
+ * A view command rather than a document edit, and a COMMAND rather than a host flag so the
1020
+ * toolbar button gets its pressed state from the same place every other button does. Both
1021
+ * adapters and any host chrome read one answer.
1022
+ */
1023
+ toggleReviewPane: Record<never, never>;
1024
+ toggleMark: {
1025
+ mark: string;
1026
+ };
1027
+ setMarkAttr: {
1028
+ mark: string;
1029
+ attr: string;
1030
+ value: unknown;
1031
+ };
1032
+ /**
1033
+ * Word's Clear All Formatting (Home > Font > the eraser).
1034
+ *
1035
+ * Takes direct CHARACTER formatting off the selected text, and resets every paragraph the
1036
+ * selection touches to the document's default paragraph style with its direct paragraph
1037
+ * properties dropped — alignment, indents, spacing, list membership. Character formatting
1038
+ * is a range and paragraph formatting is not, so a partial selection clears the text it
1039
+ * covers and still resets the paragraph it sits in, which is Word's split.
1040
+ *
1041
+ * Formatting inherited from a style is not touched: this removes what the document states
1042
+ * DIRECTLY, so the text falls back to what its style gives it rather than to nothing.
1043
+ *
1044
+ * A CHARACTER STYLE survives, and so do paragraph borders and hidden text. Those live
1045
+ * outside the property vocabulary an edit can name — a run's `w:rStyle`, `w:vanish` and
1046
+ * `w:bdr`, a paragraph's `w:pBdr` and `w:outlineLvl` — and are preserved rather than
1047
+ * dropped, which is where this stops short of Word: clearing a run that carries a
1048
+ * character style leaves that style's face on it.
1049
+ */
1050
+ clearFormatting: Record<never, never>;
1051
+ setAlignment: {
1052
+ align: 'left' | 'center' | 'right' | 'justify';
1053
+ };
1054
+ /**
1055
+ * Word's Line Spacing, on every paragraph the selection touches.
1056
+ *
1057
+ * `value` is in the unit the RULE implies, which is the unit Word's own dialog uses:
1058
+ * LINES for `multiple` (1, 1.15, 1.5, 2), points for `exact` and `atLeast`. The
1059
+ * OOXML attribute is one number meaning two different quantities depending on
1060
+ * `w:lineRule`, and a caller should not have to know which.
1061
+ */
1062
+ setLineSpacing: {
1063
+ rule: 'multiple' | 'exact' | 'atLeast';
1064
+ value: number;
1065
+ };
1066
+ /**
1067
+ * Space above and below a paragraph, in points, on every paragraph the selection
1068
+ * touches. Omitting a field leaves it as authored; `null` clears it, which is how
1069
+ * Word's "Remove space before/after paragraph" differs from setting it to zero.
1070
+ */
1071
+ setParagraphSpacing: {
1072
+ beforePt?: number | null;
1073
+ afterPt?: number | null;
1074
+ };
1075
+ /**
1076
+ * Exact paragraph indent, in twips, on every paragraph the selection touches.
1077
+ *
1078
+ * Omitting a field leaves it as authored; `null` clears it so the paragraph falls back to
1079
+ * its style, the same distinction `setParagraphSpacing` draws — a zero blocks the cascade,
1080
+ * a missing attribute does not.
1081
+ *
1082
+ * `firstLine` is ONE SIGNED offset from the left indent: negative IS the hanging indent.
1083
+ * OOXML spells it as two mutually exclusive attributes (`w:firstLine`/`w:hanging`, where
1084
+ * hanging wins per §17.3.1.12); collapsing them here means a caller cannot state a
1085
+ * contradiction, and matches the single signed value Word's own model keeps.
1086
+ */
1087
+ setIndent: {
1088
+ left?: number | null;
1089
+ right?: number | null;
1090
+ firstLine?: number | null;
1091
+ };
1092
+ toggleList: {
1093
+ kind: 'bullet' | 'ordered';
1094
+ };
1095
+ insertRow: {
1096
+ where: 'above' | 'below';
1097
+ target?: TableRowOccurrenceTarget;
1098
+ };
1099
+ insertColumn: {
1100
+ where: 'left' | 'right';
1101
+ target?: TableColumnOccurrenceTarget;
1102
+ };
1103
+ deleteRow: {
1104
+ target?: TableRowOccurrenceTarget;
1105
+ };
1106
+ deleteColumn: {
1107
+ target?: TableColumnOccurrenceTarget;
1108
+ };
1109
+ deleteTable: Record<never, never>;
1110
+ mergeCells: Record<never, never>;
1111
+ splitCell: {
1112
+ rows: number;
1113
+ cols: number;
1114
+ };
1115
+ /** Selected-cell fill. `null` clears direct fill so the table-style cascade applies again. */
1116
+ setCellFill: {
1117
+ color: ColorValue | null;
1118
+ };
1119
+ /** Vertically align content inside the selected table cells. */
1120
+ setTableCellVerticalAlignment: {
1121
+ alignment: TableCellVerticalAlignment;
1122
+ };
1123
+ toggleHeaderRow: Record<never, never>;
1124
+ /**
1125
+ * Selected-cell borders. Concrete edge scopes require a complete spec;
1126
+ * `{ scope: 'none', target }` clears only that active edge target and MUST NOT carry `spec`.
1127
+ */
1128
+ setTableBorders: {
1129
+ scope: 'none';
1130
+ target: TableBorderEdgeTarget;
1131
+ } | {
1132
+ scope: TableBorderEdgeTarget;
1133
+ spec: TableBorderSpec;
1134
+ };
1135
+ /**
1136
+ * Commit an internal column-divider resize from an explicit pointer target.
1137
+ * Widths are twips for the adjacent pair; their sum must match the pre-drag total.
1138
+ */
1139
+ commitTableColumnDividerResize: {
1140
+ target: TableColumnDividerResizeTarget;
1141
+ leftWidthTwips: number;
1142
+ rightWidthTwips: number;
1143
+ };
1144
+ /**
1145
+ * Commit an outer-right table-edge resize from an explicit pointer target.
1146
+ * Updates the last grid column and overall table width together.
1147
+ */
1148
+ commitTableRightEdgeResize: {
1149
+ target: TableRightEdgeResizeTarget;
1150
+ columnWidthTwips: number;
1151
+ tableWidthTwips: number;
1152
+ };
1153
+ /**
1154
+ * Select a table region — the whole table, the current row, or the current column.
1155
+ * Legacy's table toolbar offers all three.
1156
+ */
1157
+ selectTableRegion: {
1158
+ region: 'table' | 'row' | 'column';
1159
+ };
1160
+ /**
1161
+ * Table-level properties from the table properties dialog: preferred width and
1162
+ * its unit (`dxa`, `pct`, `auto`), and horizontal justification. `null` clears a
1163
+ * property; omitting it leaves the current value alone.
1164
+ */
1165
+ setTableProperties: {
1166
+ width?: number | null;
1167
+ widthType?: string | null;
1168
+ justification?: 'left' | 'center' | 'right' | null;
1169
+ };
1170
+ /**
1171
+ * Section-level page setup: the fields Word's Page Setup dialog and the rulers'
1172
+ * margin drags change. Twips throughout, matching OOXML. Every field is optional —
1173
+ * a margin drag sends one, the dialog sends several — and an omitted field is left
1174
+ * as it is rather than reset. `scope` is Word's "Apply to": `'document'` (the
1175
+ * default) writes every section; `'section'` writes only the section the selection
1176
+ * is in. An orientation change without explicit dimensions swaps each written
1177
+ * section's own dimensions, preserving distinct paper sizes.
1178
+ */
1179
+ setPageSetup: {
1180
+ pageWidth?: number;
1181
+ pageHeight?: number;
1182
+ marginTop?: number;
1183
+ marginRight?: number;
1184
+ marginBottom?: number;
1185
+ marginLeft?: number;
1186
+ orientation?: 'portrait' | 'landscape';
1187
+ scope?: 'document' | 'section';
1188
+ };
1189
+ /** Remove the tab stop at this position (twips) from the current paragraph. */
1190
+ removeTabMark: {
1191
+ positionTwips: number;
1192
+ };
1193
+ /**
1194
+ * Replace one found match with `text`. Addressed by {@link TextMatch} rather than a
1195
+ * `DocTarget` because that is what `findMatches` hands back, and re-deriving a target
1196
+ * from it in the caller is where an off-by-one would come from. An empty `text`
1197
+ * deletes the match, which is what a find/replace dialog means by replacing with
1198
+ * nothing.
1199
+ */
1200
+ replaceMatch: {
1201
+ match: TextMatch;
1202
+ text: string;
1203
+ };
1204
+ /**
1205
+ * Replace EVERY match of `query` in one undoable step. Separate from looping
1206
+ * `replaceMatch` because each replacement shifts the offsets of the ones after it —
1207
+ * legacy applied its edits back-to-front for exactly this reason, and that ordering
1208
+ * belongs with whoever owns the offsets.
1209
+ */
1210
+ replaceAllMatches: {
1211
+ query: string;
1212
+ text: string;
1213
+ matchCase?: boolean;
1214
+ wholeWord?: boolean;
1215
+ };
1216
+ /**
1217
+ * How the selected image sits relative to text. `inline` flows in the line; the rest
1218
+ * are `wp:anchor` variants, with `squareLeft`/`squareRight` distinguishing which side
1219
+ * text wraps on. Legacy's vocabulary, unchanged.
1220
+ */
1221
+ setImageWrapType: {
1222
+ drawingNodeId?: string;
1223
+ expectedPackageRevision?: number;
1224
+ target: 'inline' | 'square' | 'squareLeft' | 'squareRight' | 'tight' | 'through' | 'topAndBottom' | 'behind' | 'inFront';
1225
+ /** Where an inline image sat, so promoting it to an anchor keeps its place. */
1226
+ initialPositionEmu?: {
1227
+ horizontalEmu: number;
1228
+ verticalEmu: number;
1229
+ };
1230
+ };
1231
+ /** Rotate or flip the selected image. Legacy composed these into a CSS transform. */
1232
+ transformImage: {
1233
+ drawingNodeId?: string;
1234
+ expectedPackageRevision?: number;
1235
+ action: 'rotateCW' | 'rotateCCW' | 'flipH' | 'flipV';
1236
+ };
1237
+ /** Anchor position of the selected floating image, from the position dialog. */
1238
+ setImagePosition: {
1239
+ drawingNodeId?: string;
1240
+ expectedPackageRevision?: number;
1241
+ horizontalEmu?: number;
1242
+ verticalEmu?: number;
1243
+ relativeToH?: string;
1244
+ relativeToV?: string;
1245
+ };
1246
+ /** Size, alt text, crop, position, and border of the selected image, from the properties dialog. */
1247
+ setImageProperties: {
1248
+ /** Expected drawing node id captured when the dialog opened. */
1249
+ drawingNodeId?: string;
1250
+ /** Package revision captured when the dialog opened. */
1251
+ expectedPackageRevision?: number;
1252
+ /** Selection anchor captured when the dialog opened. */
1253
+ selectionParagraphId?: string;
1254
+ /** Selection offset captured when the dialog opened. */
1255
+ selectionOffset?: number;
1256
+ widthEmu?: number;
1257
+ heightEmu?: number;
1258
+ alt?: string;
1259
+ title?: string;
1260
+ description?: string;
1261
+ hyperlink?: string | null;
1262
+ crop?: ImageCropPercent;
1263
+ resetToNaturalSize?: boolean;
1264
+ wrap?: ImageWrapTarget;
1265
+ horizontalEmu?: number;
1266
+ verticalEmu?: number;
1267
+ relativeToH?: string;
1268
+ relativeToV?: string;
1269
+ borderWidthEmu?: number;
1270
+ borderColor?: ColorValue;
1271
+ };
1272
+ /** Insert a raster image at the caret as one package undo unit. */
1273
+ insertImage: {
1274
+ data: Uint8Array;
1275
+ mime: SupportedImageMime;
1276
+ widthPoints: number;
1277
+ heightPoints: number;
1278
+ expectedPackageRevision?: number;
1279
+ title?: string;
1280
+ description?: string;
1281
+ hyperlink?: string;
1282
+ };
1283
+ /** Replace the selected picture's bytes as one package undo unit. */
1284
+ replaceImage: {
1285
+ data: Uint8Array;
1286
+ mime?: SupportedImageMime;
1287
+ drawingNodeId?: string;
1288
+ expectedPackageRevision?: number;
1289
+ };
1290
+ /** Delete the selected picture drawing as one package undo unit. */
1291
+ deleteImage: {
1292
+ drawingNodeId?: string;
1293
+ expectedPackageRevision?: number;
1294
+ };
1295
+ setWatermark: {
1296
+ watermark: Watermark | null;
1297
+ };
1298
+ /** Insert a generated, hyperlink-enabled TOC for heading levels 1–3 at the selection. */
1299
+ insertToc: Record<never, never>;
1300
+ refreshToc: {
1301
+ tocId?: string;
1302
+ mode?: 'entire' | 'pageNumbers';
1303
+ };
1304
+ undo: Record<never, never>;
1305
+ redo: Record<never, never>;
1306
+ /**
1307
+ * Move the selection. Three accepted forms, and `can()` names all three when it refuses:
1308
+ * a collapsed paraId anchor, a range of two paraId anchors, or a semantic anchor/head pair.
1309
+ *
1310
+ * `{ anchor }` takes a {@link DocAnchor} rather than an {@link EditorPosition} because a
1311
+ * collapsed SEMANTIC position is spelled as a range whose anchor and head are equal, which
1312
+ * is what a caret already is.
1313
+ */
1314
+ setSelection: {
1315
+ anchor: DocAnchor;
1316
+ } | {
1317
+ range: EditorSelection;
1318
+ };
1319
+ /** Select the whole body. Word's Ctrl+A, as a command rather than only a keystroke. */
1320
+ selectAll: Record<never, never>;
1321
+ /**
1322
+ * Put the selected text on the clipboard. Reports `changed: false` — the document is
1323
+ * untouched.
1324
+ *
1325
+ * Refused at a collapsed selection: there is nothing to copy, and a live Copy row over an
1326
+ * empty selection silently no-ops.
1327
+ */
1328
+ copy: Record<never, never>;
1329
+ /**
1330
+ * Put the selected text on the clipboard and delete it. Refused at a collapsed selection,
1331
+ * and — unlike `copy` — in a read-only document.
1332
+ *
1333
+ * The clipboard write is dispatched but NOT awaited, and its failure does not fail the
1334
+ * command: the deletion has already happened by then, and reporting an edit as failed
1335
+ * because a clipboard write lost a race would be a lie about the document.
1336
+ */
1337
+ cut: Record<never, never>;
1338
+ /**
1339
+ * Insert `text` at the selection, replacing it, with newlines becoming real paragraph
1340
+ * boundaries.
1341
+ *
1342
+ * TEXT COMES IN. `exec` is synchronous and reading the clipboard is not — it prompts in
1343
+ * Chrome and is refused outright by Firefox and Safari — so an engine-owned read would
1344
+ * have to either turn every command's result into a promise or lie about this one's. The
1345
+ * caller reads the clipboard inside the click or keystroke that asked for the paste,
1346
+ * which is where the permission gesture belongs, and hands the engine a string.
1347
+ *
1348
+ * Plain text only. There is no rich lane and no `pastePlain` twin, because a second
1349
+ * command would be a second name for exactly this behavior.
1350
+ */
1351
+ paste: {
1352
+ text: string;
1353
+ };
1354
+ }
1355
+ /**
1356
+ * One command, as a discriminated union derived from {@link EditorCommands}.
1357
+ *
1358
+ * This is what `can` and `exec` take. Widening `EditorCommands` widens it automatically.
1359
+ */
1360
+ type EditorCommand = {
1361
+ [K in keyof EditorCommands]: {
1362
+ type: K;
1363
+ } & EditorCommands[K];
1364
+ }[keyof EditorCommands];
1365
+ /**
1366
+ * Every query the editor answers, keyed by name with its arguments as the value.
1367
+ *
1368
+ * Extends {@link DocQueries} with the reads that only mean something against a live, laid-out
1369
+ * document: the selection, its formatting, the table or hyperlink under the caret.
1370
+ */
1371
+ interface EditorQueries extends DocQueries {
1372
+ selection: Record<never, never>;
1373
+ selectionFormatting: Record<never, never>;
1374
+ tableContext: Record<never, never>;
1375
+ hyperlinkAt: {
1376
+ pos?: number;
1377
+ fallbackHref?: string;
1378
+ };
1379
+ selectedText: Record<never, never>;
1380
+ watermark: Record<never, never>;
1381
+ splitCellConfig: Record<never, never>;
1382
+ contentControlAt: {
1383
+ filter?: ContentControlFilter;
1384
+ };
1385
+ isInsideToc: {
1386
+ pos: number;
1387
+ };
1388
+ trackedChanges: Record<never, never>;
1389
+ }
1390
+ /**
1391
+ * One query, as a discriminated union derived from {@link EditorQueries}.
1392
+ *
1393
+ * This is what `query` takes; {@link EditorQueryResults} says what each answers.
1394
+ */
1395
+ type EditorQuery = {
1396
+ [K in keyof EditorQueries]: {
1397
+ type: K;
1398
+ } & EditorQueries[K];
1399
+ }[keyof EditorQueries];
1400
+ /** What each editor query returns. Keyed identically to `EditorQueries`. */
1401
+ interface EditorQueryResults extends DocQueryResults {
1402
+ selection: DocRange | null;
1403
+ selectionFormatting: RunFormatting | null;
1404
+ tableContext: TableContext | null;
1405
+ hyperlinkAt: HyperlinkInfo | null;
1406
+ selectedText: string;
1407
+ watermark: Watermark | null;
1408
+ splitCellConfig: {
1409
+ maxRows: number;
1410
+ maxCols: number;
1411
+ } | null;
1412
+ contentControlAt: ContentControlSummary | null;
1413
+ isInsideToc: boolean;
1414
+ trackedChanges: readonly Revision[];
1415
+ }
1416
+ /**
1417
+ * Where the caret sits inside a table: the table's shape, and the cell holding it.
1418
+ *
1419
+ * Null from the `tableContext` query when the selection is not in a table at all, which is how
1420
+ * table-only chrome decides whether to render.
1421
+ */
1422
+ interface TableContext {
1423
+ readonly rows: number;
1424
+ readonly columns: number;
1425
+ /** Zero-based, within the table. */
1426
+ readonly rowIndex: number;
1427
+ /** Zero-based, within the row. */
1428
+ readonly columnIndex: number;
1429
+ }
1430
+ /**
1431
+ * One occurrence of a search query in the document.
1432
+ *
1433
+ * Carries TWO addresses on purpose. `blockId` + `start` is the engine's own: stable
1434
+ * across edits and independent of ordering. `paragraphIndex` + `runIndex` + `runOffset`
1435
+ * is the positional one a find/replace UI needs to show and navigate results, and it is
1436
+ * derived from the same walk rather than left to the caller to reconstruct — a caller
1437
+ * guessing at run boundaries would send the selection to the wrong place.
1438
+ *
1439
+ * A match can span runs when formatting changes mid-word; the run address is where it
1440
+ * STARTS.
1441
+ */
1442
+ interface TextMatch {
1443
+ readonly blockId: string;
1444
+ /** Character offset within the paragraph's concatenated run text. */
1445
+ readonly start: number;
1446
+ readonly length: number;
1447
+ /** Ordinal among PARAGRAPHS in the body, skipping tables and other non-paragraph blocks. */
1448
+ readonly paragraphIndex: number;
1449
+ /** Index of the run the match starts in, and the offset within that run. */
1450
+ readonly runIndex: number;
1451
+ readonly runOffset: number;
1452
+ /** The matched text as it appears in the document. */
1453
+ readonly text: string;
1454
+ /**
1455
+ * Paragraph text immediately before and after the match, bounded at the derivation
1456
+ * boundary. A results list shows the match in its sentence — "…as described in this
1457
+ * **Exhi**bit A" — and nothing else in the contract can reach paragraph text, so a
1458
+ * caller would otherwise have to re-read the document to render one row.
1459
+ *
1460
+ * Optional and additive: an implementation that has not derived them omits them, and a
1461
+ * consumer treats absent as empty.
1462
+ */
1463
+ readonly contextBefore?: string;
1464
+ readonly contextAfter?: string;
1465
+ }
1466
+ /**
1467
+ * The hyperlink under a position: where it points, how far it reaches, and what Word shows on
1468
+ * hover.
1469
+ *
1470
+ * `href` has already been through `sanitizeHref` — it comes from a file, so a `javascript:` or
1471
+ * `data:` target is dropped at the parse boundary rather than here.
1472
+ */
1473
+ interface HyperlinkInfo {
1474
+ readonly href: string;
1475
+ readonly range: DocRange;
1476
+ /**
1477
+ * `w:tooltip` on the `w:hyperlink` — the text Word shows on hover, and what the
1478
+ * hyperlink dialog seeds its tooltip field with when editing an existing link.
1479
+ */
1480
+ readonly tooltip?: string;
1481
+ }
1482
+ /**
1483
+ * Section page setup — size, orientation and margins, in twips — as `getPageSetup()`
1484
+ * and `snapshot().pageSetup` report it and the `setPageSetup` command writes it. In a
1485
+ * multi-section document this is the setup of the section the SELECTION is in, which
1486
+ * is what a ruler or a dialog reflects — Word's behaviour.
1487
+ */
1488
+ interface PageSetup {
1489
+ readonly pageWidthTwips: number;
1490
+ readonly pageHeightTwips: number;
1491
+ readonly orientation: 'portrait' | 'landscape';
1492
+ readonly marginsTwips: {
1493
+ readonly top: number;
1494
+ readonly right: number;
1495
+ readonly bottom: number;
1496
+ readonly left: number;
1497
+ };
1498
+ /** Binding gutter (`w:gutter`), folded into the left margin by layout. */
1499
+ readonly gutterTwips?: number;
1500
+ }
1501
+ /**
1502
+ * A read model of the current editor state, safe to hand to framework
1503
+ * rendering. Named `EditorSnapshot` rather than `EditorState` so it never
1504
+ * collides with an editing engine's own state type.
1505
+ */
1506
+ interface EditorSnapshot {
1507
+ readonly scope: EditorScope;
1508
+ /** Whether the editor is still waiting for a document: no bytes handed over yet, and no
1509
+ * parse failure. Bytes count from the moment they are supplied, not from the moment
1510
+ * pages paint, so this stays false across a detach and remount. Safe to gate a mount
1511
+ * point on — it never depends on one existing. */
1512
+ readonly isLoading: boolean;
1513
+ readonly parseError: string | null;
1514
+ /** Whether the loaded document is being edited: a patchable document opened in edit mode. A
1515
+ * read-only document (tables/SDTs/unpreservable) or `mode: 'view'` reports false. */
1516
+ readonly editable: boolean;
1517
+ readonly zoom: number;
1518
+ readonly selection: DocRange | null;
1519
+ /**
1520
+ * Whether the selection is a CARET rather than a range. `true` when nothing is loaded.
1521
+ *
1522
+ * Separate from `selection` because `DocRange` addresses paragraphs by paraId and carries
1523
+ * no offsets, so a caret and a range inside one paragraph are the same value there.
1524
+ * Answering this from `query({ type: 'selectedText' })` builds the whole selected string
1525
+ * to produce one bit, on every tick a host's selector runs.
1526
+ */
1527
+ readonly selectionCollapsed: boolean;
1528
+ readonly formatting: RunFormatting | null;
1529
+ readonly table: TableContext | null;
1530
+ /**
1531
+ * The table of contents the last right-click landed on, or null.
1532
+ *
1533
+ * NOT caret context, unlike `table`: a right-click leaves the selection where it was, and
1534
+ * a generated TOC refuses the caret outright, so a host's context menu could otherwise
1535
+ * never tell which table of contents it was opened over. Cleared by a right-click
1536
+ * anywhere else.
1537
+ */
1538
+ readonly tocContext: {
1539
+ readonly id: string;
1540
+ } | null;
1541
+ readonly image: ImageContext | null;
1542
+ readonly page: {
1543
+ readonly current: number;
1544
+ readonly total: number;
1545
+ };
1546
+ /**
1547
+ * Whether undo/redo have anything to apply, derived from the session's history.
1548
+ * Optional and additive: an implementation that has not derived them omits them,
1549
+ * and a consumer treats absent as `false` — the honest empty answer.
1550
+ */
1551
+ readonly canUndo?: boolean;
1552
+ readonly canRedo?: boolean;
1553
+ /**
1554
+ * The section's page setup, reference-stable across ticks that did not change it.
1555
+ * Optional and additive like `canUndo`: absent means the implementation has not
1556
+ * derived it, `null` means no document is loaded.
1557
+ */
1558
+ readonly pageSetup?: PageSetup | null;
1559
+ /**
1560
+ * Whether the review pane is showing its cards.
1561
+ *
1562
+ * In the SNAPSHOT because chrome reflects it: the toolbar's comments button is pressed
1563
+ * while it is open, and a button reads its pressed state from the snapshot like every
1564
+ * other button. Kept off the snapshot it stayed pressed after the pane closed, because a
1565
+ * value-equal snapshot correctly refuses to re-render.
1566
+ */
1567
+ readonly reviewPaneOpen?: boolean;
1568
+ /**
1569
+ * Whether the document carries review content — tracked changes or comment
1570
+ * anchors — independent of any registered review module.
1571
+ *
1572
+ * The free tier's honest signal: revisions render in their final-state
1573
+ * projection there, so without this a host cannot tell its user "this
1574
+ * document has tracked changes" — the one fact the upsell hint needs.
1575
+ * Optional and additive like `canUndo`; derived cheaply from store
1576
+ * vocabulary and memoized per revision.
1577
+ */
1578
+ readonly hasReviewContent?: boolean;
1579
+ /**
1580
+ * How edits are written right now.
1581
+ *
1582
+ * In the snapshot for the same reason `reviewPaneOpen` is: the editing-mode control shows
1583
+ * it, and a control reads what it shows from the snapshot like every other control.
1584
+ */
1585
+ readonly editingMode?: DocumentEditingMode;
1586
+ /**
1587
+ * Why the last edit was refused, or null.
1588
+ *
1589
+ * The engine knew — `lastRejection` has been on the surface all along — and nothing
1590
+ * published it, so a keystroke refused because the document is open for viewing, or
1591
+ * because suggesting has no author to attribute a proposal to, looked to the user like
1592
+ * the editor had simply stopped responding.
1593
+ */
1594
+ readonly lastRejection?: string | null;
1595
+ /**
1596
+ * Document font families rendering in a substitute face: declared by the document but
1597
+ * not resolvable on this platform, not embedded in the file, and not supplied by the
1598
+ * app's font configuration. Chrome shows a compatibility notice from this the way Word
1599
+ * does. Optional and additive like `canUndo`: absent means the implementation has not
1600
+ * derived it; empty means every family resolved (or no document is loaded).
1601
+ */
1602
+ readonly fontSubstitutions?: readonly string[];
1603
+ }
1604
+ /**
1605
+ * Canonical selected-image read model shared by {@link EditorSnapshot.image} and
1606
+ * {@link Editor.getSelectedImage}.
1607
+ *
1608
+ * @public
1609
+ */
1610
+ interface SelectedImageState {
1611
+ readonly id: string;
1612
+ readonly kind: DrawingKind;
1613
+ readonly widthEmu: number;
1614
+ readonly heightEmu: number;
1615
+ /** Crop inset per edge in UI percent (0–100); OOXML stores permille (×1000). */
1616
+ readonly crop: ImageCropPercent;
1617
+ readonly rotationDegrees: number;
1618
+ readonly wrap: ImageWrapTarget;
1619
+ readonly position: DrawingPositionInput | null;
1620
+ readonly name: string;
1621
+ readonly title: string;
1622
+ readonly description: string;
1623
+ readonly hyperlink: string | null;
1624
+ readonly locks: DrawingLocks;
1625
+ readonly hidden: boolean;
1626
+ readonly resourceStatus: ImageResourceState['kind'];
1627
+ readonly intrinsic: Readonly<{
1628
+ readonly pixelWidth: number;
1629
+ readonly pixelHeight: number;
1630
+ readonly dpiX: number;
1631
+ readonly dpiY: number;
1632
+ }> | null;
1633
+ readonly canResize: boolean;
1634
+ readonly canMove: boolean;
1635
+ readonly canChangeWrap: boolean;
1636
+ readonly canCrop: boolean;
1637
+ }
1638
+ /**
1639
+ * The selected image and what may be done to it — the `imageContext` query's answer.
1640
+ *
1641
+ * An alias of `SelectedImageState`, kept as its own name because it is the query's result type
1642
+ * and chrome is written against it.
1643
+ *
1644
+ * @public
1645
+ */
1646
+ type ImageContext = SelectedImageState;
1647
+ /**
1648
+ * An error the engine raised, carrying a machine-readable `code` alongside the message.
1649
+ *
1650
+ * `code` is optional because an `Error` from deeper down (a parser, a codec) is surfaced as-is
1651
+ * rather than wrapped in a fabricated code.
1652
+ */
1653
+ interface EditorError extends Error {
1654
+ readonly code?: string;
1655
+ }
1656
+ /**
1657
+ * What `editor.on(...)` can be subscribed to, and what each handler receives.
1658
+ *
1659
+ * These are PUSH notifications and are not interchangeable with reading `snapshot()`: a snapshot
1660
+ * read cannot observe an event that was never emitted, which is why adapter behaviour is asserted
1661
+ * against these rather than against the snapshot.
1662
+ */
1663
+ interface EditorEvents {
1664
+ /** A document mutation committed, with the ids it touched. */
1665
+ change: (change: DocumentChange) => void;
1666
+ /** The selection or its derived formatting moved. */
1667
+ selectionChange: (snapshot: EditorSnapshot) => void;
1668
+ error: (error: EditorError) => void;
1669
+ }
1670
+
1671
+ export { type PageSetup as $, type AuthoredNoteNumbering as A, type EditorNoteCommands as B, type CanResult as C, type DocumentSource as D, EditorFontError as E, type FontConfiguration as F, type EditorPosition as G, type EditorQueries as H, type ImageContext as I, type EditorQuery as J, type EditorQueryResults as K, type EditorScope as L, type EditorSelection as M, type EditorSnapshot as N, type FurnitureVariant as O, type HeaderFooterSlotArgs as P, type HeaderFooterState as Q, type HyperlinkInfo as R, type SelectedImageState as S, type TableBorderEdgeTarget as T, type InteractionAffinity as U, type ViewScope as V, type InteractionOutcome as W, type InteractionOutcomeCode as X, type NoteKind as Y, type NotePropertiesSide as Z, type NotePropertiesState as _, type EditorCommand as a, type ResolvedNoteNumbering as a0, type ReviewCommentPlacement as a1, type ReviewCustomPlacement as a2, type ReviewItemPlacement as a3, type ReviewItemPlacementBase as a4, type ReviewItemQuery as a5, type ReviewRevisionPlacement as a6, type SemanticIdentity as a7, type SemanticTarget as a8, type TableBorderTarget as a9, type TableCellVerticalAlignment as aa, type TableColumnDividerResizeTarget as ab, type TableColumnOccurrenceTarget as ac, type TableRightEdgeResizeTarget as ad, type TableRowOccurrenceTarget as ae, type TextMatch as af, type ImageCropPercent as b, type ImageCropPermille as c, cropPercentFromCropPermille as d, cropPercentFromPermille as e, cropPercentFromSourceCrop as f, cropPermilleFromCropPercent as g, cropPermilleFromPercent as h, type FontSource as i, type FontSourceSubstitution as j, type FontFaceRequest as k, type Editor as l, type TableBorderSpec as m, type TableBorderStyle as n, type TableContext as o, type DocumentChange as p, type DocumentEditingMode as q, type DocumentHandle as r, sourceCropFromCropPercent as s, type EditorCommandShape as t, type EditorCommands as u, validateImageCropPercent as v, type EditorError as w, type EditorEvents as x, type EditorFontErrorCode as y, type EditorHeaderFooterCommands as z };