@docx-editor.dev/core 2.30.0 → 2.32.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 (133) hide show
  1. package/README.md +24 -0
  2. package/dist/automation.cjs +1 -1
  3. package/dist/automation.d.ts +18 -27
  4. package/dist/automation.js +1 -1
  5. package/dist/binding.cjs +1 -1
  6. package/dist/binding.d.ts +17 -16
  7. package/dist/binding.js +1 -1
  8. package/dist/bookmark-names-DDuAra_V.d.ts +37 -0
  9. package/dist/{chrome-print-BM3Js-6X.d.ts → chrome-print-CznJ0ucN.d.ts} +361 -41
  10. package/dist/chunk-25EXMBV2.cjs +5 -0
  11. package/dist/chunk-2CWQY27B.cjs +1 -0
  12. package/dist/chunk-3IEXDEFH.cjs +7 -0
  13. package/dist/chunk-65XBN67F.cjs +2 -0
  14. package/dist/chunk-6NVZNB7O.cjs +3 -0
  15. package/dist/chunk-6UDJUI3W.cjs +2 -0
  16. package/dist/chunk-ACLR4DMK.js +1 -0
  17. package/dist/{chunk-RJCQGJR7.js → chunk-B2BEELUE.js} +1 -1
  18. package/dist/chunk-DDFHKQFU.js +30 -0
  19. package/dist/chunk-E4IAO4CY.cjs +2 -0
  20. package/dist/chunk-ETDZFNMI.js +2 -0
  21. package/dist/chunk-FDSL5OJU.cjs +30 -0
  22. package/dist/chunk-FS7CMYLT.js +4 -0
  23. package/dist/chunk-GDS24SZN.js +30 -0
  24. package/dist/chunk-GIY6NT2X.js +5 -0
  25. package/dist/chunk-GWM3AKUD.js +18 -0
  26. package/dist/chunk-HDOXACHP.js +2 -0
  27. package/dist/chunk-IILEENID.js +1 -0
  28. package/dist/chunk-L5TYBFDB.js +1 -0
  29. package/dist/chunk-MNF2K7NN.cjs +30 -0
  30. package/dist/chunk-O324RB6R.cjs +1 -0
  31. package/dist/chunk-OKMEMSKB.cjs +1 -0
  32. package/dist/chunk-QXJKYNYZ.js +1 -0
  33. package/dist/chunk-RALTVLB6.js +2 -0
  34. package/dist/chunk-SRVKFUFI.js +3 -0
  35. package/dist/chunk-TELQ52LG.cjs +1 -0
  36. package/dist/chunk-TJHE5EET.cjs +1 -0
  37. package/dist/chunk-UMMRURPE.js +7 -0
  38. package/dist/chunk-WN7ICTA5.js +1 -0
  39. package/dist/{chunk-FLBEJC6X.cjs → chunk-XJYZVGQR.cjs} +1 -1
  40. package/dist/chunk-ZGTIE3QJ.cjs +4 -0
  41. package/dist/chunk-ZOZI27AW.cjs +18 -0
  42. package/dist/collaboration/replication.cjs +1 -1
  43. package/dist/collaboration/replication.d.ts +20 -5
  44. package/dist/collaboration/replication.js +1 -1
  45. package/dist/collaboration.cjs +1 -1
  46. package/dist/collaboration.d.ts +5 -6
  47. package/dist/collaboration.js +1 -1
  48. package/dist/contracts/document.d.ts +3 -3
  49. package/dist/contracts/editor.d.ts +13 -10
  50. package/dist/contracts/interaction.d.ts +2 -2
  51. package/dist/contracts/modules.d.ts +15 -13
  52. package/dist/contracts/types.d.ts +135 -2
  53. package/dist/{custom-node-writes-0v0Kgw1T.d.ts → custom-node-writes-CLJKGD9t.d.ts} +7 -2
  54. package/dist/{diagnostic-context-CcAO4q1s.d.ts → diagnostic-context-BtDUY3v2.d.ts} +3 -2
  55. package/dist/{diagnostic-hints-2Bke6H_w.d.ts → diagnostic-hints-DK8-V0fk.d.ts} +2 -2
  56. package/dist/{document-protection-8eyMWIJ0.d.ts → document-protection-NaniLHWF.d.ts} +1 -1
  57. package/dist/{editor-DaEJd2so.d.ts → editor-DZwOTmlR.d.ts} +386 -279
  58. package/dist/editor-fonts-D_-vjuJi.d.ts +53 -0
  59. package/dist/{editor-scope-7o9xweiJ.d.ts → editor-scope-B3L-1ALc.d.ts} +57 -4
  60. package/dist/editor.cjs +1 -1
  61. package/dist/editor.css +6 -4
  62. package/dist/editor.d.ts +646 -31
  63. package/dist/editor.js +1 -1
  64. package/dist/export.cjs +2 -2
  65. package/dist/export.d.ts +9 -16
  66. package/dist/export.js +2 -2
  67. package/dist/{failure-NS2lsya4.d.ts → failure-6KGFrDO9.d.ts} +1 -1
  68. package/dist/{font-resolver-CL9BVKZo.d.ts → font-resolver-BlQNGdvR.d.ts} +1 -1
  69. package/dist/{headless-document-view-DZQN8Bnx.d.ts → headless-document-view-Bcz9_Qva.d.ts} +115 -4
  70. package/dist/index.cjs +1 -1
  71. package/dist/index.d.ts +27 -26
  72. package/dist/index.js +1 -1
  73. package/dist/{layout-shaping-cEnVrhWg.d.ts → layout-shaping-E5J4Z1Gf.d.ts} +1 -1
  74. package/dist/layout.cjs +1 -1
  75. package/dist/layout.d.ts +95 -23
  76. package/dist/layout.js +1 -1
  77. package/dist/{note-properties-9FH8zAeG.d.ts → note-properties-Ds5R2_3F.d.ts} +1 -1
  78. package/dist/output.cjs +1 -1
  79. package/dist/output.d.ts +24 -10
  80. package/dist/output.js +1 -1
  81. package/dist/{protocol-CZ1uUlPO.d.ts → protocol-DKa4qt7F.d.ts} +167 -5
  82. package/dist/{review-paragraph-order-CqVelc5A.d.ts → review-paragraph-order-Dc17w_6v.d.ts} +1 -1
  83. package/dist/revision-projection-Cn6rZJ7H.d.ts +258 -0
  84. package/dist/{semantic-cell-selection-Dr9yxFZt.d.ts → semantic-cell-selection-BaUR0iJ4.d.ts} +2 -2
  85. package/dist/{semantic-interaction-Dc3pf3hr.d.ts → semantic-interaction-CJoHLoOl.d.ts} +1 -1
  86. package/dist/{semantic-paint-Bz3FBAoH.d.ts → semantic-paint-BPw_n0li.d.ts} +7 -4
  87. package/dist/{semantic-record-queries-Bx0pl6kL.d.ts → semantic-record-queries-C0rJ3sB4.d.ts} +1 -1
  88. package/dist/{semantic-records-CjcF26ZL.d.ts → semantic-records-BvN-WWst.d.ts} +365 -512
  89. package/dist/store.cjs +1 -1
  90. package/dist/store.d.ts +47 -22
  91. package/dist/store.js +1 -1
  92. package/dist/{types-barrel-CA0xB8kH.d.ts → tree-op-types-C_S2niXW.d.ts} +1239 -747
  93. package/dist/{tree-package-store-DKhdEVym.d.ts → tree-package-store-CRHvMyNj.d.ts} +676 -646
  94. package/dist/{tree-session-contract-CwnTEVLF.d.ts → tree-session-contract-D1awo_dA.d.ts} +26 -13
  95. package/dist/unsupported-elements-CVos5cqe.d.ts +23 -0
  96. package/docs/accept-reject-without-scrolling.md +95 -0
  97. package/docs/comment-on-found-text.md +72 -0
  98. package/docs/custom-review-cards.md +178 -0
  99. package/docs/highlight-ranges.md +48 -0
  100. package/docs/index.md +36 -0
  101. package/docs/propose-rewrite.md +247 -0
  102. package/docs/tracked-replacement.md +68 -0
  103. package/package.json +3 -2
  104. package/dist/chunk-2PV5L5NJ.js +0 -5
  105. package/dist/chunk-5OTS2EJ7.js +0 -7
  106. package/dist/chunk-5P4223J4.cjs +0 -27
  107. package/dist/chunk-5RRAZMBU.cjs +0 -1
  108. package/dist/chunk-5SPITS5F.js +0 -18
  109. package/dist/chunk-6JIJOZDW.js +0 -1
  110. package/dist/chunk-7ATNKARV.js +0 -2
  111. package/dist/chunk-BGCUTKWE.cjs +0 -5
  112. package/dist/chunk-BTQKU64Q.cjs +0 -1
  113. package/dist/chunk-EBCXSH2W.js +0 -3
  114. package/dist/chunk-ERECKTIR.cjs +0 -2
  115. package/dist/chunk-HYASLE2G.cjs +0 -1
  116. package/dist/chunk-JC47NRJC.cjs +0 -3
  117. package/dist/chunk-KJRTNY2Q.cjs +0 -2
  118. package/dist/chunk-NKPUH3QJ.js +0 -1
  119. package/dist/chunk-PF5NJWS3.js +0 -2
  120. package/dist/chunk-PWTRJSES.cjs +0 -18
  121. package/dist/chunk-QFKGI242.cjs +0 -30
  122. package/dist/chunk-RPPV4S6T.cjs +0 -4
  123. package/dist/chunk-T32MGBJS.js +0 -1
  124. package/dist/chunk-UM26GH5B.js +0 -30
  125. package/dist/chunk-VRCZBFLH.js +0 -27
  126. package/dist/chunk-WF7JZ755.cjs +0 -1
  127. package/dist/chunk-WK5X2MMA.cjs +0 -7
  128. package/dist/chunk-WPDD2Y32.js +0 -2
  129. package/dist/chunk-YBMWBXST.cjs +0 -2
  130. package/dist/chunk-YZGUBCHP.js +0 -1
  131. package/dist/chunk-ZOS2PM4A.js +0 -4
  132. package/dist/image-resources-Bipz8toD.d.ts +0 -211
  133. package/dist/omml-equation-DP2WpUbK.d.ts +0 -97
