docxodus 6.3.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/docxodus.worker.js +13 -6
  2. package/dist/docxodus.worker.js.map +1 -1
  3. package/dist/editor.bundle.js +2143 -0
  4. package/dist/editor.d.ts +336 -0
  5. package/dist/editor.d.ts.map +1 -0
  6. package/dist/editor.js +1525 -0
  7. package/dist/editor.js.map +1 -0
  8. package/dist/index.d.ts +130 -5
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +336 -8
  11. package/dist/index.js.map +1 -1
  12. package/dist/session.bundle.js +674 -0
  13. package/dist/session.d.ts +72 -2
  14. package/dist/session.d.ts.map +1 -1
  15. package/dist/session.js +104 -0
  16. package/dist/session.js.map +1 -1
  17. package/dist/types.d.ts +326 -5
  18. package/dist/types.d.ts.map +1 -1
  19. package/dist/types.js +67 -0
  20. package/dist/types.js.map +1 -1
  21. package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm +0 -0
  22. package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm +0 -0
  23. package/dist/wasm/_framework/Docxodus.wasm +0 -0
  24. package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
  25. package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
  26. package/dist/wasm/_framework/System.Collections.Immutable.wasm +0 -0
  27. package/dist/wasm/_framework/System.Collections.NonGeneric.wasm +0 -0
  28. package/dist/wasm/_framework/System.Collections.Specialized.wasm +0 -0
  29. package/dist/wasm/_framework/System.Collections.wasm +0 -0
  30. package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm +0 -0
  31. package/dist/wasm/_framework/System.ComponentModel.TypeConverter.wasm +0 -0
  32. package/dist/wasm/_framework/System.ComponentModel.wasm +0 -0
  33. package/dist/wasm/_framework/System.Console.wasm +0 -0
  34. package/dist/wasm/_framework/System.Diagnostics.Process.wasm +0 -0
  35. package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
  36. package/dist/wasm/_framework/System.IO.Packaging.wasm +0 -0
  37. package/dist/wasm/_framework/System.IO.Pipelines.wasm +0 -0
  38. package/dist/wasm/_framework/System.Linq.Expressions.wasm +0 -0
  39. package/dist/wasm/_framework/System.Linq.wasm +0 -0
  40. package/dist/wasm/_framework/System.Memory.wasm +0 -0
  41. package/dist/wasm/_framework/System.Net.Http.wasm +0 -0
  42. package/dist/wasm/_framework/System.Net.Primitives.wasm +0 -0
  43. package/dist/wasm/_framework/System.ObjectModel.wasm +0 -0
  44. package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
  45. package/dist/wasm/_framework/System.Private.Uri.wasm +0 -0
  46. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
  47. package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
  48. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
  49. package/dist/wasm/_framework/System.Runtime.wasm +0 -0
  50. package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
  51. package/dist/wasm/_framework/System.Text.Encoding.Extensions.wasm +0 -0
  52. package/dist/wasm/_framework/System.Text.Encodings.Web.wasm +0 -0
  53. package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
  54. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
  55. package/dist/wasm/_framework/System.Threading.Thread.wasm +0 -0
  56. package/dist/wasm/_framework/System.Threading.wasm +0 -0
  57. package/dist/wasm/_framework/System.Xml.Linq.wasm +0 -0
  58. package/dist/wasm/_framework/System.Xml.ReaderWriter.wasm +0 -0
  59. package/dist/wasm/_framework/System.Xml.XDocument.wasm +0 -0
  60. package/dist/wasm/_framework/System.Xml.XPath.XDocument.wasm +0 -0
  61. package/dist/wasm/_framework/System.Xml.XPath.wasm +0 -0
  62. package/dist/wasm/_framework/System.wasm +0 -0
  63. package/dist/wasm/_framework/dotnet.boot.js +279 -0
  64. package/dist/wasm/_framework/dotnet.js +2 -2
  65. package/dist/wasm/_framework/dotnet.js.map +1 -1
  66. package/dist/wasm/_framework/dotnet.native.js +5628 -3
  67. package/dist/wasm/_framework/dotnet.native.js.symbols +5530 -3599
  68. package/dist/wasm/_framework/dotnet.native.wasm +0 -0
  69. package/dist/wasm/_framework/dotnet.runtime.js +1 -1
  70. package/dist/wasm/_framework/dotnet.runtime.js.map +1 -1
  71. package/package.json +8 -4
  72. package/dist/wasm/_framework/System.Diagnostics.DiagnosticSource.wasm +0 -0
  73. package/dist/wasm/_framework/blazor.boot.json +0 -66
  74. package/dist/wasm/_framework/supportFiles/0_runtimeconfig.bin +0 -1
