@docx-editor.dev/core 2.35.0-canary.20261104032723.sha-43c2012 → 2.35.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 (115) hide show
  1. package/README.md +9 -14
  2. package/dist/automation.cjs +1 -1
  3. package/dist/automation.d.ts +16 -11
  4. package/dist/automation.js +1 -1
  5. package/dist/binding.cjs +1 -1
  6. package/dist/binding.d.ts +12 -12
  7. package/dist/binding.js +1 -1
  8. package/dist/{chrome-print-CK3v5NhC.d.ts → chrome-print-H0uSxdg3.d.ts} +70 -17
  9. package/dist/{chunk-AMOFXSEO.cjs → chunk-2C75IWUN.cjs} +1 -1
  10. package/dist/chunk-2NFQ7OXY.cjs +1 -0
  11. package/dist/chunk-2V3V57SC.js +3 -0
  12. package/dist/chunk-32YOHLEK.cjs +32 -0
  13. package/dist/chunk-4NISR27M.cjs +31 -0
  14. package/dist/chunk-5ZE7ROGP.js +31 -0
  15. package/dist/{chunk-LZ64O473.js → chunk-66ZAVRGB.js} +1 -1
  16. package/dist/{chunk-A5GMOQWD.js → chunk-6767S7UF.js} +1 -1
  17. package/dist/chunk-6BZINLEN.cjs +1 -0
  18. package/dist/{chunk-EBIMIIC3.js → chunk-AVGSD7FB.js} +5 -5
  19. package/dist/{chunk-TZTKEPCC.cjs → chunk-CBJMRC4L.cjs} +1 -1
  20. package/dist/chunk-DCILO4XD.cjs +236 -0
  21. package/dist/{chunk-FBGZOGLU.cjs → chunk-DSIDCA3V.cjs} +4 -4
  22. package/dist/chunk-FLEFHRJG.cjs +5 -0
  23. package/dist/chunk-FMSXJ22B.js +2 -0
  24. package/dist/{chunk-7ACXKFKO.js → chunk-I4OZW2NO.js} +1 -1
  25. package/dist/chunk-JGE2O6IF.cjs +3 -0
  26. package/dist/chunk-K2BBJDM7.js +32 -0
  27. package/dist/chunk-LMASPHYS.js +1 -0
  28. package/dist/chunk-NYZP5NM3.js +4 -0
  29. package/dist/chunk-P3XUE72N.js +236 -0
  30. package/dist/{chunk-JZ4LO3ZZ.js → chunk-Q5YVLUGG.js} +1 -1
  31. package/dist/chunk-QALWAAPL.cjs +4 -0
  32. package/dist/{chunk-ELR6HXOO.js → chunk-QDUMZYTX.js} +1 -1
  33. package/dist/chunk-QLANEOTJ.cjs +7 -0
  34. package/dist/{chunk-4NPNKO5U.cjs → chunk-SJV6WCTP.cjs} +1 -1
  35. package/dist/{chunk-I4WSXFY6.js → chunk-TMCMXIBT.js} +4 -4
  36. package/dist/{chunk-GTICLPKS.cjs → chunk-TY5CE2JR.cjs} +1 -1
  37. package/dist/chunk-UAUB5YV6.js +7 -0
  38. package/dist/{chunk-Q4MUV5KX.cjs → chunk-WWZWT26M.cjs} +1 -1
  39. package/dist/chunk-Y3JSN52G.cjs +2 -0
  40. package/dist/chunk-YD3KSZJM.js +1 -0
  41. package/dist/collaboration/replication.cjs +1 -1
  42. package/dist/collaboration/replication.d.ts +3 -3
  43. package/dist/collaboration/replication.js +1 -1
  44. package/dist/collaboration.d.ts +2 -2
  45. package/dist/contracts/document.d.ts +2 -2
  46. package/dist/contracts/editor.d.ts +8 -8
  47. package/dist/contracts/modules.cjs +1 -1
  48. package/dist/contracts/modules.d.ts +34 -13
  49. package/dist/contracts/modules.js +1 -1
  50. package/dist/contracts/types.d.ts +3 -3
  51. package/dist/{custom-node-writes-BC3z8NwO.d.ts → custom-node-writes-Cj0itq58.d.ts} +2 -2
  52. package/dist/{document-protection-8hXPdn5U.d.ts → document-protection-CDLoYQI3.d.ts} +1 -1
  53. package/dist/{drawing-graphic-kind-_5u2E4ee.d.ts → drawing-graphic-kind-8zt6pugF.d.ts} +2 -2
  54. package/dist/{drawing-preview-package-JyAqL6OV.d.ts → drawing-preview-package-8o9LSfzP.d.ts} +1 -1
  55. package/dist/{editor-BGCdE3tB.d.ts → editor-CujQcn1z.d.ts} +20 -10
  56. package/dist/editor.cjs +1 -1
  57. package/dist/editor.css +3 -3
  58. package/dist/editor.d.ts +52 -29
  59. package/dist/editor.js +1 -1
  60. package/dist/export.cjs +2 -2
  61. package/dist/export.d.ts +6 -6
  62. package/dist/export.js +1 -1
  63. package/dist/index.cjs +1 -1
  64. package/dist/index.d.ts +18 -18
  65. package/dist/index.js +1 -1
  66. package/dist/{layout-shaping-D2qNes9-.d.ts → layout-shaping-ZjLRz_qa.d.ts} +1 -1
  67. package/dist/layout.cjs +1 -1
  68. package/dist/layout.d.ts +25 -21
  69. package/dist/layout.js +1 -1
  70. package/dist/{note-properties-DGPzKUuA.d.ts → note-properties-BRpskBeh.d.ts} +1 -1
  71. package/dist/output.cjs +1 -1
  72. package/dist/output.d.ts +10 -10
  73. package/dist/output.js +1 -1
  74. package/dist/{protocol-0y3iaYj9.d.ts → protocol-GVVQQwCX.d.ts} +13 -2
  75. package/dist/{review-paragraph-order-DeQp2_iu.d.ts → review-paragraph-order-aEHnQTt7.d.ts} +5 -2
  76. package/dist/{revision-projection-CrAv-HP6.d.ts → revision-projection-vU_UiTLx.d.ts} +1 -1
  77. package/dist/{semantic-cell-selection-CeG_qqDV.d.ts → semantic-cell-selection-CaRIISu-.d.ts} +2 -2
  78. package/dist/{semantic-interaction-DcE_ZsiD.d.ts → semantic-interaction-LJtUJJS5.d.ts} +1 -1
  79. package/dist/{semantic-paint-DfeN6UQF.d.ts → semantic-paint-2LBYlQcn.d.ts} +11 -4
  80. package/dist/{semantic-record-queries-qwd9Ict6.d.ts → semantic-record-queries-C_YK2gCz.d.ts} +1 -1
  81. package/dist/{semantic-records-thL1rcmG.d.ts → semantic-records-AU2pIYRp.d.ts} +3 -3
  82. package/dist/store.cjs +1 -1
  83. package/dist/store.d.ts +1664 -1664
  84. package/dist/store.js +1 -1
  85. package/dist/{tree-op-types-CGlwCQXg.d.ts → tree-op-types-DmrePCMo.d.ts} +320 -11
  86. package/dist/{tree-package-store-D0_BOm-r.d.ts → tree-package-fragment-DXZ2B7x0.d.ts} +702 -691
  87. package/dist/{tree-session-contract-DkThhHjy.d.ts → tree-session-contract-D3e-5YNq.d.ts} +19 -4
  88. package/dist/{unsupported-elements-qj0sXi2s.d.ts → unsupported-elements-9Pz4I_F6.d.ts} +1 -1
  89. package/docs/accept-reject-without-scrolling.md +31 -48
  90. package/docs/comment-on-found-text.md +47 -23
  91. package/docs/custom-review-cards.md +50 -34
  92. package/docs/highlight-ranges.md +26 -6
  93. package/docs/index.md +21 -21
  94. package/docs/propose-rewrite.md +87 -138
  95. package/docs/tracked-replacement.md +41 -20
  96. package/package.json +2 -2
  97. package/dist/chunk-4C7JUJDX.cjs +0 -1
  98. package/dist/chunk-4HENIAFG.js +0 -1
  99. package/dist/chunk-6IYKYYWF.js +0 -32
  100. package/dist/chunk-CDAFCPT6.cjs +0 -3
  101. package/dist/chunk-CU6QPXKD.js +0 -2
  102. package/dist/chunk-EMITIPLN.js +0 -4
  103. package/dist/chunk-IGB66DIX.cjs +0 -30
  104. package/dist/chunk-JQPTNBLT.js +0 -236
  105. package/dist/chunk-JVWRILO3.cjs +0 -4
  106. package/dist/chunk-KOYEBRLV.js +0 -1
  107. package/dist/chunk-KZZPOZOF.cjs +0 -2
  108. package/dist/chunk-NPWMZIPA.cjs +0 -32
  109. package/dist/chunk-NS2QCOAS.js +0 -7
  110. package/dist/chunk-O2PGRL5D.js +0 -3
  111. package/dist/chunk-TFJUIP4X.cjs +0 -236
  112. package/dist/chunk-V3N4SOAK.cjs +0 -1
  113. package/dist/chunk-V6FOPP2H.js +0 -30
  114. package/dist/chunk-XFEIUKOS.cjs +0 -7
  115. package/dist/chunk-ZF4N3N3L.cjs +0 -5