@@ -1,11 +1,10 @@
1
- import { S as StoryScope, g as SelectionMark, h as TransactOptions, b as StoryTargetRejection, e as TreeModelChange, H as HeaderFooterParts, a as HeaderFooterSectionResolution, I as InsertImageInput, i as ImageIntentResult, R as ReplaceImageOptions, A as ApplyImagePropertiesInput, j as FragmentPasteInput, k as FragmentPasteResult } from './tree-package-store-DKhdEVym.js';
2
- import { ag as TreeDocOp, O as OoxmlPart, N as OoxmlNode, Q as OoxmlPackage, ah as OoxmlElement, a as ReviewItem, ax as RevisionAttributionInput } from './types-barrel-CA0xB8kH.js';
3
- import { S as SupportedImageMime, c as ImageDecodePort } from './image-resources-Bipz8toD.js';
4
- import { D as DocumentTrackingSettings, I as InsertCustomNodeWrite, C as CustomNodeWriteResult, a as CustomNodeSweepOutcome, E as EmbeddedFont, B as BookmarkIndex, L as ListKind } from './custom-node-writes-0v0Kgw1T.js';
5
- import { H as HeadlessDocumentView, D as DocumentProperties } from './headless-document-view-DZQN8Bnx.js';
1
+ import { S as StoryScope, g as SelectionMark, h as TransactOptions, d as HistoryGroup, a as StoryTargetRejection, e as TreeModelChange, H as HeaderFooterParts, b as HeaderFooterSectionResolution, I as InsertImageInput, i as ImageIntentResult, R as ReplaceImageOptions, A as ApplyImagePropertiesInput, j as FragmentPasteInput, k as FragmentPasteResult } from './tree-package-store-CRHvMyNj.js';
2
+ import { au as TreeDocOp, O as OoxmlPart, a4 as OoxmlNode, a5 as OoxmlPackage, av as OoxmlElement, a as ReviewItem, an as SupportedImageMime, ad as ImageDecodePort, aF as RevisionAttributionInput } from './tree-op-types-C_S2niXW.js';
3
+ import { D as DocumentTrackingSettings, I as InsertCustomNodeWrite, C as CustomNodeWriteResult, a as CustomNodeSweepOutcome, E as EmbeddedFont, B as BookmarkIndex, L as ListKind } from './custom-node-writes-CLJKGD9t.js';
4
+ import { H as HeadlessDocumentView, D as DocumentProperties } from './headless-document-view-Bcz9_Qva.js';
6
5
  import { Node } from 'prosemirror-model';