@@ -0,0 +1,674 @@
1
+ "use strict";
2
+ var DocxodusSession = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/session.ts
22
+ var session_exports = {};
23
+ __export(session_exports, {
24
+ ContextBoundary: () => ContextBoundary,
25
+ DocxSession: () => DocxSession,
26
+ PlaceholderKinds: () => PlaceholderKinds,
27
+ openDocxSession: () => openDocxSession
28
+ });
29
+
30
+ // src/types.ts
31
+ var PlaceholderKinds = {
32
+ BlankFill: 1,
33
+ AlternativeClause: 2,
34
+ Instruction: 4,
35
+ All: 7
36
+ };
37
+ var DiffFormat = {
38
+ Json: 0,
39
+ Unified: 1,
40
+ SideBySide: 2
41
+ };
42
+ var ContextBoundary = {
43
+ Char: 0,
44
+ Bracket: 1,
45
+ Sentence: 2,
46
+ Comma: 3
47
+ };
48
+
49
+ // src/session.ts
50
+ var DocxSession = class {
51
+ /** @internal */
52
+ constructor(handle, wasm) {
53
+ // ─── Raw escape hatch ────────────────────────────────────────────────
54
+ this.raw = {
55
+ getXml: (anchorId) => this.wasm.RawGetXml(this.handle, anchorId),
56
+ insertXml: (anchorId, position, xml) => JSON.parse(this.wasm.RawInsertXml(this.handle, anchorId, position, xml)),
57
+ replaceXml: (anchorId, xml) => JSON.parse(this.wasm.RawReplaceXml(this.handle, anchorId, xml))
58
+ };
59
+ this.handle = handle;
60
+ this.wasm = wasm;
61
+ }
62
+ // ─── View ────────────────────────────────────────────────────────────
63
+ project() {
64
+ return JSON.parse(this.wasm.Project(this.handle));
65
+ }
66
+ /**
67
+ * Project a slice of the document keyed off an anchor — useful for showing
68
+ * one section to an LLM at a time without paying the cost of projecting the
69
+ * whole document.
70
+ *
71
+ * - `ProjectionDepth.SelfOnly` — just the addressed block (one paragraph,
72
+ * row, etc.).
73
+ * - `ProjectionDepth.Subtree` — the block + descendants (e.g. a table with
74
+ * all its rows/cells, but no following content).
75
+ * - `ProjectionDepth.SubtreeAndFollowingSiblings` (default) — for headings
76
+ * this returns the whole section (heading + content up to the next same-
77
+ * or-higher heading); for non-headings it behaves like `Subtree`.
78
+ *
79
+ * @see docs/architecture/docx_mutation_api.md
80
+ */
81
+ projectAnchor(anchorId, depth = 2 /* SubtreeAndFollowingSiblings */) {
82
+ return JSON.parse(
83
+ this.wasm.ProjectAnchor(this.handle, anchorId, depth)
84
+ );
85
+ }
86
+ /**
87
+ * Render a single block to faithful HTML from the live session — the editor's
88
+ * incremental per-block re-render after an edit. Resolves against the in-memory
89
+ * document (no Save round-trip). `anchorId` is a block anchor (`kind:scope:unid`)
90
+ * or the bare unid carried by a `data-anchor` attribute. Returns the block's HTML
91
+ * element (no `<html>`/`<head>` wrapper).
92
+ */
93
+ renderBlock(anchorId, options) {
94
+ const html = this.wasm.RenderBlockHtml(
95
+ this.handle,
96
+ anchorId,
97
+ options?.cssPrefix ?? "docx-",
98
+ options?.fabricateClasses ?? false
99
+ );
100
+ if (html.charCodeAt(0) === 123) {
101
+ const err = JSON.parse(html);
102
+ throw new Error(`renderBlock failed: ${err.error ?? "unknown error"}`);
103
+ }
104
+ return html;
105
+ }
106
+ // ─── Tier A: text CRUD ───────────────────────────────────────────────
107
+ replaceText(anchorId, markdown) {
108
+ return JSON.parse(this.wasm.ReplaceText(this.handle, anchorId, markdown));
109
+ }
110
+ deleteBlock(anchorId) {
111
+ return JSON.parse(this.wasm.DeleteBlock(this.handle, anchorId));
112
+ }
113
+ /**
114
+ * Delete every top-level block-level sibling between `fromAnchorId` (inclusive)
115
+ * and `toAnchorIdExclusive` (exclusive). Both anchors must share a direct
116
+ * parent and live in the same package part. Returns a single `EditResult`
117
+ * whose `removed` lists every anchor that was deleted.
118
+ *
119
+ * Records ONE undo snapshot — `undo()` restores the entire range.
120
+ *
121
+ * @see docs/architecture/docx_mutation_api.md#deleterange
122
+ */
123
+ deleteRange(fromAnchorId, toAnchorIdExclusive) {
124
+ return JSON.parse(this.wasm.DeleteRange(this.handle, fromAnchorId, toAnchorIdExclusive));
125
+ }
126
+ /**
127
+ * Delete a heading and everything below it up to (but not including) the next
128
+ * heading at the same or higher level. The heading anchor must have `kind === "h"`.
129
+ *
130
+ * If the target is the last heading in its parent, the section extends to the
131
+ * end of the parent (heading + everything after).
132
+ *
133
+ * @see docs/architecture/docx_mutation_api.md#deletesection
134
+ */
135
+ deleteSection(headingAnchorId) {
136
+ return JSON.parse(this.wasm.DeleteSection(this.handle, headingAnchorId));
137
+ }
138
+ // ─── Tier B: structural ──────────────────────────────────────────────
139
+ insertParagraph(anchorId, position, markdown) {
140
+ return JSON.parse(this.wasm.InsertParagraph(this.handle, anchorId, position, markdown));
141
+ }
142
+ splitParagraph(anchorId, characterOffset) {
143
+ return JSON.parse(this.wasm.SplitParagraph(this.handle, anchorId, characterOffset));
144
+ }
145
+ mergeParagraphs(firstAnchorId, secondAnchorId) {
146
+ return JSON.parse(this.wasm.MergeParagraphs(this.handle, firstAnchorId, secondAnchorId));
147
+ }
148
+ /**
149
+ * Insert an empty paragraph carrying a bottom border — an S-1-style horizontal rule —
150
+ * before/after the block. `rule` styles the line (default: a single ≈1.5pt black rule).
151
+ */
152
+ insertHorizontalRule(anchorId, position, rule) {
153
+ const ruleJson = rule ? JSON.stringify(rule) : "";
154
+ return JSON.parse(
155
+ this.wasm.InsertHorizontalRule(this.handle, anchorId, position, ruleJson)
156
+ );
157
+ }
158
+ /**
159
+ * Insert a `rows`×`cols` table before/after the block. `options` controls borders, row-major
160
+ * cell markdown, and cell alignment. The returned `EditResult.created` lists the cell-paragraph
161
+ * anchors (row-major), so each cell can then be addressed to fill/format.
162
+ */
163
+ insertTable(anchorId, position, rows, cols, options) {
164
+ const optionsJson = options ? JSON.stringify(options) : "";
165
+ return JSON.parse(
166
+ this.wasm.InsertTable(this.handle, anchorId, position, rows, cols, optionsJson)
167
+ );
168
+ }
169
+ /**
170
+ * Table row/column editing, addressed by a cell-paragraph anchor (e.g. one returned from
171
+ * {@link insertTable}'s `created`). Insert clones the reference row/column's widths and starts
172
+ * empty (`created` lists the new cell-paragraph anchors); delete of the last row/column removes
173
+ * the whole table. v1 assumes a rectangular grid (no horizontal cell merges).
174
+ */
175
+ insertTableRow(cellAnchorId, position) {
176
+ return JSON.parse(this.wasm.InsertTableRow(this.handle, cellAnchorId, position));
177
+ }
178
+ insertTableColumn(cellAnchorId, position) {
179
+ return JSON.parse(this.wasm.InsertTableColumn(this.handle, cellAnchorId, position));
180
+ }
181
+ deleteTableRow(cellAnchorId) {
182
+ return JSON.parse(this.wasm.DeleteTableRow(this.handle, cellAnchorId));
183
+ }
184
+ deleteTableColumn(cellAnchorId) {
185
+ return JSON.parse(this.wasm.DeleteTableColumn(this.handle, cellAnchorId));
186
+ }
187
+ // ─── Tier C: formatting ──────────────────────────────────────────────
188
+ applyFormat(anchorId, span, op) {
189
+ const spanJson = span ? JSON.stringify(span) : "";
190
+ return JSON.parse(this.wasm.ApplyFormat(this.handle, anchorId, spanJson, JSON.stringify(op)));
191
+ }
192
+ /**
193
+ * Convenience: find `substring` in the anchor's flat text and apply `op` to the
194
+ * first occurrence. Eliminates the offset-arithmetic trap from #138 — caller passes
195
+ * the visible text they want formatted, the WASM-side resolves it to a CharSpan.
196
+ */
197
+ applyFormatBySubstring(anchorId, substring, op) {
198
+ return JSON.parse(
199
+ this.wasm.ApplyFormatBySubstring(this.handle, anchorId, substring, JSON.stringify(op))
200
+ );
201
+ }
202
+ /**
203
+ * Convenience: apply `op` to the exact span of a {@link TextMatch} (typically from
204
+ * {@link grep}). The match's `enclosingAnchor.id` + `span` address one specific
205
+ * occurrence even when several identical needles share the same block.
206
+ */
207
+ applyFormatToMatch(match, op) {
208
+ const span = { start: match.span.start, length: match.span.length };
209
+ return this.applyFormat(match.enclosingAnchor.id, span, op);
210
+ }
211
+ setParagraphStyle(anchorId, styleId) {
212
+ return JSON.parse(this.wasm.SetParagraphStyle(this.handle, anchorId, styleId));
213
+ }
214
+ /** Set paragraph alignment / indent / page-break-before (omitted fields are left unchanged). */
215
+ setParagraphFormat(anchorId, op) {
216
+ return JSON.parse(this.wasm.SetParagraphFormat(this.handle, anchorId, JSON.stringify(op)));
217
+ }
218
+ setListLevel(anchorId, levelDelta) {
219
+ return JSON.parse(this.wasm.SetListLevel(this.handle, anchorId, levelDelta));
220
+ }
221
+ removeListMembership(anchorId) {
222
+ return JSON.parse(this.wasm.RemoveListMembership(this.handle, anchorId));
223
+ }
224
+ /** Make the paragraph a bullet/numbered list item, or remove list membership ("none"). */
225
+ applyListFormat(anchorId, kind) {
226
+ return JSON.parse(this.wasm.ApplyListFormat(this.handle, anchorId, kind));
227
+ }
228
+ // ─── Tier D: cell content ────────────────────────────────────────────
229
+ replaceCellContent(cellAnchorId, markdown) {
230
+ return JSON.parse(this.wasm.ReplaceCellContent(this.handle, cellAnchorId, markdown));
231
+ }
232
+ // ─── Search ──────────────────────────────────────────────────────────
233
+ /**
234
+ * Searches the flat text of every paragraph/heading/list-item in scope for
235
+ * matches of `pattern`, returning them in document order with the run
236
+ * fragments each match spans. Lets callers rewrite a match in place while
237
+ * preserving each fragment's formatting (bold/italic/hyperlink/etc.).
238
+ *
239
+ * `pattern` is a regular expression — use plain string equivalents wrapped
240
+ * in `^` / `$` or pass literal text escaped via a helper.
241
+ *
242
+ * @see docs/architecture/docx_mutation_api.md#grep
243
+ */
244
+ grep(pattern, options) {
245
+ return JSON.parse(this.wasm.Grep(this.handle, pattern, options ? JSON.stringify(options) : ""));
246
+ }
247
+ /**
248
+ * Like {@link grep}, but lets a single match span adjacent block-level
249
+ * siblings (paragraphs/headings/list items) under the same parent. Block
250
+ * boundaries appear in the matched text as `\n`, so `^`/`$` with the
251
+ * Multiline flag anchor at boundaries and `.` won't cross unless Singleline
252
+ * is set.
253
+ *
254
+ * Matches never cross OOXML package parts, container boundaries (body →
255
+ * table cell), or non-paragraph siblings (a table between two paragraphs
256
+ * breaks the run). Returned superset of {@link grep}: single-block matches
257
+ * still appear with one slice. Filter `slices.length > 1` for cross-block only.
258
+ *
259
+ * @see docs/architecture/docx_mutation_api.md#grepcrossblock
260
+ */
261
+ grepCrossBlock(pattern, options) {
262
+ return JSON.parse(
263
+ this.wasm.GrepCrossBlock(this.handle, pattern, options ? JSON.stringify(options) : "")
264
+ );
265
+ }
266
+ /**
267
+ * Finds every literal occurrence of `find` in the anchor's flat text and
268
+ * replaces it with `replace`, preserving the surrounding run formatting that
269
+ * the match didn't touch. Returns one `EditResult` per attempted match.
270
+ *
271
+ * Run-formatting contract: the replacement text inherits the formatting of
272
+ * the FIRST run the match spanned. Middle/trailing runs keep their `w:rPr`
273
+ * but lose the slice of text the match consumed.
274
+ *
275
+ * @see docs/architecture/docx_mutation_api.md#replacetextrange
276
+ */
277
+ replaceTextRange(anchorId, find, replace, options) {
278
+ return JSON.parse(
279
+ this.wasm.ReplaceTextRange(this.handle, anchorId, find, replace, options ? JSON.stringify(options) : "")
280
+ );
281
+ }
282
+ /**
283
+ * Replaces a specific Grep match in place — addresses the exact span by
284
+ * `enclosingAnchor.id` + `span.{start,length}`, so identical needles in the
285
+ * same paragraph (the template-fill case where five `[___]` placeholders
286
+ * each get a different value) don't collide.
287
+ */
288
+ replaceMatch(match, replace) {
289
+ return JSON.parse(
290
+ this.wasm.ReplaceTextAtSpan(this.handle, match.enclosingAnchor.id, match.span.start, match.span.length, replace)
291
+ );
292
+ }
293
+ /**
294
+ * Helper for {@link fillPlaceholders} `coalesceWhitespaceAroundEmptyFill` path —
295
+ * mirrors the .NET `ReplaceMatchCoalescingNeighbors` rules. Inspects the chars
296
+ * immediately surrounding the match via `match.contextBefore` / `contextAfter`
297
+ * (so the option requires `contextChars >= 1`, the default) and expands the
298
+ * deletion span to absorb whitespace / leading-space-before-punctuation /
299
+ * matched-brackets where the patterns match. Falls back to literal-delete
300
+ * when no neighbor pattern applies.
301
+ *
302
+ * Note: with `boundary: ContextBoundary.Bracket`, neighbor brackets are not
303
+ * captured in context, so the bracket-coalesce rule won't fire on the JS side.
304
+ * The .NET implementation reads flat text directly and handles that case;
305
+ * callers who care should leave `boundary` at the default `Char`.
306
+ */
307
+ replaceMatchCoalescingNeighbors(match) {
308
+ const fold = (c) => {
309
+ if (c === "\xA0" || c === "\u202F" || c === "\u2009") return " ";
310
+ return c;
311
+ };
312
+ const l = fold(match.contextBefore.length > 0 ? match.contextBefore[match.contextBefore.length - 1] : void 0);
313
+ const r = fold(match.contextAfter.length > 0 ? match.contextAfter[0] : void 0);
314
+ const isSpace = (c) => c === " " || c === " ";
315
+ const isClauseTerm = (c) => c === "." || c === "," || c === ";" || c === ":" || c === "!" || c === "?";
316
+ const isOpen = (c) => c === "(" || c === "[" || c === "{";
317
+ const isClose = (c) => c === ")" || c === "]" || c === "}";
318
+ let extendLeft = 0;
319
+ let extendRight = 0;
320
+ if (isSpace(l) && isSpace(r)) {
321
+ extendRight = 1;
322
+ } else if (isSpace(l) && isClauseTerm(r)) {
323
+ extendLeft = 1;
324
+ } else if (isOpen(l) && isClose(r)) {
325
+ extendLeft = 1;
326
+ extendRight = 1;
327
+ }
328
+ if (extendLeft === 0 && extendRight === 0) {
329
+ return this.replaceMatch(match, "");
330
+ }
331
+ return JSON.parse(
332
+ this.wasm.ReplaceTextAtSpan(
333
+ this.handle,
334
+ match.enclosingAnchor.id,
335
+ match.span.start - extendLeft,
336
+ match.span.length + extendLeft + extendRight,
337
+ ""
338
+ )
339
+ );
340
+ }
341
+ /**
342
+ * Replace the bracketed portion of a `TextMatch` with `newInner`, preserving any
343
+ * prefix or suffix outside the brackets. Designed for `findPlaceholders` matches
344
+ * like `$[___]` where the regex `\$?\[…\]` captures a leading `$`:
345
+ * `replaceInner(match, "0.20")` yields `$0.20`, not `0.20`.
346
+ *
347
+ * Returns `MalformedMarkdown` if the match text does not contain balanced brackets.
348
+ */
349
+ replaceInner(match, newInner) {
350
+ return JSON.parse(this.wasm.ReplaceInner(
351
+ this.handle,
352
+ match.text,
353
+ match.enclosingAnchor.id,
354
+ match.span.start,
355
+ match.span.length,
356
+ newInner
357
+ ));
358
+ }
359
+ /**
360
+ * Picker-driven template fill. For every placeholder matching `options.kinds`,
361
+ * calls `picker`; if the picker returns a non-null string, the placeholder is
362
+ * replaced (with optional `$`-prefix preservation). Iterates until no more
363
+ * placeholders match (or `maxPasses` is reached, or a pass makes zero changes)
364
+ * — handles nested brackets that surface only after the inner ones are stripped.
365
+ *
366
+ * The TypeScript implementation mirrors the .NET `DocxSession.FillPlaceholders`
367
+ * exactly.
368
+ *
369
+ * The picker is invoked synchronously by this loop on the JS side (it does
370
+ * NOT run inside the WASM module). Async pickers are not supported: returning
371
+ * a `Promise` will cause a `TypeError` at runtime inside the `$`-prefix
372
+ * preservation branch (`Promise.startsWith is not a function`). For async
373
+ * data, pre-build a lookup map before calling and have the picker read from
374
+ * it synchronously.
375
+ */
376
+ fillPlaceholders(picker, options) {
377
+ const opts = options ?? {};
378
+ const kinds = opts.kinds ?? PlaceholderKinds.All;
379
+ const scope = opts.scope ?? 1;
380
+ const maxPasses = opts.maxPasses ?? 8;
381
+ const preserveDollarPrefix = opts.preserveDollarPrefix ?? true;
382
+ const contextChars = opts.contextChars ?? 80;
383
+ const boundary = opts.boundary ?? ContextBoundary.Char;
384
+ const coalesceEmpty = opts.coalesceWhitespaceAroundEmptyFill ?? false;
385
+ if (maxPasses <= 0) {
386
+ throw new RangeError("FillOptions.maxPasses must be > 0");
387
+ }
388
+ let filled = 0;
389
+ let workPasses = 0;
390
+ const errors = [];
391
+ const unfilled = [];
392
+ const seenSkipKeys = /* @__PURE__ */ new Set();
393
+ for (let pass = 1; pass <= maxPasses; pass++) {
394
+ const placeholders = this.findPlaceholders(kinds, scope, contextChars, boundary).sort((a, b) => {
395
+ const cmp = b.match.enclosingAnchor.id.localeCompare(a.match.enclosingAnchor.id);
396
+ if (cmp !== 0) return cmp;
397
+ return b.match.span.start - a.match.span.start;
398
+ });
399
+ if (placeholders.length === 0) break;
400
+ let passChanges = 0;
401
+ for (const p of placeholders) {
402
+ const pick = picker(p);
403
+ if (pick == null) {
404
+ const key = `${p.match.enclosingAnchor.id}:${p.match.span.start}:${p.match.span.length}`;
405
+ if (!seenSkipKeys.has(key)) {
406
+ seenSkipKeys.add(key);
407
+ unfilled.push(p);
408
+ }
409
+ continue;
410
+ }
411
+ let replacement = pick;
412
+ if (preserveDollarPrefix && p.match.text.startsWith("$") && !replacement.startsWith("$")) {
413
+ replacement = "$" + replacement;
414
+ }
415
+ const r = coalesceEmpty && replacement.length === 0 ? this.replaceMatchCoalescingNeighbors(p.match) : this.replaceMatch(p.match, replacement);
416
+ if (r.success) {
417
+ filled++;
418
+ passChanges++;
419
+ } else if (r.error) {
420
+ errors.push(r.error);
421
+ }
422
+ }
423
+ if (passChanges > 0) workPasses = pass;
424
+ if (passChanges === 0) break;
425
+ }
426
+ const stillPresent = this.findPlaceholders(kinds, scope).length;
427
+ return {
428
+ filled,
429
+ skipped: unfilled.length,
430
+ stillPresent,
431
+ passes: workPasses,
432
+ unfilled,
433
+ errors
434
+ };
435
+ }
436
+ /**
437
+ * Enumerate template placeholders in the document. Thin classifier over
438
+ * {@link grep}: distinguishes `[___]` value blanks (`blank_fill`),
439
+ * `[bracketed alternative clauses]` (`alternative_clause`), and
440
+ * `[insert X]` / `[*italic hint*]` instructions (`instruction`).
441
+ *
442
+ * Combine kinds with bitwise OR: `PlaceholderKinds.BlankFill | PlaceholderKinds.Instruction`.
443
+ * Default is `PlaceholderKinds.All`; default scope is body only (1).
444
+ *
445
+ * @see docs/architecture/docx_mutation_api.md#findplaceholders
446
+ */
447
+ findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char) {
448
+ return JSON.parse(
449
+ this.wasm.FindPlaceholders(this.handle, kinds, scope, contextChars, boundary)
450
+ );
451
+ }
452
+ /**
453
+ * Returns a snapshot of edit-state introspection signals — placeholder counts,
454
+ * underscore-run leftovers, footnote/comment counts. Useful for "am I done?"
455
+ * verification at the end of an edit pipeline.
456
+ */
457
+ getEditSummary() {
458
+ return JSON.parse(this.wasm.GetEditSummary(this.handle));
459
+ }
460
+ /**
461
+ * Discoverability alias for {@link findPlaceholders}. Same return shape.
462
+ */
463
+ remainingPlaceholders(kinds = PlaceholderKinds.All) {
464
+ return JSON.parse(this.wasm.RemainingPlaceholders(this.handle, kinds));
465
+ }
466
+ getDiff(format = DiffFormat.Json) {
467
+ const raw = this.wasm.GetDiff(this.handle, format);
468
+ if (format === DiffFormat.Json) {
469
+ return JSON.parse(raw);
470
+ }
471
+ return raw;
472
+ }
473
+ // ─── Annotation-based anchor discovery (#132) ────────────────────────
474
+ /**
475
+ * Resolves an annotation's range to the block-level markdown anchors covering
476
+ * it, in document order. The bridge between Docxodus' read-side annotation API
477
+ * and the write-side session: an agent that wants to edit "the indemnification
478
+ * clause" looks the annotation up by id and gets the anchors it can hand to
479
+ * {@link replaceText} / {@link deleteBlock} / {@link raw}. Returns an empty
480
+ * list when the id is unknown or its bookmark is missing.
481
+ *
482
+ * v1 returns the enclosing block anchors — every paragraph/heading/list-item/
483
+ * cell/row/table whose subtree overlaps the bookmark range. Filter by
484
+ * `kind === "p" | "h" | "li"` when you want only text-bearing blocks.
485
+ *
486
+ * @see docs/architecture/docx_mutation_api.md#findbyannotation
487
+ */
488
+ findByAnnotation(annotationId) {
489
+ return JSON.parse(this.wasm.FindByAnnotation(this.handle, annotationId));
490
+ }
491
+ /**
492
+ * Finds every annotation whose `labelId` matches and resolves each of their
493
+ * ranges. The result is keyed by annotation id so callers can disambiguate
494
+ * when the same label is applied to multiple regions (three "WARRANTY"
495
+ * annotations on different paragraphs become three entries). Annotations
496
+ * whose bookmark resolves to no anchors are omitted from the result.
497
+ */
498
+ findByLabel(labelId) {
499
+ return JSON.parse(this.wasm.FindByLabel(this.handle, labelId));
500
+ }
501
+ /**
502
+ * Resolves any bookmark in the main document part (Docxodus-managed or
503
+ * user-authored) to the block-level anchors covering its range, in document
504
+ * order. Empty when the bookmark name is unknown. Use this for raw bookmark
505
+ * names that didn't come from the annotation system.
506
+ */
507
+ findByBookmark(bookmarkName) {
508
+ return JSON.parse(this.wasm.FindByBookmark(this.handle, bookmarkName));
509
+ }
510
+ // ─── Text/kind-based anchor discovery (#171) ─────────────────────────
511
+ /**
512
+ * True when `anchorId` resolves to a live element in the current session.
513
+ * Cheap existence probe — use it to guard an anchor obtained from an earlier
514
+ * projection before handing it to a mutation (anchors can be invalidated by
515
+ * intervening edits; see the anchor lifecycle table in the mutation docs).
516
+ */
517
+ exists(anchorId) {
518
+ return this.wasm.Exists(this.handle, anchorId);
519
+ }
520
+ /**
521
+ * Find the first block-level anchor (in document order) whose flat text
522
+ * contains `needle`, or `null` when nothing matches. `options` tune case /
523
+ * whitespace handling and narrow the search by kind or scope. For all
524
+ * matches use {@link findAllByText}.
525
+ */
526
+ findByText(needle, options) {
527
+ return JSON.parse(
528
+ this.wasm.FindByText(this.handle, needle, options ? JSON.stringify(options) : "")
529
+ );
530
+ }
531
+ /**
532
+ * Like {@link findByText} but returns every matching anchor in document
533
+ * order (empty when nothing matches).
534
+ */
535
+ findAllByText(needle, options) {
536
+ return JSON.parse(
537
+ this.wasm.FindAllByText(this.handle, needle, options ? JSON.stringify(options) : "")
538
+ );
539
+ }
540
+ /**
541
+ * Find every block-level anchor whose flat text matches the regular
542
+ * expression `pattern`, in document order. `regexOptions` uses the numeric
543
+ * layout of .NET `RegexOptions` (e.g. `1` = IgnoreCase); `options` is the
544
+ * same shape as {@link findByText} (its `ignoreCase` composes with the regex
545
+ * flag). Defaults to `regexOptions = 0` (none).
546
+ */
547
+ findByRegex(pattern, regexOptions = 0, options) {
548
+ return JSON.parse(
549
+ this.wasm.FindByRegex(this.handle, pattern, regexOptions, options ? JSON.stringify(options) : "")
550
+ );
551
+ }
552
+ /**
553
+ * Return every anchor of the given `kind` (`"p"`, `"h"`, `"li"`, `"tbl"`,
554
+ * `"row"`, `"cell"`, …), in document order. Reads the projection's anchor
555
+ * index directly — no text scan. Pass `scope` (e.g. `"body"`) to restrict to
556
+ * a single part; omit it to span all scopes.
557
+ */
558
+ findByKind(kind, scope) {
559
+ return JSON.parse(
560
+ this.wasm.FindByKind(this.handle, kind, scope ?? "")
561
+ );
562
+ }
563
+ /**
564
+ * Look up a single anchor's preview info — `{ id, kind, scope, textPreview }`.
565
+ * Returns null when the anchor id is unknown.
566
+ *
567
+ * For iterating many anchors at once, prefer reading `textPreview` directly
568
+ * off the {@link MarkdownProjection.anchorIndex} entries (cheaper — no extra
569
+ * WASM round trip), or use {@link getAnchorInfos} for batched lookups.
570
+ */
571
+ getAnchorInfo(anchorId) {
572
+ const raw = this.wasm.GetAnchorInfo(this.handle, anchorId);
573
+ return JSON.parse(raw);
574
+ }
575
+ /**
576
+ * Bulk variant of {@link getAnchorInfo}: takes an array of anchor ids,
577
+ * returns a record where each unknown id maps to `null`.
578
+ */
579
+ getAnchorInfos(anchorIds) {
580
+ const raw = this.wasm.GetAnchorInfos(this.handle, JSON.stringify(anchorIds));
581
+ return JSON.parse(raw);
582
+ }
583
+ /**
584
+ * Resolve block-level metadata (style id+name, outline level, list membership,
585
+ * formatting probe) for an anchor. Returns null when the anchor doesn't exist.
586
+ */
587
+ getBlockMetadata(anchorId) {
588
+ const raw = this.wasm.GetBlockMetadata(this.handle, anchorId);
589
+ return JSON.parse(raw);
590
+ }
591
+ /**
592
+ * Bulk variant of {@link getBlockMetadata}. Unknown ids map to null;
593
+ * duplicates are deduped.
594
+ */
595
+ getBlockMetadatas(anchorIds) {
596
+ const raw = this.wasm.GetBlockMetadatas(this.handle, JSON.stringify(anchorIds));
597
+ return JSON.parse(raw);
598
+ }
599
+ /**
600
+ * Resolve the numbering facts for a list-item paragraph; returns null when
601
+ * the anchor has no w:numPr.
602
+ */
603
+ getListMembership(anchorId) {
604
+ const raw = this.wasm.GetListMembership(this.handle, anchorId);
605
+ return JSON.parse(raw);
606
+ }
607
+ /**
608
+ * Resolve page-layout info for the w:sectPr that governs an anchor.
609
+ * Returns null for anchors outside the body part.
610
+ */
611
+ getSectionInfo(anchorId) {
612
+ const raw = this.wasm.GetSectionInfo(this.handle, anchorId);
613
+ return JSON.parse(raw);
614
+ }
615
+ /**
616
+ * Enumerates every annotation persisted in the document. Lets an agent prime
617
+ * itself with "here are the labeled regions you can target" before committing
618
+ * to a specific id.
619
+ */
620
+ listAnnotations() {
621
+ return JSON.parse(this.wasm.ListAnnotations(this.handle));
622
+ }
623
+ // ─── Annotation write surface ────────────────────────────────────────
624
+ /**
625
+ * Annotate a range inside `anchorId`. When `span` is `null`/`undefined`
626
+ * the annotation wraps every inline run of the block. When
627
+ * `annotation.id` is `undefined`, a 16-char hex id is auto-generated and
628
+ * returned in `EditResult.annotationId`.
629
+ */
630
+ addAnnotation(anchorId, span, annotation) {
631
+ const spanJson = span ? JSON.stringify(span) : "";
632
+ return JSON.parse(
633
+ this.wasm.AddAnnotation(this.handle, anchorId, spanJson, JSON.stringify(annotation))
634
+ );
635
+ }
636
+ removeAnnotation(annotationId) {
637
+ return JSON.parse(this.wasm.SessionRemoveAnnotation(this.handle, annotationId));
638
+ }
639
+ updateAnnotation(annotationId, update) {
640
+ return JSON.parse(
641
+ this.wasm.UpdateAnnotation(this.handle, annotationId, JSON.stringify(update))
642
+ );
643
+ }
644
+ moveAnnotation(annotationId, newAnchorId, newSpan) {
645
+ const spanJson = newSpan ? JSON.stringify(newSpan) : "";
646
+ return JSON.parse(
647
+ this.wasm.MoveAnnotation(this.handle, annotationId, newAnchorId, spanJson)
648
+ );
649
+ }
650
+ // ─── Lifecycle ───────────────────────────────────────────────────────
651
+ undo() {
652
+ return this.wasm.Undo(this.handle);
653
+ }
654
+ redo() {
655
+ return this.wasm.Redo(this.handle);
656
+ }
657
+ save() {
658
+ return this.wasm.Save(this.handle);
659
+ }
660
+ close() {
661
+ this.wasm.CloseSession(this.handle);
662
+ }
663
+ // TypeScript 5.2+ disposable protocol
664
+ [Symbol.dispose]() {
665
+ this.close();
666
+ }
667
+ };
668
+ function openDocxSession(bytes, wasmExports, settings) {
669
+ const bridge = wasmExports.DocxSessionBridge;
670
+ const handle = bridge.OpenSession(bytes, settings ? JSON.stringify(settings) : "");
671
+ return new DocxSession(handle, bridge);
672
+ }
673
+ return __toCommonJS(session_exports);
674
+ })();