@@ -1,7 +1,7 @@
1
- import { S as StoryScope, g as SelectionMark, h as TransactOptions, b as HistoryGroup, a as StoryTargetRejection, c as TreeModelChange, e as HeaderFooterParts, f as HeaderFooterSectionResolution, I as InsertImageInput, i as ImageIntentResult, R as ReplaceImageOptions, A as ApplyImagePropertiesInput, j as FragmentPasteInput, k as FragmentPasteResult } from './tree-package-store-D0_BOm-r.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, aE as RevisionAttributionInput } from './tree-op-types-CGlwCQXg.js';
3
- import { D as DocumentTrackingSettings, I as InsertCustomNodeWrite, C as CustomNodeWriteResult, a as CustomNodeSweepOutcome, E as EmbeddedFont, B as BookmarkIndex, L as ListKind, b as ListDefinitionGeometry } from './custom-node-writes-BC3z8NwO.js';
4
- import { H as HeadlessDocumentView, c as DocumentProperties } from './drawing-graphic-kind-_5u2E4ee.js';
1
+ import { c as TreeModelChange, S as StoryScope, g as SelectionMark, h as TransactOptions, b as HistoryGroup, a as StoryTargetRejection, e as HeaderFooterParts, f as HeaderFooterSectionResolution, I as InsertImageInput, i as ImageIntentResult, R as ReplaceImageOptions, A as ApplyImagePropertiesInput, j as FragmentPasteInput, k as FragmentPasteResult } from './tree-package-fragment-DXZ2B7x0.js';
2
+ import { aO as TreeDocOp, O as OoxmlPart, am as OoxmlNode, r as OoxmlPackage, aU as OoxmlElement, a as ReviewItem, aG as SupportedImageMime, aw as ImageDecodePort, aY as RevisionAttributionInput } from './tree-op-types-DmrePCMo.js';
3
+ import { D as DocumentTrackingSettings, I as InsertCustomNodeWrite, C as CustomNodeWriteResult, a as CustomNodeSweepOutcome, E as EmbeddedFont, B as BookmarkIndex, L as ListKind, b as ListDefinitionGeometry } from './custom-node-writes-Cj0itq58.js';
4
+ import { H as HeadlessDocumentView, c as DocumentProperties } from './drawing-graphic-kind-8zt6pugF.js';
5
5
  import { Node } from 'prosemirror-model';