7
6
  import { CollaborationDocumentPort } from './collaboration/replication.js';
8
- import { V as ViewScope } from './editor-scope-7o9xweiJ.js';
7
+ import { V as ViewScope } from './editor-scope-B3L-1ALc.js';
9
8
 
10
9
  /**
11
10
  * Why an edited projection could not be mapped back into tree ops.
@@ -259,7 +258,11 @@ interface TreeApplyResult {
259
258
  readonly reason?: TreeBindingRejection | StoryTargetRejection | string;
260
259
  }
261
260
  /** Optional collaboration attribution and history grouping for a direct tree-op transaction. */
262
- type TreeApplyOptions = Pick<TransactOptions, 'origin' | 'actorId' | 'operationId' | 'recordsHistory' | 'historyGroup'> & {
261
+ type TreeApplyOptions = Pick<TransactOptions, 'origin' | 'actorId' | 'operationId' | 'recordsHistory'> & {
262
+ /** The store token of the commit's history group, as `commitReviewOps` hands it over. */
263
+ readonly historyToken?: HistoryGroup;
264
+ /** @deprecated Use `historyToken`, the same store token under its one name. */
265
+ readonly historyGroup?: HistoryGroup;
263
266
  /** Package resources committed in the same transaction as the tree operations. */
264
267
  readonly packageEdits?: readonly ((pkg: OoxmlPackage) => OoxmlPackage)[];
265
268
  /** Canonical operations on related package parts, committed in the same undo unit. */
@@ -268,6 +271,11 @@ type TreeApplyOptions = Pick<TransactOptions, 'origin' | 'actorId' | 'operationI
268
271
  readonly ops: readonly TreeDocOp[];
269
272
  }[];
270
273
  };
274
+ /** History grouping for a comment or custom-node write: frames of one group share one undo step. */
275
+ interface PackageWriteOptions {
276
+ /** The store token of the commit's history group, as `commitReviewOps` hands it over. */
277
+ readonly historyToken?: HistoryGroup;
278
+ }
271
279
  /**
272
280
  * One open document: the canonical tree, and the only write path into it.
273
281
  *
@@ -359,6 +367,11 @@ interface TreeDocxSessionView extends HeadlessDocumentView {
359
367
  collaborationPort(documentId: string): CollaborationDocumentPort;
360
368
  /** Serialize the whole package back to DOCX bytes. */
361
369
  save(): Uint8Array;
370
+ /**
371
+ * {@link save} in steps a caller can pause between. The package is captured at the call, so
372
+ * the bytes describe the document as it was then, whatever happens between steps.
373
+ */
374
+ saveSteps(): Generator<void, Uint8Array>;
362
375
  /**
363
376
  * The resolved header/footer parts of the section, by variant (phase 2).
364
377
  *
@@ -506,7 +519,7 @@ interface TreeDocxSessionView extends HeadlessDocumentView {
506
519
  /** ISO-8601. Omitted writes no `@w:date`, because inventing one is a content change. */
507
520
  date?: string, scope?: StoryScope,