6
6
  import { CollaborationDocumentPort } from './collaboration/replication.js';
7
7
  import { V as ViewScope } from './editor-scope-CA6egMaK.js';
@@ -64,6 +64,16 @@ declare function reconcileDoc(previousDoc: Node, part: OoxmlPart, change: {
64
64
  readonly splitJoin: readonly unknown[];
65
65
  } | null): Node;
66
66
 
67
+ /** Why a whole-package replacement was refused. */
68
+ type PackageReplacementRefusal = 'missing-main-part' | 'different-main-part';
69
+ type PackageReplacementResult = {
70
+ readonly ok: true;
71
+ readonly change: TreeModelChange | null;
72
+ } | {
73
+ readonly ok: false;
74
+ readonly reason: PackageReplacementRefusal;
75
+ };
76
+
67
77
  /**
68
78
  * The faces a theme's font scheme offers, per script slot.
69
79
  *
@@ -680,6 +690,11 @@ interface TreeDocxSessionView extends HeadlessDocumentView {
680
690
  applyImageProperties(scope: StoryScope, input: ApplyImagePropertiesInput): ImageIntentResult;
681
691
  /** Land a clipboard fragment (resource merge + blocks) as one package undo unit. */
682
692
  applyFragmentPaste(scope: StoryScope, input: FragmentPasteInput): FragmentPasteResult;
693
+ /**
694
+ * Replace the whole package with one derived from {@link currentPackage}, as one package undo
695
+ * unit that publishes a `global` change. For writes that rebuild many parts, such as a compare.
696
+ */
697
+ replacePackage(pkg: OoxmlPackage): PackageReplacementResult;
683
698
  }
684
699
 
685
700
  export { bodyParagraphs as d, docToTreeOps as e, partHasNode as p, reconcileDoc as r, treeToDoc as t };
@@ -1,4 +1,4 @@
1
- import { U as UnsupportedElement } from './semantic-records-thL1rcmG.js';
1
+ import { U as UnsupportedElement } from './semantic-records-AU2pIYRp.js';
2
2
 
3
3
  /** A component mount point and its built-in fallback. @public */