508
521
  /** Binds id allocation to one actor, so two peers cannot mint the same comment id. */
509
- actorId?: string): string | null;
522
+ actorId?: string, options?: PackageWriteOptions): string | null;
510
523
  /**
511
524
  * Resolve a comment thread, or reopen it. False when the document holds no such comment.
512
525
  *
@@ -514,7 +527,7 @@ interface TreeDocxSessionView extends HeadlessDocumentView {
514
527
  * `commentsExtended.xml`, a part a document with no thread does not have, so the state and the
515
528
  * part that records it commit together.
516
529
  */
517
- setCommentResolved(commentId: string, resolved: boolean): boolean;
530
+ setCommentResolved(commentId: string, resolved: boolean, options?: PackageWriteOptions): boolean;
518
531
  /**
519
532
  * Delete a comment thread outright — body, thread state and story markers.
520
533
  *
@@ -523,7 +536,7 @@ interface TreeDocxSessionView extends HeadlessDocumentView {
523
536
  * shared notes part. False when the document holds no such comment, or when the removal was
524
537
  * refused.
525
538
  */
526
- deleteComment(commentId: string, scope?: StoryScope, noteId?: number): boolean;
539
+ deleteComment(commentId: string, scope?: StoryScope, noteId?: number, options?: PackageWriteOptions): boolean;
527
540
  /**
528
541
  * Delete several comment objects as one package transaction and one undo unit.
529
542
  *
@@ -533,7 +546,7 @@ interface TreeDocxSessionView extends HeadlessDocumentView {
533
546
  deleteComments(comments: readonly {
534
547
  readonly commentId: string;
535
548
  readonly parentCommentId?: string;
536
- }[], scope?: StoryScope, noteId?: number): boolean;
549
+ }[], scope?: StoryScope, noteId?: number, options?: PackageWriteOptions): boolean;
537
550
  /**
538
551
  * Insert a custom node, with the payload it carries, as ONE transaction.
539
552
  *
@@ -545,7 +558,7 @@ interface TreeDocxSessionView extends HeadlessDocumentView {
545
558
  * to live in its `w:tag` needs.
546
559
  */
547
560
  /** `scope` names the story the paragraph is in, and defaults to the body. */