4
4
  interface UnsupportedElementHost {
@@ -2,7 +2,11 @@
2
2
 
3
3
  # Accept or reject without scrolling
4
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.
5
+ Resolve a tracked change from your own list with `useReview().accept(item)` or `reject(item)`. Review writes such as accept, reject, and comment never scroll the page, and they need no prior activation.
6
+
7
+ ## Resolve a change without scrolling
8
+
9
+ The following row opens its item without scrolling and resolves it from two buttons:
6
10
 
7
11
  **React**
8
12
 
@@ -10,35 +14,22 @@ When your own list drives the scroll, open an item with `useReview().setActive(k
10
14
  import { useReview, type ReviewItemView } from '@docx-editor.dev/pro/react';
11
15
 
12
16
  export function ChangeRow({ item }: { item: ReviewItemView }) {
13
- const { setActive, accept, reject } = useReview();
17
+ const { setActive, accept, reject, lastRefusal } = useReview();
14
18
  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
- >
19
+ <li>
20
+ <button onClick={() => setActive(item.key, { reveal: false })}>{item.text}</button>
21
+ <button disabled={item.readOnly} onClick={() => accept(item)}>
24
22
  Accept
25
23
  </button>
26
- <button
27
- disabled={item.readOnly}
28
- onClick={(event) => {
29
- event.stopPropagation();
30
- reject(item);
31
- }}
32
- >
24
+ <button disabled={item.readOnly} onClick={() => reject(item)}>
33
25
  Reject
34
26
  </button>
27
+ {lastRefusal?.key === item.key && <p role="alert">{lastRefusal.code}</p>}
35
28
  </li>
36
29
  );
37
30
  }
38
31
  ```
39
32
 
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
33
  **Vue**
43
34
 
44
35
  ```vue
@@ -46,23 +37,24 @@ export function ChangeRow({ item }: { item: ReviewItemView }) {
46
37
  import { useReview, type ReviewItemView } from '@docx-editor.dev/pro/vue';
47
38
 
48
39
  defineProps<{ item: ReviewItemView }>();
49
- const { setActive, accept, reject } = useReview();
40
+ const { setActive, accept, reject, lastRefusal } = useReview();
50
41
  </script>
51
42
 
52
43
  <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>
44
+ <li>
45
+ <button @click="setActive(item.key, { reveal: false })">{{ item.text }}</button>
46
+ <button :disabled="item.readOnly" @click="accept(item)">Accept</button>
47
+ <button :disabled="item.readOnly" @click="reject(item)">Reject</button>
48
+ <p v-if="lastRefusal?.key === item.key" role="alert">{{ lastRefusal.code }}</p>
57
49
  </li>
58
50
  </template>
59
51
  ```
60
52
 
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.
53
+ `accept` and `reject` return `false` when the editor refuses the decision, for example in viewing mode. `lastRefusal` holds the item key and the diagnostic code. In Vue it is a ref, so read `lastRefusal.value?.code` in script.
62
54
 
63
- The buttons stop the click from reaching the row, so a decision does not also activate the item.
55
+ ## Activate without scrolling
64
56
 
65
- `reveal` takes `false` or one of four placements:
57
+ `setActive(key, { reveal: false })` moves the caret to the item and keeps the scroll position. `reveal` takes `false` or one of four placements:
66
58
 
67
59
  | Value | Where the item lands |
68
60
  | --- | --- |
@@ -73,23 +65,14 @@ The buttons stop the click from reaching the row, so a decision does not also ac
73
65
 
74
66
  Add `announce: true` to open the packaged balloon or card for the item.
75
67
 
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
- ```
68
+ ## Limits
69
+
70
+ - Disable the decision buttons for items where `item.readOnly` is `true`.
71
+ - If the whole row is clickable, stop button clicks from reaching it, so a decision does not also activate the item.
72
+ - Headless pipelines have no viewport. To decide changes on a server, call `Revision.accept()` or `Revision.reject()`. For more information, see [Tracked changes](https://www.docx-editor.dev/docs/2.x/editor-api/revisions).
73
+
74
+ ## See also
75
+
76
+ - [Tracked changes](https://www.docx-editor.dev/docs/2.x/pro/tracked-changes)
77
+ - [Diagnostics](https://www.docx-editor.dev/docs/2.x/guides/diagnostics)
78
+ - [Custom cards in the review rail](./custom-review-cards.md)
@@ -2,71 +2,95 @@
2
2
 
3
3
  # Comment on found text
4
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.
5
+ Find text with `editor.findMatches`, select a match with `editor.selectMatch`, and comment with `useReview().comment`. On a server, search with `body.search` and call `Range.insertComment`.
6
+
7
+ ## Comment on a search result
8
+
9
+ The following button comments on the first whole-word match of a term. The editor needs an `author` and `reviewModule()` in its modules.
6
10
 
7
11
  **React**
8
12
 
9
13
  ```tsx
14
+ import { useState } from 'react';
10
15
  import { useDocxEditor } from '@docx-editor.dev/react';
11
16
  import { useReview } from '@docx-editor.dev/pro/react';
12
17
 
13
18
  export function CommentOnTerm({ term }: { term: string }) {
14
19
  const editor = useDocxEditor();
15
- const { comment } = useReview();
20
+ const { comment, lastRefusal } = useReview();
21
+ const [status, setStatus] = useState('');
16
22
  const run = () => {
17
23
  const [match] = editor?.findMatches(term, { wholeWord: true }) ?? [];
18
- if (match && editor?.selectMatch(match).ok) comment(`Define "${term}".`);
24
+ if (!match || !editor?.selectMatch(match).ok) return setStatus('No match');
25
+ setStatus(comment(`Define "${term}".`) ? '' : 'Refused');
19
26
  };
20
- return <button onClick={run}>Comment on {term}</button>;
27
+ return (
28
+ <>
29
+ <button onClick={run}>Comment on {term}</button>
30
+ {/* lastRefusal updates on the next render, so read it here. */}
31
+ <span role="status">{status === 'Refused' ? lastRefusal?.code : status}</span>
32
+ </>
33
+ );
21
34
  }
22
35
  ```
23
36
 
24
- `comment` returns `false` when the editor refuses the write. Read `useReview().lastRefusal` for the diagnostic code.
25
-
26
37
  **Vue**
27
38
 
28
39
  ```vue
29
40
  <script setup lang="ts">
41
+ import { ref } from 'vue';
30
42
  import { useDocxEditor } from '@docx-editor.dev/vue';
31
43
  import { useReview } from '@docx-editor.dev/pro/vue';
32
44
 
33
45
  const props = defineProps<{ term: string }>();
34
46
  const editor = useDocxEditor();
35
- const { comment } = useReview();
47
+ const { comment, lastRefusal } = useReview();
48
+ const status = ref('');
36
49
  function run() {
37
50
  const [match] = editor.value?.findMatches(props.term, { wholeWord: true }) ?? [];
38
- if (match && editor.value?.selectMatch(match).ok) comment(`Define "${props.term}".`);
51
+ if (!match || !editor.value?.selectMatch(match).ok) {
52
+ status.value = 'No match';
53
+ return;
54
+ }
55
+ if (!comment(`Define "${props.term}".`)) status.value = lastRefusal.value?.code ?? 'Refused';
39
56
  }
40
57
  </script>
41
58
 
42
59
  <template>
43
60
  <button @click="run">Comment on {{ term }}</button>
61
+ <span role="status">{{ status }}</span>
44
62
  </template>
45
63
  ```
46
64
 
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.
65
+ Comment only after `selectMatch(match).ok` is `true`. `selectMatch` opens the header, footer, or note that holds the match. `comment` returns `false` when the editor refuses the write, and `lastRefusal` holds the diagnostic code.
48
66
 
49
- ## Comment in a headless pipeline
67
+ ## Comment on a server
50
68
 
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.
69
+ The following example searches the body, not every story, and saves the result:
52
70
 
53
71
  ```ts
54
72
  import { DocxEditor } from '@docx-editor.dev/editor-api';
55
73
 
56
74
  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();
75
+ try {
76
+ await runtime.run(async (context) => {
77
+ const found = context.document.body.search(term, { matchWholeWord: true });
78
+ found.load('items');
79
+ await context.sync();
80
+ if (found.items.length === 0) return;
81
+ found.items[0]!.insertComment(`Define "${term}".`);
82
+ await context.sync();
83
+ });
84
+ const saved = await runtime.save();
85
+ } finally {
86
+ runtime.dispose();
87
+ }
66
88
  ```
67
89
 
68
- Both paths save the same comment markup: the same `w:comment` attributes, the same date format, and the same range markers.
90
+ Load the search results before you read them, and end with `await context.sync()` to send the queued comment. The runtime writes the comment as its `author` option.
69
91
 
70
- ## Reply to a thread
92
+ ## See also
71
93
 
72
- To answer an existing comment, call `useReview().reply(item, text)` in a browser host, or `Comment.reply(text)` in the editing API.
94
+ - [Comments](https://www.docx-editor.dev/docs/2.x/pro/comments)
95
+ - [Diagnostics](https://www.docx-editor.dev/docs/2.x/guides/diagnostics)
96
+ - [Replace as a tracked change](./tracked-replacement.md)
@@ -2,9 +2,11 @@
2
2
 
3
3
  # Custom cards in the review rail
4
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()`.
5
+ Render `DocxEditorReview` with `preset` set to `false` and put your card component inside it. The component reads items from `useReview()`, stacks measured cards with `useStackedReviewPositions`, and converts positions to pixels with `useReviewRailMetrics()`.
6
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.
7
+ ## Render custom cards
8
+
9
+ The following example measures each card with a `ResizeObserver`, stacks the cards, and shows a refused decision:
8
10
 
9
11
  **React**
10
12
 
@@ -19,17 +21,8 @@ import {
19
21
 
20
22
  type OnHeight = (key: string, height: number) => void;
21
23
 
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
- }) {
24
+ function MeasuredCard(props: { id: string; top: number; onHeight: OnHeight; children: ReactNode }) {
25
+ const { id, top, onHeight, children } = props;
33
26
  const ref = useRef<HTMLDivElement>(null);
34
27
  useEffect(() => {
35
28
  const node = ref.current!;
@@ -45,8 +38,9 @@ function MeasuredCard({
45
38
  }
46
39
 
47
40
  function MyCards() {
48
- const { items, selectionAnchorY, accept } = useReview();
41
+ const { items, selectionAnchorY, accept, lastRefusal } = useReview();
49
42
  const metrics = useReviewRailMetrics();
43
+ // A new map only when a height changes, so the stack recomputes only then.
50
44
  const [heights, setHeights] = useState<ReadonlyMap<string, number>>(() => new Map());
51
45
  const onHeight = useCallback<OnHeight>(
52
46
  (key, height) =>
@@ -69,6 +63,7 @@ function MyCards() {
69
63
  >
70
64
  {item.text}
71
65
  {item.kind === 'revision' && <button onClick={() => accept(item)}>Accept</button>}
66
+ {lastRefusal?.key === item.key && <p>{lastRefusal.code}</p>}
72
67
  </MeasuredCard>
73
68
  ))}
74
69
  <DocxEditorReview.Draft top={top(selectionAnchorY ?? 0)} />
@@ -86,8 +81,6 @@ export function MyRail() {
86
81
  }
87
82
  ```
88
83
 
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
84
  **Vue**
92
85
 
93
86
  ```vue
@@ -101,7 +94,7 @@ import {
101
94
  useStackedReviewPositions,
102
95
  } from '@docx-editor.dev/pro/vue';
103
96
 
104
- const { items, selectionAnchorY, accept } = useReview();
97
+ const { items, selectionAnchorY, accept, lastRefusal } = useReview();
105
98
  const { metrics } = useReviewRailMetrics();
106
99
  // Reactive, so a new measurement restacks the cards.
107
100
  const heights = shallowReactive(new Map<string, number>());
@@ -111,19 +104,20 @@ const positions = useStackedReviewPositions(items, heights, () => ({
111
104
  }));
112
105
  const top = (y: number) => `${metrics.value.top + y * metrics.value.scale}px`;
113
106
 
114
- const observed = new Map<string, { el: Element; observer: ResizeObserver }>();
107
+ const observed = new Map<string, { el: HTMLElement; observer: ResizeObserver }>();
115
108
  function measure(key: string, el: unknown) {
109
+ const node = el as HTMLElement | null;
116
110
  const current = observed.get(key);
117
- if (current?.el === el) return;
111
+ if (current?.el === node) return;
118
112
  current?.observer.disconnect();
119
113
  observed.delete(key);
120
- if (!(el instanceof HTMLElement)) {
114
+ if (!node) {
121
115
  heights.delete(key);
122
116
  return;
123
117
  }
124
- const observer = new ResizeObserver(() => heights.set(key, el.offsetHeight));
125
- observer.observe(el);
126
- observed.set(key, { el, observer });
118
+ const observer = new ResizeObserver(() => heights.set(key, node.offsetHeight));
119
+ observer.observe(node);
120
+ observed.set(key, { el: node, observer });
127
121
  }
128
122
  onBeforeUnmount(() => observed.forEach(({ observer }) => observer.disconnect()));
129
123
  </script>
@@ -137,6 +131,7 @@ onBeforeUnmount(() => observed.forEach(({ observer }) => observer.disconnect()))
137
131
  >
138
132
  {{ item.text }}
139
133
  <button v-if="item.kind === 'revision'" @click="accept(item)">Accept</button>
134
+ <p v-if="lastRefusal?.key === item.key">{{ lastRefusal.code }}</p>
140
135
  </div>
141
136
  <DocxEditorReview.Draft :top="metrics.top + (selectionAnchorY ?? 0) * metrics.scale" />
142
137
  </template>
@@ -157,22 +152,43 @@ import MyCards from './MyCards.vue';
157
152
  </template>
158
153
  ```
159
154
 
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.
155
+ A plain `Map` is not reactive. Use `shallowReactive(new Map())`, or a ref that you replace on each change.
156
+
157
+ `accept` returns `false` when the editor refuses, and `lastRefusal` names the item and the diagnostic code. `DocxEditorReview` already announces each refusal in a live region, so show it in the card as plain text. For a translated sentence, call `useReviewRefusalMessage()`.
158
+
159
+ ## Position cards and drafts
160
+
161
+ Convert a layout position to CSS pixels with `metrics.top + position * metrics.scale`.
162
+
163
+ | Input to `useStackedReviewPositions` | Description |
164
+ | --- | --- |
165
+ | `heights` | Measured card heights in CSS pixels, by item key. A card without a height reserves `defaultHeight`, and a taller card overlaps the next one. |
166
+ | `scale` | `metrics.scale`, CSS pixels per layout point. |
167
+ | `defaultHeight` | The height to reserve before a card is measured. |
168
+
169
+ `DocxEditorReview.Draft` renders only while a comment draft is open. Without `top`, it takes the packaged stack's position, so pass `top` from your own stack. To keep the box clear of your cards, add an entry at `selectionAnchorY` to the list that you stack, in document order.
161
170
 
162
- ## Root parts and card parts
171
+ ## Place review parts
163
172
 
164
- `DocxEditorReview` finds its parts among its own children by element type.
173
+ `DocxEditorReview` finds its parts among its own children by element type. Fragments are transparent in React and Vue.
165
174
 
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`.
175
+ | Part | Placement |
176
+ | --- | --- |
177
+ | `List`, `Markers`, `AddComment`, `Balloon` | Direct children of `DocxEditorReview`. Inside your own wrapper, a part renders as plain content without its geometry. |
178
+ | `Draft` | A direct child, or inside your own card component. Inside a custom stack, pass `top` explicitly. |
179
+ | `Card`, `Empty`, and card parts such as `Author`, `Summary`, `Accept`, and `Reply` | Inside `DocxEditorReview.List`. |
168
180
 
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.
181
+ With `preset` set to `false`, the rail adds no defaults, but recognized parts keep their wiring. Other children render inside the rail element.
170
182
 
171
- ## Position the compose box
183
+ ## Limits
172
184
 
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:
185
+ - Disconnect each observer when its card unmounts or its item changes.
186
+ - In React, keep the heights map reference-stable. In Vue, keep it reactive.
187
+ - In the compact rail, `metrics.compact` is `true`. Place cards at `metrics.compactCardLeft`; `Draft` does this itself.
188
+ - Keep the column hidden while `metrics.left` is `null`.
174
189
 
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.
190
+ ## See also
177
191
 
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.
192
+ - [Review cards](https://www.docx-editor.dev/docs/2.x/customization/review-cards)
193
+ - [Tracked changes](https://www.docx-editor.dev/docs/2.x/pro/tracked-changes)
194
+ - [Accept or reject without scrolling](./accept-reject-without-scrolling.md)
@@ -2,7 +2,11 @@
2
2
 
3
3
  # Highlight ranges
4
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.
5
+ Call `useHighlights(name, source, options)` to mark text as a named set while a component is mounted. Highlights are local view state. They do not change the document and are not saved.
6
+
7
+ ## Highlight matching text
8
+
9
+ The following component marks each whole-word match of a list of terms. Style the `glossary-term` class with your own CSS, or omit `className` for the default fill.
6
10
 
7
11
  **React**
8
12
 
@@ -11,6 +15,7 @@ import { useCallback } from 'react';
11
15
  import { useHighlights, type Editor } from '@docx-editor.dev/react';
12
16
 
13
17
  export function GlossaryMarks({ terms }: { terms: readonly string[] }) {
18
+ // A stable function source, so the set refreshes only when `terms` changes.
14
19
  const findTerms = useCallback(
15
20
  (editor: Editor) => editor.findMatches(terms, { wholeWord: true }).flat(),
16
21
  [terms]
@@ -20,8 +25,6 @@ export function GlossaryMarks({ terms }: { terms: readonly string[] }) {
20
25
  }
21
26
  ```
22
27
 
23
- Keep a function source stable with `useCallback`.
24
-
25
28
  **Vue**
26
29
 
27
30
  ```vue
@@ -29,6 +32,7 @@ Keep a function source stable with `useCallback`.
29
32
  import { useHighlights, type Editor } from '@docx-editor.dev/vue';
30
33
 
31
34
  const props = defineProps<{ terms: readonly string[] }>();
35
+ // A source that reads props or refs runs again when they change.
32
36
  const result = useHighlights(
33
37
  'glossary',
34
38
  (editor: Editor) => editor.findMatches(props.terms, { wholeWord: true }).flat(),
@@ -41,8 +45,24 @@ const result = useHighlights(
41
45
  </template>
42
46
  ```
43
47
 
44
- A function source that reads props or refs runs again when they change.
48
+ A function source runs again after each document change, so the marks follow the text. The set is removed when the component unmounts.
49
+
50
+ ## Configure a highlight set
51
+
52
+ | Argument or field | Description |
53
+ | --- | --- |
54
+ | `name` | The set name. Two sets with the same name replace each other's ranges. |
55
+ | `source` | The ranges, or a function of the editor that returns them. |
56
+ | `options.className` | CSS classes added to every mark of the set. |
57
+ | `options.activeIndex` | The range to show in the active color, such as the current result of your own search list. |
58
+ | `applied` | The number of ranges that resolve and paint. |
59
+
60
+ ## Use highlights outside components
61
+
62
+ Call `watchHighlights(editor, name, source, options)` from `@docx-editor.dev/core/editor` to follow document changes. Call `stop()` on the returned watch to remove the set and stop watching. For a fixed set that does not refresh, call `editor.setHighlights`.
45
63
 
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.
64
+ ## See also
47
65
 
48
- Outside a component, call `watchHighlights(editor, name, source, options)` from `@docx-editor.dev/core/editor`, or `editor.setHighlights` for a fixed set.
66
+ - [Highlight text ranges](https://www.docx-editor.dev/docs/2.x/guides/navigation#highlight-text-ranges)
67
+ - [React hooks](https://www.docx-editor.dev/docs/2.x/react/hooks#usehighlights)
68
+ - [Vue composables](https://www.docx-editor.dev/docs/2.x/vue/composables#usehighlights)
package/docs/index.md CHANGED
@@ -1,36 +1,36 @@
1
1
  <!-- Generated from docs/site/content/recipes by scripts/sync-agent-docs.mjs. Edit the source and run `bun run docs:agent`. -->
2
2
 
3
- # Which API for which job
3
+ # Task recipes
4
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.
5
+ Each recipe solves one task with a complete code sample. Choose the API first, then find the symbol for your task.
6
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. |
7
+ ## Which API for which job
11
8
 
12
- | Task | Browser host (React or Vue) | Headless, server, or agent |
9
+ Use the React or Vue adapter hooks, the review hooks, and `DocxEditorReview` from `@docx-editor.dev/pro/react` or `@docx-editor.dev/pro/vue` to build browser review controls. Use the editing API in `@docx-editor.dev/editor-api` for document automation. It follows the Office.js object model and runs in browser and server hosts.
10
+
11
+ | Task | Browser controls (React or Vue) | Document automation |
13
12
  | --- | --- | --- |
14
13
  | [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)` |
14
+ | [Reply to a thread](https://www.docx-editor.dev/docs/2.x/pro/comments) | `useReview().reply(item, text)` | `Comment.reply(text)` |
16
15
  | [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` |
16
+ | [Propose an AI rewrite as tracked changes](./propose-rewrite.md) | `prepareRewrite` through `DocxEditor.createBrowser` | `prepareRewrite` from `@docx-editor.dev/editor-api/agent` |
18
17
  | [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 |
18
+ | [Open a change without scrolling](./accept-reject-without-scrolling.md) | `useReview().setActive(key, { reveal: false })` | Not applicable: no viewport |
20
19
  | [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 |
20
+ | [Custom review rail cards](./custom-review-cards.md) | `DocxEditorReview` with `preset` set to `false`, `useStackedReviewPositions`, and `useReviewRailMetrics` | Not applicable |
22
21
  | 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` |
22
+ | [Page setup and paragraph dialogs](https://www.docx-editor.dev/docs/2.x/customization/dialogs) | `DocxEditor.PageSetupDialog` or `DocxEditor.ParagraphDialog` | `Section.pageSetup`, and `Paragraph` properties such as `alignment` |
24
23
 
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`.
24
+ ## Check prerequisites
26
25
 
27
- ## Recipes
26
+ - The review hooks need `reviewModule()` from `@docx-editor.dev/pro` in the editor's modules.
27
+ - Comments and tracked changes need an author: the editor's `author` prop, or the runtime's `author` option.
28
+ - Create a server runtime with `DocxEditor.createServer` from `@docx-editor.dev/editor-api`. Create a browser runtime with `DocxEditor.createBrowser` from `@docx-editor.dev/editor-api/browser`.
29
+ - Review controls stay separate from the Office.js-shaped document model.
30
+ - `@docx-editor.dev/pro` and `@docx-editor.dev/editor-api` require the EigenPal Pro License. For more information, see [Pro features](https://www.docx-editor.dev/docs/2.x/pro).
28
31
 
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)
32
+ ## See also
35
33
 
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).
34
+ - [React hooks](https://www.docx-editor.dev/docs/2.x/react/hooks)
35
+ - [Vue composables](https://www.docx-editor.dev/docs/2.x/vue/composables)
36
+ - [Editing API](https://www.docx-editor.dev/docs/2.x/editor-api)