548
- insertCustomNode(write: InsertCustomNodeWrite, scope?: StoryScope): CustomNodeWriteResult;
561
+ insertCustomNode(write: InsertCustomNodeWrite, scope?: StoryScope, options?: PackageWriteOptions): CustomNodeWriteResult;
549
562
  /**
550
563
  * Remove a custom node and, in the same transaction, the payload it bound.
551
564
  *
@@ -663,4 +676,4 @@ interface TreeDocxSessionView extends HeadlessDocumentView {
663
676
  }
664
677
 
665
678
  export { bodyParagraphs as d, docToTreeOps as e, partHasNode as p, reconcileDoc as r, treeToDoc as t };
666
- export type { DocumentStyleEntry as D, MapResult as M, TreeDocxSessionView as T, TreeApplyResult as a, TreeApplyOptions as b, TreeBindingRejection as c, DocumentThemeColorEntry as f };
679
+ export type { DocumentStyleEntry as D, MapResult as M, PackageWriteOptions as P, TreeDocxSessionView as T, TreeApplyResult as a, TreeApplyOptions as b, TreeBindingRejection as c, DocumentThemeColorEntry as f };
@@ -0,0 +1,23 @@
1
+ import { U as UnsupportedElement } from './semantic-records-BvN-WWst.js';
2
+
3
+ /** A component mount point and its built-in fallback. @public */
4
+ interface UnsupportedElementHost {
5
+ /** Stable identity for one painted occurrence. */
6
+ readonly key: string;
7
+ readonly element: HTMLElement;
8
+ readonly fallback: HTMLElement;
9
+ readonly slot: HTMLElement;
10
+ readonly unsupported: UnsupportedElement;
11
+ /** Hide the default and expose custom controls to assistive technology. Restore with false. */
12
+ setCustomized(custom: boolean): void;
13
+ }
14
+ /**
15
+ * Observe placeholder mounts across repaint, pagination, and document replacement.
16
+ * The returned function disconnects the observer. Component mutations do not publish
17
+ * another list unless the engine's mount points change.
18
+ * @public
19
+ */
20
+ declare function observeUnsupportedElements(container: HTMLElement, listener: (hosts: readonly UnsupportedElementHost[]) => void): () => void;
21
+
22
+ export { observeUnsupportedElements as o };
23
+ export type { UnsupportedElementHost as U };
@@ -0,0 +1,95 @@
1
+ <!-- Generated from docs/site/content/recipes by scripts/sync-agent-docs.mjs. Edit the source and run `bun run docs:agent`. -->
2
+
3
+ # Accept or reject without scrolling
4
+
5
+ When your own list drives the scroll, open an item with `useReview().setActive(key, { reveal: false })`. The caret moves to the item, and the page stays where it is. Then resolve the item with `accept(item)` or `reject(item)`. Review writes such as accept, reject, and comment never scroll the page.
6
+
7
+ **React**
8
+
9
+ ```tsx
10
+ import { useReview, type ReviewItemView } from '@docx-editor.dev/pro/react';
11
+
12
+ export function ChangeRow({ item }: { item: ReviewItemView }) {
13
+ const { setActive, accept, reject } = useReview();
14
+ return (
15
+ <li onClick={() => setActive(item.key, { reveal: false })}>
16
+ {item.text}
17
+ <button
18
+ disabled={item.readOnly}
19
+ onClick={(event) => {
20
+ event.stopPropagation();
21
+ accept(item);
22
+ }}
23
+ >
24
+ Accept
25
+ </button>
26
+ <button
27
+ disabled={item.readOnly}
28
+ onClick={(event) => {
29
+ event.stopPropagation();
30
+ reject(item);
31
+ }}
32
+ >
33
+ Reject
34
+ </button>
35
+ </li>
36
+ );
37
+ }
38
+ ```
39
+
40
+ `accept` and `reject` return `false` when the editor refuses the decision, for example in viewing mode. Read `useReview().lastRefusal` for the diagnostic code.
41
+
42
+ **Vue**
43
+
44
+ ```vue
45
+ <script setup lang="ts">
46
+ import { useReview, type ReviewItemView } from '@docx-editor.dev/pro/vue';
47
+
48
+ defineProps<{ item: ReviewItemView }>();
49
+ const { setActive, accept, reject } = useReview();
50
+ </script>
51
+
52
+ <template>
53
+ <li @click="setActive(item.key, { reveal: false })">
54
+ {{ item.text }}
55
+ <button :disabled="item.readOnly" @click.stop="accept(item)">Accept</button>
56
+ <button :disabled="item.readOnly" @click.stop="reject(item)">Reject</button>
57
+ </li>
58
+ </template>
59
+ ```
60
+
61
+ `accept` and `reject` return `false` when the editor refuses the decision, for example in viewing mode. `useReview()` returns `lastRefusal` as a ref, so read `lastRefusal.value?.code` in script, or `lastRefusal?.code` in the template.
62
+
63
+ The buttons stop the click from reaching the row, so a decision does not also activate the item.
64
+
65
+ `reveal` takes `false` or one of four placements:
66
+
67
+ | Value | Where the item lands |
68
+ | --- | --- |
69
+ | `'centerIfNeeded'` (default) | Stays still when the item is on screen; centered when it has to travel. |
70
+ | `'center'` | Always centered. |
71
+ | `'nearest'` | The smallest scroll that brings it into view. |
72
+ | `'start'` | Near the top of the viewport. |
73
+
74
+ Add `announce: true` to open the packaged balloon or card for the item.
75
+
76
+ ## Decide in a headless pipeline
77
+
78
+ The editing API has no viewport. Load the revisions and call `Revision.accept()` or `Revision.reject()`.
79
+
80
+ ```ts
81
+ import { DocxEditor } from '@docx-editor.dev/editor-api';
82
+
83
+ const runtime = await DocxEditor.createServer(bytes, { author: 'Review bot' });
84
+ await runtime.run(async (context) => {
85
+ const revisions = context.document.body.revisions;
86
+ revisions.load('items/author');
87
+ await context.sync();
88
+ for (const revision of revisions.items) {
89
+ if (revision.author === 'Review bot') revision.accept();
90
+ }
91
+ await context.sync();
92
+ });
93
+ const saved = await runtime.save();
94
+ runtime.dispose();
95
+ ```
@@ -0,0 +1,72 @@
1
+ <!-- Generated from docs/site/content/recipes by scripts/sync-agent-docs.mjs. Edit the source and run `bun run docs:agent`. -->
2
+
3
+ # Comment on found text
4
+
5
+ In a browser host, find the text with `editor.findMatches`, select the first match with `editor.selectMatch`, and then call `useReview().comment`. The comment is written as the editor's `author`, so set `author` on the editor and add `reviewModule()` to its modules. `selectMatch` opens the header, footer, or note that holds the match, so a match in any story works.
6
+
7
+ **React**
8
+
9
+ ```tsx
10
+ import { useDocxEditor } from '@docx-editor.dev/react';
11
+ import { useReview } from '@docx-editor.dev/pro/react';
12
+
13
+ export function CommentOnTerm({ term }: { term: string }) {
14
+ const editor = useDocxEditor();
15
+ const { comment } = useReview();
16
+ const run = () => {
17
+ const [match] = editor?.findMatches(term, { wholeWord: true }) ?? [];
18
+ if (match && editor?.selectMatch(match).ok) comment(`Define "${term}".`);
19
+ };
20
+ return <button onClick={run}>Comment on {term}</button>;
21
+ }
22
+ ```
23
+
24
+ `comment` returns `false` when the editor refuses the write. Read `useReview().lastRefusal` for the diagnostic code.
25
+
26
+ **Vue**
27
+
28
+ ```vue
29
+ <script setup lang="ts">
30
+ import { useDocxEditor } from '@docx-editor.dev/vue';
31
+ import { useReview } from '@docx-editor.dev/pro/vue';
32
+
33
+ const props = defineProps<{ term: string }>();
34
+ const editor = useDocxEditor();
35
+ const { comment } = useReview();
36
+ function run() {
37
+ const [match] = editor.value?.findMatches(props.term, { wholeWord: true }) ?? [];
38
+ if (match && editor.value?.selectMatch(match).ok) comment(`Define "${props.term}".`);
39
+ }
40
+ </script>
41
+
42
+ <template>
43
+ <button @click="run">Comment on {{ term }}</button>
44
+ </template>
45
+ ```
46
+
47
+ `comment` returns `false` when the editor refuses the write. `useReview()` returns `lastRefusal` as a ref, so read `lastRefusal.value?.code` in script, or `lastRefusal?.code` in the template.
48
+
49
+ ## Comment in a headless pipeline
50
+
51
+ On a server or in an agent pipeline, search the body and call `Range.insertComment`. The runtime writes the comment as its `author` option.
52
+
53
+ ```ts
54
+ import { DocxEditor } from '@docx-editor.dev/editor-api';
55
+
56
+ const runtime = await DocxEditor.createServer(bytes, { author: 'Review bot' });
57
+ await runtime.run(async (context) => {
58
+ const found = context.document.body.search(term, { matchWholeWord: true });
59
+ found.load('items');
60
+ await context.sync();
61
+ found.items[0]?.insertComment(`Define "${term}".`);
62
+ await context.sync();
63
+ });
64
+ const saved = await runtime.save();
65
+ runtime.dispose();
66
+ ```
67
+
68
+ Both paths save the same comment markup: the same `w:comment` attributes, the same date format, and the same range markers.
69
+
70
+ ## Reply to a thread
71
+
72
+ To answer an existing comment, call `useReview().reply(item, text)` in a browser host, or `Comment.reply(text)` in the editing API.
@@ -0,0 +1,178 @@
1
+ <!-- Generated from docs/site/content/recipes by scripts/sync-agent-docs.mjs. Edit the source and run `bun run docs:agent`. -->
2
+
3
+ # Custom cards in the review rail
4
+
5
+ To draw your own cards, render `DocxEditorReview` with `preset` set to `false` and put your card component inside it. Your component reads the items from `useReview()`, measures each card, stacks the cards with `useStackedReviewPositions`, and converts positions to pixels with `useReviewRailMetrics()`.
6
+
7
+ Measure every card with a `ResizeObserver` and pass the heights in CSS pixels. Without measured heights, each card reserves only `defaultHeight`, and a taller card overlaps the next one.
8
+
9
+ **React**
10
+
11
+ ```tsx
12
+ import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';
13
+ import {
14
+ DocxEditorReview,
15
+ useReview,
16
+ useReviewRailMetrics,
17
+ useStackedReviewPositions,
18
+ } from '@docx-editor.dev/pro/react';
19
+
20
+ type OnHeight = (key: string, height: number) => void;
21
+
22
+ function MeasuredCard({
23
+ id,
24
+ top,
25
+ onHeight,
26
+ children,
27
+ }: {
28
+ id: string;
29
+ top: number;
30
+ onHeight: OnHeight;
31
+ children: ReactNode;
32
+ }) {
33
+ const ref = useRef<HTMLDivElement>(null);
34
+ useEffect(() => {
35
+ const node = ref.current!;
36
+ const observer = new ResizeObserver(() => onHeight(id, node.offsetHeight));
37
+ observer.observe(node);
38
+ return () => observer.disconnect();
39
+ }, [id, onHeight]);
40
+ return (
41
+ <div ref={ref} style={{ position: 'absolute', top }}>
42
+ {children}
43
+ </div>
44
+ );
45
+ }
46
+
47
+ function MyCards() {
48
+ const { items, selectionAnchorY, accept } = useReview();
49
+ const metrics = useReviewRailMetrics();
50
+ const [heights, setHeights] = useState<ReadonlyMap<string, number>>(() => new Map());
51
+ const onHeight = useCallback<OnHeight>(
52
+ (key, height) =>
53
+ setHeights((prev) => (prev.get(key) === height ? prev : new Map(prev).set(key, height))),
54
+ []
55
+ );
56
+ const positions = useStackedReviewPositions(items, heights, {
57
+ scale: metrics.scale,
58
+ defaultHeight: 96,
59
+ });
60
+ const top = (y: number) => metrics.top + y * metrics.scale;
61
+ return (
62
+ <>
63
+ {items.map((item) => (
64
+ <MeasuredCard
65
+ key={item.key}
66
+ id={item.key}
67
+ top={top(positions.get(item.key) ?? 0)}
68
+ onHeight={onHeight}
69
+ >
70
+ {item.text}
71
+ {item.kind === 'revision' && <button onClick={() => accept(item)}>Accept</button>}
72
+ </MeasuredCard>
73
+ ))}
74
+ <DocxEditorReview.Draft top={top(selectionAnchorY ?? 0)} />
75
+ </>
76
+ );
77
+ }
78
+
79
+ export function MyRail() {
80
+ return (
81
+ <DocxEditorReview preset={false}>
82
+ <MyCards />
83
+ <DocxEditorReview.AddComment />
84
+ </DocxEditorReview>
85
+ );
86
+ }
87
+ ```
88
+
89
+ The heights map gets a new reference only when a height changes, so the stack recomputes only then. Keep that rule if you store heights another way.
90
+
91
+ **Vue**
92
+
93
+ ```vue
94
+ <!-- MyCards.vue -->
95
+ <script setup lang="ts">
96
+ import { onBeforeUnmount, shallowReactive } from 'vue';
97
+ import {
98
+ DocxEditorReview,
99
+ useReview,
100
+ useReviewRailMetrics,
101
+ useStackedReviewPositions,
102
+ } from '@docx-editor.dev/pro/vue';
103
+
104
+ const { items, selectionAnchorY, accept } = useReview();
105
+ const { metrics } = useReviewRailMetrics();
106
+ // Reactive, so a new measurement restacks the cards.
107
+ const heights = shallowReactive(new Map<string, number>());
108
+ const positions = useStackedReviewPositions(items, heights, () => ({
109
+ scale: metrics.value.scale,
110
+ defaultHeight: 96,
111
+ }));
112
+ const top = (y: number) => `${metrics.value.top + y * metrics.value.scale}px`;
113
+
114
+ const observed = new Map<string, { el: Element; observer: ResizeObserver }>();
115
+ function measure(key: string, el: unknown) {
116
+ const current = observed.get(key);
117
+ if (current?.el === el) return;
118
+ current?.observer.disconnect();
119
+ observed.delete(key);
120
+ if (!(el instanceof HTMLElement)) {
121
+ heights.delete(key);
122
+ return;
123
+ }
124
+ const observer = new ResizeObserver(() => heights.set(key, el.offsetHeight));
125
+ observer.observe(el);
126
+ observed.set(key, { el, observer });
127
+ }
128
+ onBeforeUnmount(() => observed.forEach(({ observer }) => observer.disconnect()));
129
+ </script>
130
+
131
+ <template>
132
+ <div
133
+ v-for="item in items"
134
+ :key="item.key"
135
+ :ref="(el) => measure(item.key, el)"
136
+ :style="{ position: 'absolute', top: top(positions.get(item.key) ?? 0) }"
137
+ >
138
+ {{ item.text }}
139
+ <button v-if="item.kind === 'revision'" @click="accept(item)">Accept</button>
140
+ </div>
141
+ <DocxEditorReview.Draft :top="metrics.top + (selectionAnchorY ?? 0) * metrics.scale" />
142
+ </template>
143
+ ```
144
+
145
+ ```vue
146
+ <!-- MyRail.vue -->
147
+ <script setup lang="ts">
148
+ import { DocxEditorReview } from '@docx-editor.dev/pro/vue';
149
+ import MyCards from './MyCards.vue';
150
+ </script>
151
+
152
+ <template>
153
+ <DocxEditorReview :preset="false">
154
+ <MyCards />
155
+ <DocxEditorReview.AddComment />
156
+ </DocxEditorReview>
157
+ </template>
158
+ ```
159
+
160
+ A plain `Map` is not reactive, so writes to it would not restack the cards. Use `shallowReactive(new Map())`, or a ref that you replace on each change.
161
+
162
+ ## Root parts and card parts
163
+
164
+ `DocxEditorReview` finds its parts among its own children by element type.
165
+
166
+ - **Root parts** are `List`, `Markers`, `AddComment`, `Draft`, and `Balloon`. Put them as direct children of `DocxEditorReview`. Fragments are transparent in React and in Vue. The rail gives these parts geometry that only it can compute, such as the comment button's `top`. A root part inside your own wrapper component is not a part. It renders as ordinary content, without that wiring.
167
+ - **Card parts** are `Card`, `Empty`, and the parts inside a card, such as `Author`, `Summary`, `Accept`, and `Reply`. Put them inside `DocxEditorReview.List`.
168
+
169
+ With `preset` set to `false`, the rail adds no defaults. Root parts that you pass keep their wiring. Every other child renders as is inside the rail element, where `useReviewRailMetrics()` reads the rail's own measurement.
170
+
171
+ ## Position the compose box
172
+
173
+ `DocxEditorReview.Draft` renders only while a comment draft is open. A draft opens from `DocxEditorReview.AddComment` or from the `review.addComment` toolbar control. You can place `Draft` in either of these places:
174
+
175
+ - As a direct child of `DocxEditorReview`.
176
+ - Inside your own card component, as in the example. It still renders only while the draft is open.
177
+
178
+ Without `top`, the box sits where the packaged card stack puts it. That position comes from the packaged cards, not yours, so when you render your own cards, pass `top` from your own stack. In the narrow compact rail, the box takes the compact card's `left` in both cases, so it does not overflow the marker strip. The example places the box at the selection's anchor. To keep it clear of your cards, add an entry with the key of your choice and the anchor `selectionAnchorY` to the list that you pass to `useStackedReviewPositions`, in document order.
@@ -0,0 +1,48 @@
1
+ <!-- Generated from docs/site/content/recipes by scripts/sync-agent-docs.mjs. Edit the source and run `bun run docs:agent`. -->
2
+
3
+ # Highlight ranges
4
+
5
+ Use `useHighlights(name, source, options)` to mark text as a named set while a component is mounted. A function source runs again after document changes, so the marks follow the text. Highlights are view state. They do not change the document and are not saved.
6
+
7
+ **React**
8
+
9
+ ```tsx
10
+ import { useCallback } from 'react';
11
+ import { useHighlights, type Editor } from '@docx-editor.dev/react';
12
+
13
+ export function GlossaryMarks({ terms }: { terms: readonly string[] }) {
14
+ const findTerms = useCallback(
15
+ (editor: Editor) => editor.findMatches(terms, { wholeWord: true }).flat(),
16
+ [terms]
17
+ );
18
+ const { applied } = useHighlights('glossary', findTerms, { className: 'glossary-term' });
19
+ return <span>{applied} marked</span>;
20
+ }
21
+ ```
22
+
23
+ Keep a function source stable with `useCallback`.
24
+
25
+ **Vue**
26
+
27
+ ```vue
28
+ <script setup lang="ts">
29
+ import { useHighlights, type Editor } from '@docx-editor.dev/vue';
30
+
31
+ const props = defineProps<{ terms: readonly string[] }>();
32
+ const result = useHighlights(
33
+ 'glossary',
34
+ (editor: Editor) => editor.findMatches(props.terms, { wholeWord: true }).flat(),
35
+ { className: 'glossary-term' }
36
+ );
37
+ </script>
38
+
39
+ <template>
40
+ <span>{{ result.applied }} marked</span>
41
+ </template>
42
+ ```
43
+
44
+ A function source that reads props or refs runs again when they change.
45
+
46
+ Use a unique `name` for each set. Two calls with the same name replace each other's ranges. Set `activeIndex` in the options to show one range in the active color, for example the current result of your own search list.
47
+
48
+ Outside a component, call `watchHighlights(editor, name, source, options)` from `@docx-editor.dev/core/editor`, or `editor.setHighlights` for a fixed set.
package/docs/index.md ADDED
@@ -0,0 +1,36 @@
1
+ <!-- Generated from docs/site/content/recipes by scripts/sync-agent-docs.mjs. Edit the source and run `bun run docs:agent`. -->
2
+
3
+ # Which API for which job
4
+
5
+ The library has two ways to change a document. Each one fits a different host. Choose the path first, then use the symbol for your task.
6
+
7
+ | Job | Use |
8
+ | --- | --- |
9
+ | Build a review UI in the browser | The React or Vue adapter hooks, plus the review hooks and `DocxEditorReview` from `@docx-editor.dev/pro/react` or `@docx-editor.dev/pro/vue`. |
10
+ | Edit in a headless, server, or agent pipeline | The editing API in `@docx-editor.dev/editor-api`, which follows the Office.js object model. |
11
+
12
+ | Task | Browser host (React or Vue) | Headless, server, or agent |
13
+ | --- | --- | --- |
14
+ | [Comment on found text](./comment-on-found-text.md) | `editor.findMatches`, `editor.selectMatch`, then `useReview().comment` | `body.search`, then `Range.insertComment` |
15
+ | Reply to a thread | `useReview().reply(item, text)` | `Comment.reply(text)` |
16
+ | [Record a tracked replacement](./tracked-replacement.md) | Suggesting mode, then `editor.exec({ type: 'replaceMatch', match, text })` | `document.changeTrackingMode = 'TrackMineOnly'`, then `Range.insertText(text, 'Replace')` |
17
+ | [Propose an AI rewrite as tracked changes](./propose-rewrite.md) | The same code through `DocxEditor.createBrowser` from `@docx-editor.dev/editor-api/browser` | `prepareRewrite` from `@docx-editor.dev/editor-api/agent` |
18
+ | [Accept or reject a change](./accept-reject-without-scrolling.md) | `useReview().accept(item)` or `useReview().reject(item)` | `Revision.accept()` or `Revision.reject()` |
19
+ | [Open or reveal a change without scrolling](./accept-reject-without-scrolling.md) | `useReview().setActive(key, { reveal: false })` | Not applicable: no viewport |
20
+ | [Highlight ranges](./highlight-ranges.md) | `useHighlights(name, source, options)` | Not applicable |
21
+ | [Custom review rail cards](./custom-review-cards.md) | `DocxEditorReview` with `preset` set to `false`, `useReview().items`, `useStackedReviewPositions`, and `useReviewRailMetrics` | Not applicable |
22
+ | Comment-on-selection button | `DocxEditorReview.AddComment` as a direct child of `DocxEditorReview`, or `useEditorCommand('review.addComment')` | Not applicable |
23
+ | Page setup and paragraph dialogs from a custom toolbar | `DocxEditor.PageSetupDialog` or `DocxEditor.ParagraphDialog` with `open` and a close handler (`onClose` in React, `@close` in Vue); enable the button from `usePageSetup().isEnabled` or `useParagraphFormat().isEnabled` | `Section.pageSetup`, and `Paragraph` properties such as `alignment` |
24
+
25
+ The review hooks need `reviewModule()` from `@docx-editor.dev/pro` in the editor's modules, and an editor `author` for comments and tracked changes. The editing API takes its author from the `author` option of `DocxEditor.createServer`, imported from `@docx-editor.dev/editor-api`, or of `DocxEditor.createBrowser`, imported from `@docx-editor.dev/editor-api/browser`.
26
+
27
+ ## Recipes
28
+
29
+ - [Comment on found text](./comment-on-found-text.md)
30
+ - [Replace as a tracked change](./tracked-replacement.md)
31
+ - [Propose an AI rewrite as tracked changes](./propose-rewrite.md)
32
+ - [Custom cards in the review rail](./custom-review-cards.md)
33
+ - [Highlight ranges](./highlight-ranges.md)
34
+ - [Accept or reject without scrolling](./accept-reject-without-scrolling.md)
35
+
36
+ For the full hook reference, see [React hooks](https://www.docx-editor.dev/docs/2.x/react/hooks) and [Vue composables](https://www.docx-editor.dev/docs/2.x/vue/composables). For the editing API, see [Editing API](https://www.docx-editor.dev/docs/2.x/editor-api).