@stll/folio-core 0.60.0 → 0.62.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 (173) hide show
  1. package/README.md +13 -0
  2. package/dist/ai-edits/apply.js +139 -32
  3. package/dist/ai-edits/headless.d.ts +3 -0
  4. package/dist/ai-edits/headless.js +70 -75
  5. package/dist/ai-edits/note-story-inventory.d.ts +7 -0
  6. package/dist/ai-edits/note-story-inventory.js +11 -0
  7. package/dist/ai-edits/read.js +16 -9
  8. package/dist/ai-edits/revisionStretches.js +52 -1
  9. package/dist/ai-edits/table-row-column-mutations.js +6 -1
  10. package/dist/compare/reproducible-package.js +2 -1
  11. package/dist/compare/verification.js +1 -0
  12. package/dist/compat/eigenpal.d.ts +3 -1
  13. package/dist/controller/canonicalClipboard.js +2 -1
  14. package/dist/controller/canonicalListRendering.d.ts +4 -6
  15. package/dist/controller/canonicalListRendering.js +3 -157
  16. package/dist/controller/headerFooterEditorManager.d.ts +1 -0
  17. package/dist/controller/headerFooterEditorManager.js +11 -0
  18. package/dist/controller/hiddenEditorManager.d.ts +1 -1
  19. package/dist/controller/hiddenEditorManager.js +3 -1
  20. package/dist/controller/layoutPipeline.d.ts +3 -1
  21. package/dist/controller/layoutPipeline.js +13 -4
  22. package/dist/controller/layoutSession.d.ts +2 -1
  23. package/dist/controller/noteEditorManager.d.ts +1 -1
  24. package/dist/display-list/build/imagePrimitives.js +28 -12
  25. package/dist/display-list/build/paragraphPrimitives.js +15 -2
  26. package/dist/display-list/dom/renderDisplayListToDom.js +8 -5
  27. package/dist/display-list/types.d.ts +7 -3
  28. package/dist/docx/canonicalStoryRepack.js +3 -2
  29. package/dist/docx/commentTextParagraph.d.ts +6 -0
  30. package/dist/docx/commentTextParagraph.js +15 -0
  31. package/dist/docx/conformance.js +6 -1
  32. package/dist/docx/diagramPreview.js +2 -1
  33. package/dist/docx/documentParser.d.ts +13 -1
  34. package/dist/docx/documentParser.js +1 -1
  35. package/dist/docx/encryption/compoundFile.d.ts +0 -8
  36. package/dist/docx/encryption/compoundFile.js +2 -1
  37. package/dist/docx/encryption/cryptoBytes.d.ts +2 -2
  38. package/dist/docx/encryption/cryptoBytes.js +3 -18
  39. package/dist/docx/encryption/encryptionInfo.d.ts +0 -5
  40. package/dist/docx/encryption/encryptionInfo.js +2 -1
  41. package/dist/docx/ensureParaIds.js +2 -1
  42. package/dist/docx/groupDrawingParser.js +28 -13
  43. package/dist/docx/listModelNormalization.d.ts +10 -0
  44. package/dist/docx/listModelNormalization.js +166 -0
  45. package/dist/docx/metadataPrivacy.js +3 -2
  46. package/dist/docx/metafileRaster.js +2 -1
  47. package/dist/docx/packageParts.js +6 -5
  48. package/dist/docx/paragraphParser.js +6 -4
  49. package/dist/docx/parser.js +5 -4
  50. package/dist/docx/preservedRunContent.js +11 -2
  51. package/dist/docx/removeHeaderFooterParts.js +8 -7
  52. package/dist/docx/replyToComment.js +3 -13
  53. package/dist/docx/rezip.js +88 -89
  54. package/dist/docx/runParser.js +12 -6
  55. package/dist/docx/runPropertyPresence.d.ts +11 -0
  56. package/dist/docx/runPropertyPresence.js +14 -0
  57. package/dist/docx/selectiveSave.js +15 -14
  58. package/dist/docx/serializer/runSerializer.js +3 -2
  59. package/dist/docx/serializer/tableSerializer.js +2 -1
  60. package/dist/docx/server/applyDocxXmlPatchProposal.js +2 -2
  61. package/dist/docx/server/boundedArchive.js +2 -1
  62. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +2 -1
  63. package/dist/docx/server/inspectDocxPackage.js +5 -1
  64. package/dist/docx/server/validateDocxConformance.js +2 -1
  65. package/dist/docx/tableParser.js +7 -4
  66. package/dist/docx/tableRowPropertyPresence.d.ts +9 -0
  67. package/dist/docx/tableRowPropertyPresence.js +15 -0
  68. package/dist/docx/unzip.js +2 -1
  69. package/dist/docx/xmlEncoding.js +6 -5
  70. package/dist/fonts/fontsourceFaces.js +2 -1
  71. package/dist/fonts/sfnt/parse.js +6 -9
  72. package/dist/fonts/sfnt/subset.js +2 -1
  73. package/dist/fonts/sfnt/tables.js +2 -3
  74. package/dist/fonts/sfnt/woff.js +2 -1
  75. package/dist/i18n/messages/ar.gen.js +1 -0
  76. package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
  77. package/dist/i18n/messages/catalogs.gen.js +17 -0
  78. package/dist/i18n/messages/cs.gen.js +1 -0
  79. package/dist/i18n/messages/de.gen.js +1 -0
  80. package/dist/i18n/messages/en.gen.js +1 -0
  81. package/dist/i18n/messages/es.gen.js +1 -0
  82. package/dist/i18n/messages/et.gen.js +1 -0
  83. package/dist/i18n/messages/fr.gen.js +1 -0
  84. package/dist/i18n/messages/he.gen.js +1 -0
  85. package/dist/i18n/messages/hi.gen.js +1 -0
  86. package/dist/i18n/messages/hu.gen.js +1 -0
  87. package/dist/i18n/messages/lt.gen.js +1 -0
  88. package/dist/i18n/messages/lv.gen.js +1 -0
  89. package/dist/i18n/messages/messages.gen.d.ts +1 -0
  90. package/dist/i18n/messages/pl.gen.js +1 -0
  91. package/dist/i18n/messages/pt-BR.gen.js +1 -0
  92. package/dist/i18n/messages/sk.gen.js +1 -0
  93. package/dist/i18n/messages/tr.gen.js +1 -0
  94. package/dist/i18n/messages/zh-CN.gen.js +1 -0
  95. package/dist/index.d.ts +3 -1
  96. package/dist/internal/revisionResolutionInline.d.ts +2 -1
  97. package/dist/internal/revisionResolutionInline.js +22 -1
  98. package/dist/internal/wholeStoryRevisionResolution.js +24 -7
  99. package/dist/layout-bridge/convert/endnoteLayout.d.ts +1 -1
  100. package/dist/layout-bridge/convert/endnoteLayout.js +1 -3
  101. package/dist/layout-bridge/convert/flowConversionShared.d.ts +2 -0
  102. package/dist/layout-bridge/convert/footnoteLayout.d.ts +4 -0
  103. package/dist/layout-bridge/convert/footnoteLayout.js +13 -3
  104. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +5 -0
  105. package/dist/layout-bridge/convert/headerFooterLayout.js +12 -15
  106. package/dist/layout-bridge/convert/paragraphRuns.js +2 -1
  107. package/dist/layout-engine/index.js +2 -2
  108. package/dist/layout-engine/measure/crossRunWords.js +12 -2
  109. package/dist/layout-engine/noteAreaFlow.d.ts +4 -4
  110. package/dist/layout-engine/noteAreaFlow.js +12 -6
  111. package/dist/layout-engine/types.d.ts +5 -2
  112. package/dist/layout-painter/index.d.ts +1 -1
  113. package/dist/managers/AutoSaveManager.js +2 -1
  114. package/dist/managers/EditorModeManager.d.ts +10 -1
  115. package/dist/managers/EditorModeManager.js +15 -1
  116. package/dist/managers/FindReplaceManager.d.ts +13 -6
  117. package/dist/managers/FindReplaceManager.js +15 -9
  118. package/dist/managers/TableSelectionManager.d.ts +1 -1
  119. package/dist/managers/autoSaveCodec.js +2 -6
  120. package/dist/managers/reviewDisplayMode.d.ts +8 -0
  121. package/dist/managers/reviewDisplayMode.js +15 -0
  122. package/dist/paged-layout/editorScrollRoot.d.ts +3 -1
  123. package/dist/paged-layout/editorScrollRoot.js +2 -2
  124. package/dist/paged-layout/pageText.d.ts +11 -2
  125. package/dist/paged-layout/pageText.js +8 -3
  126. package/dist/pdf/fonts.js +5 -14
  127. package/dist/pdf/images.js +4 -2
  128. package/dist/pdf/objects.d.ts +0 -11
  129. package/dist/pdf/objects.js +7 -9
  130. package/dist/pdf/writePdf.js +2 -1
  131. package/dist/prosemirror/commands/comments.js +51 -13
  132. package/dist/prosemirror/commentRangeInlineAtoms.d.ts +40 -0
  133. package/dist/prosemirror/commentRangeInlineAtoms.js +33 -0
  134. package/dist/prosemirror/containerFinalParagraph.d.ts +8 -8
  135. package/dist/prosemirror/containerFinalParagraph.js +8 -8
  136. package/dist/prosemirror/conversion/fromProseDoc.js +14 -8
  137. package/dist/prosemirror/conversion/toProseDoc.js +16 -23
  138. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +16 -2
  139. package/dist/prosemirror/fieldRepresentation.js +4 -2
  140. package/dist/prosemirror/findReplaceSelection.d.ts +48 -2
  141. package/dist/prosemirror/findReplaceSelection.js +153 -38
  142. package/dist/prosemirror/inlineDisplayText.d.ts +10 -0
  143. package/dist/prosemirror/inlineDisplayText.js +29 -0
  144. package/dist/prosemirror/markupViewProjection.d.ts +10 -1
  145. package/dist/prosemirror/markupViewProjection.js +21 -3
  146. package/dist/prosemirror/plugins/transactionInvariants.js +6 -1
  147. package/dist/prosemirror/replacedAnnotations.d.ts +15 -1
  148. package/dist/prosemirror/replacedAnnotations.js +71 -12
  149. package/dist/prosemirror/runFormattingReconciliation.d.ts +3 -1
  150. package/dist/prosemirror/runFormattingReconciliation.js +10 -2
  151. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +5 -7
  152. package/dist/prosemirror/runIdentityAcrossRevisions.js +66 -38
  153. package/dist/prosemirror/runPropertyChangeResolution.js +13 -1
  154. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
  155. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
  156. package/dist/utils/base64.d.ts +1 -31
  157. package/dist/utils/base64.js +19 -33
  158. package/dist/utils/clipboard.js +5 -4
  159. package/dist/utils/colorResolver.js +11 -2
  160. package/dist/utils/findMatchView.d.ts +32 -0
  161. package/dist/utils/findMatchView.js +406 -0
  162. package/dist/utils/findReplace.d.ts +67 -4
  163. package/dist/utils/findReplace.js +359 -37
  164. package/dist/utils/findSearchKey.d.ts +29 -0
  165. package/dist/utils/findSearchKey.js +34 -0
  166. package/dist/utils/replaceText.js +138 -94
  167. package/dist/utils/sliceText.d.ts +2 -0
  168. package/dist/utils/sliceText.js +2 -0
  169. package/dist/utils/textEncoding.d.ts +2 -0
  170. package/dist/utils/textEncoding.js +2 -0
  171. package/dist/utils/tiffConverter.d.ts +0 -9
  172. package/dist/utils/tiffConverter.js +2 -4
  173. package/package.json +51 -25
@@ -58,7 +58,6 @@ function prepareEndnoteAreas(input) {
58
58
  notesBySection.set(section, noteIds);
59
59
  }
60
60
  const separatorBlockIds = /* @__PURE__ */ new Map();
61
- const contentBlockIds = /* @__PURE__ */ new Set();
62
61
  const noteStoryByBlockId = /* @__PURE__ */ new Map();
63
62
  const areas = [];
64
63
  for (const [section, noteIds] of [...notesBySection].sort(([left], [right]) => left - right)) {
@@ -82,7 +81,6 @@ function prepareEndnoteAreas(input) {
82
81
  const id = `endnote-${String(noteId)}-${String(index)}`;
83
82
  blocks.push(withId(stripFlowBlockPmAnchors(block), id));
84
83
  measures.push(content.measures[index]);
85
- contentBlockIds.add(id);
86
84
  noteStoryByBlockId.set(id, {
87
85
  kind: "endnote",
88
86
  noteId
@@ -101,7 +99,7 @@ function prepareEndnoteAreas(input) {
101
99
  areas,
102
100
  noteAreas: {
103
101
  separatorBlockIds,
104
- contentBlockIds,
102
+ noteStoryByBlockId,
105
103
  continuationSeparator
106
104
  },
107
105
  noteStoryByBlockId
@@ -9,6 +9,8 @@ import { Node } from "prosemirror-model";
9
9
  * Options for the conversion.
10
10
  */
11
11
  type ToFlowBlocksOptions = {
12
+ /** Paint comment anchors and highlights; false leaves authored marks intact. */
13
+ showCommentHighlights?: boolean;
12
14
  /** Embedded families owned by this editor, never ambient process state. */
13
15
  embeddedFontFamilyMap?: ReadonlyMap<string, string>;
14
16
  /** Default font family. */
@@ -1,10 +1,14 @@
1
1
  import { document_d_exports } from "../../types/document.js";
2
+ import { DisplayMode } from "../../managers/EditorModeManager.js";
2
3
  import { FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FlowBlock, FootnoteContent, Measure, Page } from "../../layout-engine/types.js";
3
4
  import { ToFlowBlocksOptions } from "./flowConversionShared.js";
4
5
  import "./toFlowBlocks.js";
5
6
  //#region src/layout-bridge/convert/footnoteLayout.d.ts
6
7
  type MeasureBlocksFn = (blocks: FlowBlock[], contentWidth: number) => Measure[];
7
8
  type ConvertFootnoteOptions = Pick<ToFlowBlocksOptions, "embeddedFontFamilyMap"> & {
9
+ /** Whether comment highlights are painted. Defaults to true. */
10
+ showCommentHighlights?: boolean;
11
+ markupView?: DisplayMode;
8
12
  styles?: document_d_exports.StyleDefinitions | null;
9
13
  numbering?: document_d_exports.NumberingDefinitions;
10
14
  theme?: document_d_exports.Theme | null;
@@ -2,6 +2,8 @@ import { measureParagraph } from "../../layout-engine/measure/measureParagraph.j
2
2
  import { layoutTextBoxContent } from "../../layout-engine/measure/textBoxParagraphLayout.js";
3
3
  import { DEFAULT_TEXTBOX_MARGINS, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, isFloatingImageRun, resolveTableWidthPx } from "../../layout-engine/types.js";
4
4
  import { footnoteToProseDoc } from "../../prosemirror/conversion/toProseDoc.js";
5
+ import { projectStoryMarkupView } from "../../prosemirror/markupViewProjection.js";
6
+ import { remapMarkupViewBlocks } from "./markupViewFlow.js";
5
7
  import { collectNoteRefs } from "./noteReferences.js";
6
8
  import { toFlowBlocks } from "./toFlowBlocks.js";
7
9
  //#region src/layout-bridge/convert/footnoteLayout.ts
@@ -204,7 +206,7 @@ function convertNoteStoryToFlowBlocks(content, options, referenceMarkText) {
204
206
  if (options.theme !== void 0) proseOptions.theme = options.theme;
205
207
  if (options.numbering !== void 0) proseOptions.numbering = options.numbering;
206
208
  const pmDoc = footnoteToProseDoc(content, proseOptions);
207
- const flowOptions = {};
209
+ const flowOptions = { showCommentHighlights: options.showCommentHighlights !== false && options.markupView !== "no-markup" && options.markupView !== "original" };
208
210
  if (options.defaultSize !== void 0) flowOptions.defaultSize = options.defaultSize;
209
211
  if (options.styles) flowOptions.styles = options.styles;
210
212
  if (options.theme !== void 0) flowOptions.theme = options.theme;
@@ -215,10 +217,18 @@ function convertNoteStoryToFlowBlocks(content, options, referenceMarkText) {
215
217
  if (options.justificationCompatibility) flowOptions.justificationCompatibility = options.justificationCompatibility;
216
218
  if (options.tableIndentCompatibility) flowOptions.tableIndentCompatibility = options.tableIndentCompatibility;
217
219
  if (options.automaticHyphenation) flowOptions.automaticHyphenation = options.automaticHyphenation;
218
- const hasReferenceMark = containsNoteReferenceMark(pmDoc);
220
+ const projection = projectStoryMarkupView({
221
+ doc: pmDoc,
222
+ markupView: options.markupView ?? "all-markup",
223
+ styles: options.styles,
224
+ numbering: options.numbering
225
+ });
226
+ const viewDoc = projection.type === "resolved" ? projection.doc : pmDoc;
227
+ const hasReferenceMark = containsNoteReferenceMark(viewDoc);
219
228
  if (hasReferenceMark && referenceMarkText !== void 0) flowOptions.noteReferenceMarkText = referenceMarkText;
229
+ const converted = toFlowBlocks(viewDoc, flowOptions);
220
230
  return {
221
- flowBlocks: preserveAuthoredFootnoteTerminalParagraph(toFlowBlocks(pmDoc, flowOptions)),
231
+ flowBlocks: preserveAuthoredFootnoteTerminalParagraph(projection.type === "resolved" ? remapMarkupViewBlocks(converted, projection) : converted),
222
232
  hasReferenceMark
223
233
  };
224
234
  }
@@ -1,4 +1,5 @@
1
1
  import { document_d_exports } from "../../types/document.js";
2
+ import { DisplayMode } from "../../managers/EditorModeManager.js";
2
3
  import { FlowBlock, HeaderFooterContent, ImageRun, ImageRunPosition, Measure, PageMargins } from "../../layout-engine/types.js";
3
4
  import { ToFlowBlocksOptions } from "./flowConversionShared.js";
4
5
  import "./toFlowBlocks.js";
@@ -40,6 +41,10 @@ declare function calculateHeaderFooterMarginPushBounds(blocks: FlowBlock[], meas
40
41
  */
41
42
  declare function calculateHeaderFooterBodyTopClearance(blocks: FlowBlock[], flowHeight: number, metrics: HeaderFooterMetrics): number | undefined;
42
43
  type ConvertHeaderFooterOptions = {
44
+ /** Review projection applied before header/footer measurement. Defaults to All Markup. */
45
+ markupView?: DisplayMode;
46
+ /** Whether comment highlights are painted. Defaults to true. */
47
+ showCommentHighlights?: boolean;
43
48
  styles?: document_d_exports.StyleDefinitions | null;
44
49
  numbering?: document_d_exports.NumberingDefinitions;
45
50
  theme?: document_d_exports.Theme | null;
@@ -1,7 +1,9 @@
1
1
  import { cloneParagraphWithPropertySource } from "../../docx/paragraphPropertySource.js";
2
2
  import { isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun } from "../../layout-engine/types.js";
3
3
  import { headerFooterToProseDoc } from "../../prosemirror/conversion/toProseDoc.js";
4
+ import { projectStoryMarkupView } from "../../prosemirror/markupViewProjection.js";
4
5
  import { emuToPixels } from "../../utils/units.js";
6
+ import { remapMarkupViewBlocks } from "./markupViewFlow.js";
5
7
  import { toFlowBlocks } from "./toFlowBlocks.js";
6
8
  //#region src/layout-bridge/convert/headerFooterLayout.ts
7
9
  const DETACHED_WATERMARK_HOST = Symbol.for("stll.detachedWatermarkHost");
@@ -341,19 +343,7 @@ function convertHeaderFooterToContent(headerFooter, contentWidth, metrics, optio
341
343
  if (options.styles) proseDocOptions.styles = options.styles;
342
344
  if (options.theme !== void 0) proseDocOptions.theme = options.theme;
343
345
  if (options.numbering !== void 0) proseDocOptions.numbering = options.numbering;
344
- const pmDoc = headerFooter.watermarkBlockIndex === void 0 ? headerFooterToProseDoc(headerFooter.content, proseDocOptions) : headerFooterToProseDocWithDetachedWatermarkHost(headerFooter, proseDocOptions);
345
- const flowOptions = {};
346
- if (options.defaultSize !== void 0) flowOptions.defaultSize = options.defaultSize;
347
- if (options.styles) flowOptions.styles = options.styles;
348
- if (options.theme !== void 0) flowOptions.theme = options.theme;
349
- if (options.embeddedFontFamilyMap !== void 0) flowOptions.embeddedFontFamilyMap = options.embeddedFontFamilyMap;
350
- if (options.fontAlternates !== void 0) flowOptions.fontAlternates = options.fontAlternates;
351
- if (options.defaultTabStopTwips !== void 0) flowOptions.defaultTabStopTwips = options.defaultTabStopTwips;
352
- if (options.lineBreakRules) flowOptions.lineBreakRules = options.lineBreakRules;
353
- if (options.justificationCompatibility) flowOptions.justificationCompatibility = options.justificationCompatibility;
354
- if (options.tableIndentCompatibility) flowOptions.tableIndentCompatibility = options.tableIndentCompatibility;
355
- if (options.automaticHyphenation) flowOptions.automaticHyphenation = options.automaticHyphenation;
356
- return finalizeHeaderFooterContent(toFlowBlocks(pmDoc, flowOptions), contentWidth, metrics, options);
346
+ return convertHeaderFooterPmDocToContent(headerFooter.watermarkBlockIndex === void 0 ? headerFooterToProseDoc(headerFooter.content, proseDocOptions) : headerFooterToProseDocWithDetachedWatermarkHost(headerFooter, proseDocOptions), contentWidth, metrics, options);
357
347
  }
358
348
  /**
359
349
  * Same as {@link convertHeaderFooterToContent}, but sourced from a live
@@ -368,7 +358,7 @@ function convertHeaderFooterToContent(headerFooter, contentWidth, metrics, optio
368
358
  */
369
359
  function convertHeaderFooterPmDocToContent(pmDoc, contentWidth, metrics, options) {
370
360
  if (!pmDoc || pmDoc.content.size === 0) return;
371
- const flowOptions = {};
361
+ const flowOptions = { showCommentHighlights: options.showCommentHighlights !== false && options.markupView !== "no-markup" && options.markupView !== "original" };
372
362
  if (options.defaultSize !== void 0) flowOptions.defaultSize = options.defaultSize;
373
363
  if (options.styles) flowOptions.styles = options.styles;
374
364
  if (options.theme !== void 0) flowOptions.theme = options.theme;
@@ -379,7 +369,14 @@ function convertHeaderFooterPmDocToContent(pmDoc, contentWidth, metrics, options
379
369
  if (options.justificationCompatibility) flowOptions.justificationCompatibility = options.justificationCompatibility;
380
370
  if (options.tableIndentCompatibility) flowOptions.tableIndentCompatibility = options.tableIndentCompatibility;
381
371
  if (options.automaticHyphenation) flowOptions.automaticHyphenation = options.automaticHyphenation;
382
- return finalizeHeaderFooterContent(toFlowBlocks(pmDoc, flowOptions), contentWidth, metrics, options);
372
+ const projection = projectStoryMarkupView({
373
+ doc: pmDoc,
374
+ markupView: options.markupView ?? "all-markup",
375
+ styles: options.styles,
376
+ numbering: options.numbering
377
+ });
378
+ const flowBlocks = toFlowBlocks(projection.type === "resolved" ? projection.doc : pmDoc, flowOptions);
379
+ return finalizeHeaderFooterContent(projection.type === "resolved" ? remapMarkupViewBlocks(flowBlocks, projection) : flowBlocks, contentWidth, metrics, options);
383
380
  }
384
381
  function horizontalImageLeft({ run, contentWidth, metrics }) {
385
382
  const horizontal = run.position?.horizontal;
@@ -58,7 +58,8 @@ function paragraphToRuns(node, startPos, _options, pageBreaks) {
58
58
  });
59
59
  return;
60
60
  }
61
- const effectiveMarks = mergeProjectedInlineMarks(inheritedMarks, child.marks);
61
+ const projectedMarks = mergeProjectedInlineMarks(inheritedMarks, child.marks);
62
+ const effectiveMarks = _options.showCommentHighlights === false ? projectedMarks.filter((mark) => mark.type.name !== "comment") : projectedMarks;
62
63
  if (child.type.name === "pageBreakRun") {
63
64
  const trackedChange = extractRunFormatting(effectiveMarks, theme, fontAlternates);
64
65
  if (trackedChange.isDeletion) {
@@ -4,7 +4,7 @@ import { layoutImage } from "./imageLayout.js";
4
4
  import { calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
5
5
  import "./layoutFlowShared.js";
6
6
  import { measuredLineAdvance } from "./lineFlow.js";
7
- import { annotateNoteSeparators, continuesNoteArea, createNoteAreaContinuation } from "./noteAreaFlow.js";
7
+ import { annotateNoteAreas, continuesNoteArea, createNoteAreaContinuation } from "./noteAreaFlow.js";
8
8
  import { createPaginator } from "./paginator.js";
9
9
  import { layoutParagraph } from "./paragraphLayout.js";
10
10
  import { paragraphsShareStyle, resolveEffectiveParagraphSpacingTree } from "./paragraphSpacing.js";
@@ -63,7 +63,7 @@ function applyContextualSpacing(blocks) {
63
63
  */
64
64
  function layoutDocument(blocks, measures, options) {
65
65
  const initialLayout = layoutDocumentPass(blocks, measures, options);
66
- return annotateNoteSeparators(applySectionVerticalAlignment(options.footnoteHeightById ? reflowFootnoteColumns({
66
+ return annotateNoteAreas(applySectionVerticalAlignment(options.footnoteHeightById ? reflowFootnoteColumns({
67
67
  initialLayout,
68
68
  ...options.footnoteReservedHeights ? { initialReserveFloors: options.footnoteReservedHeights } : {},
69
69
  runLayout: (reserveFloors) => layoutDocumentPass(blocks, measures, {
@@ -1,3 +1,4 @@
1
+ import { sliceText } from "../../utils/sliceText.js";
1
2
  import { splitTrailingToken } from "../../utils/trailingText.js";
2
3
  import { resolveEffectiveLineBreakPolicy } from "./effectiveLineBreakPolicy.js";
3
4
  import "./lineBreakProvider.js";
@@ -47,7 +48,11 @@ function computeFollowingTextLeads(block) {
47
48
  leads[index] = lead;
48
49
  const run = block.runs[index];
49
50
  if (!run || !isTextRun(run)) lead = void 0;
50
- else if (run.text) lead = run.text[0];
51
+ else if (run.text) lead = sliceText({
52
+ text: run.text,
53
+ units: "codePoints",
54
+ end: 1
55
+ });
51
56
  }
52
57
  return leads;
53
58
  }
@@ -101,6 +106,7 @@ function computeProtectedCrossRunGlueWidths(block) {
101
106
  * invoked from, without the per-word text-length budget ever kicking in.
102
107
  */
103
108
  const MAX_CROSS_RUN_SEGMENTS = 64;
109
+ const MAX_SCALAR_CODE_UNITS = 2;
104
110
  /** Collect one lexical word continued through adjacent formatting runs. */
105
111
  function collectCrossRunWord({ block, startRunIndex, startChar }) {
106
112
  const segments = [];
@@ -115,7 +121,11 @@ function collectCrossRunWord({ block, startRunIndex, startChar }) {
115
121
  if (remainder.length === 0) continue;
116
122
  if (runIndex !== startRunIndex && isSpaceOrTab(remainder[0])) break;
117
123
  const remainingBudget = 256 - text.length;
118
- const boundedRemainder = remainder.slice(0, remainingBudget + 1);
124
+ const boundedRemainder = sliceText({
125
+ text: remainder,
126
+ units: "codeUnits",
127
+ end: remainingBudget + MAX_SCALAR_CODE_UNITS
128
+ });
119
129
  const firstBreak = findWordBreaks(boundedRemainder, resolveEffectiveLineBreakPolicy({
120
130
  attrs: block.attrs,
121
131
  run
@@ -10,10 +10,10 @@ declare function createNoteAreaContinuation(noteAreas: NoteAreaLayout | undefine
10
10
  /** Whether laying out `block` continues a note area onto any page it opens. */
11
11
  declare function continuesNoteArea(noteAreas: NoteAreaLayout | undefined, block: FlowBlock): boolean;
12
12
  /**
13
- * Record where each separator mark landed. A rule is centred on the first line
14
- * of the separator paragraph: a `w:separator` spans part of the column and a
13
+ * Record note identities and where each separator mark landed. A rule is centred
14
+ * on the first line of the separator paragraph: a `w:separator` spans part of the column and a
15
15
  * `w:continuationSeparator` all of it.
16
16
  */
17
- declare function annotateNoteSeparators(layout: Layout, blocks: readonly FlowBlock[], measures: readonly Measure[], noteAreas: NoteAreaLayout | undefined): Layout;
17
+ declare function annotateNoteAreas(layout: Layout, blocks: readonly FlowBlock[], measures: readonly Measure[], noteAreas: NoteAreaLayout | undefined): Layout;
18
18
  //#endregion
19
- export { annotateNoteSeparators, continuesNoteArea, createNoteAreaContinuation };
19
+ export { annotateNoteAreas, continuesNoteArea, createNoteAreaContinuation };
@@ -39,15 +39,15 @@ function createNoteAreaContinuation(noteAreas, paginator) {
39
39
  }
40
40
  /** Whether laying out `block` continues a note area onto any page it opens. */
41
41
  function continuesNoteArea(noteAreas, block) {
42
- return noteAreas?.contentBlockIds.has(String(block.id)) === true;
42
+ return noteAreas?.noteStoryByBlockId.has(String(block.id)) === true;
43
43
  }
44
44
  /**
45
- * Record where each separator mark landed. A rule is centred on the first line
46
- * of the separator paragraph: a `w:separator` spans part of the column and a
45
+ * Record note identities and where each separator mark landed. A rule is centred
46
+ * on the first line of the separator paragraph: a `w:separator` spans part of the column and a
47
47
  * `w:continuationSeparator` all of it.
48
48
  */
49
- function annotateNoteSeparators(layout, blocks, measures, noteAreas) {
50
- if (noteAreas === void 0 || noteAreas.separatorBlockIds.size === 0) return layout;
49
+ function annotateNoteAreas(layout, blocks, measures, noteAreas) {
50
+ if (noteAreas === void 0) return layout;
51
51
  const measureById = /* @__PURE__ */ new Map();
52
52
  for (const [index, block] of blocks.entries()) {
53
53
  const measure = measures[index];
@@ -56,6 +56,12 @@ function annotateNoteSeparators(layout, blocks, measures, noteAreas) {
56
56
  const continuation = noteAreas.continuationSeparator;
57
57
  if (continuation !== void 0) measureById.set(String(continuation.block.id), continuation.measure);
58
58
  for (const page of layout.pages) {
59
+ const endnoteIds = /* @__PURE__ */ new Set();
60
+ for (const fragment of page.fragments) {
61
+ const story = noteAreas.noteStoryByBlockId.get(String(fragment.blockId));
62
+ if (story?.kind === "endnote") endnoteIds.add(story.noteId);
63
+ }
64
+ if (endnoteIds.size > 0) page.endnoteIds = [...endnoteIds];
59
65
  const rules = [];
60
66
  for (const fragment of page.fragments) {
61
67
  if (fragment.kind !== "paragraph" || fragment.fromLine !== 0) continue;
@@ -80,4 +86,4 @@ function separatorWidthFraction(kind) {
80
86
  }
81
87
  }
82
88
  //#endregion
83
- export { annotateNoteSeparators, continuesNoteArea, createNoteAreaContinuation };
89
+ export { annotateNoteAreas, continuesNoteArea, createNoteAreaContinuation };
@@ -1,4 +1,5 @@
1
1
  import { OutlineStyleAttr } from "../types/documentEnumValues.js";
2
+ import { NoteStoryKey } from "../types/editor-story.js";
2
3
  import { CssBorderStyle } from "../utils/borderCss.js";
3
4
  import { ImagePosition, ImageWrap, InlineWrapper, NumberFormat, OutlineLevel, ParagraphNumberingOverride, PreviewDescriptor, SdtProperties, SdtType, SectionStart, ShapeTextBody, TableCellFormatting, TableWidthType, UnderlineStyle, VerticalAlign } from "@stll/docx-core/model";
4
5
  declare namespace types_d_exports {
@@ -1268,6 +1269,8 @@ type Page = {
1268
1269
  headerFooterRefs?: PageHeaderFooterRefs;
1269
1270
  /** Footnote IDs that appear on this page (for rendering). */
1270
1271
  footnoteIds?: number[];
1272
+ /** Endnote IDs whose content fragments appear on this page. */
1273
+ endnoteIds?: number[];
1271
1274
  /** Height reserved for the footnote area at page bottom (pixels). */
1272
1275
  footnoteReservedHeight?: number;
1273
1276
  /**
@@ -1436,10 +1439,10 @@ type NoteAreaLayout = {
1436
1439
  */
1437
1440
  separatorBlockIds: ReadonlyMap<string, NoteSeparatorKind>;
1438
1441
  /**
1439
- * Note content blocks. A page opened while one of them is laid out
1442
+ * Note identities keyed by content block. A page opened while one is laid out
1440
1443
  * continues the area, so it starts with the continuation separator.
1441
1444
  */
1442
- contentBlockIds: ReadonlySet<string>;
1445
+ noteStoryByBlockId: ReadonlyMap<string, NoteStoryKey>;
1443
1446
  /** The `w:continuationSeparator` story's first paragraph, already measured. */
1444
1447
  continuationSeparator?: {
1445
1448
  block: ParagraphBlock;
@@ -1,5 +1,5 @@
1
- import { FlowBlock, Layout, Measure } from "../layout-engine/types.js";
2
1
  import { NoteStoryKey } from "../types/editor-story.js";
2
+ import { FlowBlock, Layout, Measure } from "../layout-engine/types.js";
3
3
  import { RenderContext } from "./renderUtils.js";
4
4
  import { BlockFor, FeatureDispatchInput, FeatureFallback, FeatureModule, FeatureRenderInput, FragmentFor, MeasureFor } from "./registry/types.js";
5
5
  import { FeatureRegistry, createFeatureRegistry } from "./registry/registry.js";
@@ -1,3 +1,4 @@
1
+ import { encodeUtf8 } from "../utils/textEncoding.js";
1
2
  import { Subscribable } from "./Subscribable.js";
2
3
  import { decodeAutoSaveEnvelope, encodeAutoSaveDocument, encodeAutoSaveEnvelopeFromDocument } from "./autoSaveCodec.js";
3
4
  //#region src/managers/AutoSaveManager.ts
@@ -228,7 +229,7 @@ function getAutoSaveStorageSize(storageKey = DEFAULT_STORAGE_KEY) {
228
229
  try {
229
230
  const data = localStorage.getItem(storageKey);
230
231
  if (!data) return 0;
231
- return new Blob([data]).size;
232
+ return encodeUtf8(data).byteLength;
232
233
  } catch {
233
234
  return 0;
234
235
  }
@@ -5,6 +5,15 @@ type EditorMode = "editing" | "suggesting" | "viewing";
5
5
  /** How tracked changes render. Drives the display mode dropdown. */
6
6
  declare const DISPLAY_MODES: readonly ["all-markup", "simple-markup", "no-markup", "original"];
7
7
  type DisplayMode = (typeof DISPLAY_MODES)[number];
8
+ /** Why a user action cannot edit the displayed document. */
9
+ type EditorReadOnlyReason = "host-read-only" | "viewing-mode" | "review-display-mode";
10
+ declare const getReviewViewReadOnlyReason: (displayMode: DisplayMode) => EditorReadOnlyReason | null;
11
+ type EditorReadOnlyReasonOptions = {
12
+ editingMode: EditorMode;
13
+ readOnlyProp: boolean;
14
+ displayMode: DisplayMode;
15
+ };
16
+ declare const getEditorReadOnlyReason: ({ editingMode, readOnlyProp, displayMode }: EditorReadOnlyReasonOptions) => EditorReadOnlyReason | null;
8
17
  /** EditorModeManager snapshot. */
9
18
  type EditorModeSnapshot = {
10
19
  /** Uncontrolled internal editing mode (the host's controlled prop wins over this). */
@@ -26,4 +35,4 @@ declare class EditorModeManager extends Subscribable<EditorModeSnapshot> {
26
35
  setDisplayMode(mode: DisplayMode): void;
27
36
  }
28
37
  //#endregion
29
- export { DISPLAY_MODES, DisplayMode, EditorMode, EditorModeManager, EditorModeSnapshot, isReadOnlyMode, isTrackChangesMode, toggledTrackChangesMode };
38
+ export { DISPLAY_MODES, DisplayMode, EditorMode, EditorModeManager, EditorModeSnapshot, EditorReadOnlyReason, getEditorReadOnlyReason, getReviewViewReadOnlyReason, isReadOnlyMode, isTrackChangesMode, toggledTrackChangesMode };
@@ -18,6 +18,20 @@ const DISPLAY_MODES = [
18
18
  "no-markup",
19
19
  "original"
20
20
  ];
21
+ const REVIEW_VIEW_ACCESS = {
22
+ "all-markup": "editable",
23
+ "simple-markup": "read-only",
24
+ "no-markup": "read-only",
25
+ original: "read-only"
26
+ };
27
+ const getReviewViewReadOnlyReason = (displayMode) => REVIEW_VIEW_ACCESS[displayMode] === "read-only" ? "review-display-mode" : null;
28
+ const getEditorReadOnlyReason = ({ editingMode, readOnlyProp, displayMode }) => {
29
+ const viewReason = getReviewViewReadOnlyReason(displayMode);
30
+ if (viewReason) return viewReason;
31
+ if (readOnlyProp) return "host-read-only";
32
+ if (editingMode === "viewing") return "viewing-mode";
33
+ return null;
34
+ };
21
35
  /** True when the host opts into read-only OR the mode is `"viewing"`. */
22
36
  const isReadOnlyMode = (editingMode, readOnlyProp) => readOnlyProp || editingMode === "viewing";
23
37
  /** True when the mode is `"suggesting"`. */
@@ -47,4 +61,4 @@ var EditorModeManager = class extends Subscribable {
47
61
  }
48
62
  };
49
63
  //#endregion
50
- export { DISPLAY_MODES, EditorModeManager, isReadOnlyMode, isTrackChangesMode, toggledTrackChangesMode };
64
+ export { DISPLAY_MODES, EditorModeManager, getEditorReadOnlyReason, getReviewViewReadOnlyReason, isReadOnlyMode, isTrackChangesMode, toggledTrackChangesMode };
@@ -1,5 +1,7 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { ReplaceAllSummary, ReplaceEligibility } from "../utils/findReplace.js";
2
3
  import { FindMatchPosition } from "../prosemirror/findReplaceSelection.js";
4
+ import { OpStory } from "@stll/docx-core/ops";
3
5
  //#region src/managers/FindReplaceManager.d.ts
4
6
  type FindDirection = "next" | "previous";
5
7
  type FindReplaceResult<TMatch> = {
@@ -7,16 +9,21 @@ type FindReplaceResult<TMatch> = {
7
9
  totalCount: number;
8
10
  currentIndex: number;
9
11
  };
10
- type ReplaceAllOutcome = {
12
+ type ReplaceAllOutcome = ReplaceAllSummary & {
11
13
  document: document_d_exports.Document;
12
- replacedCount: number;
13
14
  };
14
15
  /**
15
16
  * The cursor index after stepping `direction` over `totalCount` matches,
16
17
  * wrapping at both ends. Returns 0 when there are no matches.
17
18
  */
18
19
  declare const getAdjacentFindIndex: (currentIndex: number, totalCount: number, direction: FindDirection) => number;
19
- declare class FindReplaceManager<TMatch extends FindMatchPosition> {
20
+ type ReplaceableFindPosition = FindMatchPosition & {
21
+ story?: OpStory;
22
+ textBoxIndex?: number;
23
+ commentId?: number;
24
+ replacement: ReplaceEligibility;
25
+ };
26
+ declare class FindReplaceManager<TMatch extends ReplaceableFindPosition> {
20
27
  private result;
21
28
  /** The active find result, or null when there is no active search. */
22
29
  getResult(): FindReplaceResult<TMatch> | null;
@@ -27,7 +34,7 @@ declare class FindReplaceManager<TMatch extends FindMatchPosition> {
27
34
  * first match. The result is kept even when `matches` is empty so callers can
28
35
  * surface a "no results" state.
29
36
  */
30
- setMatches(matches: TMatch[]): FindReplaceResult<TMatch>;
37
+ setMatches(matches: TMatch[], currentIndex?: number): FindReplaceResult<TMatch>;
31
38
  /**
32
39
  * Step the cursor and return the now-current match with its index, or null
33
40
  * when there are no matches to navigate.
@@ -51,8 +58,8 @@ declare class FindReplaceManager<TMatch extends FindMatchPosition> {
51
58
  replaceCurrent(document: document_d_exports.Document, replaceText: string): document_d_exports.Document | null;
52
59
  /**
53
60
  * Replace every match. Matches are replaced from the end of the document
54
- * backwards so earlier offsets stay valid as the text shifts. Clears the
55
- * active result and returns the new document plus the match count, or null
61
+ * backwards so earlier offsets stay valid as the text shifts. Ineligible
62
+ * matches are skipped and counted. Clears the active result, or returns null
56
63
  * when there is nothing to replace.
57
64
  */
58
65
  replaceAll(document: document_d_exports.Document, matches: readonly TMatch[], replaceText: string): ReplaceAllOutcome | null;
@@ -1,3 +1,4 @@
1
+ import { replaceEligibility } from "../utils/findReplace.js";
1
2
  import { replaceTextInDocument } from "../utils/replaceText.js";
2
3
  //#region src/managers/FindReplaceManager.ts
3
4
  /**
@@ -34,11 +35,11 @@ var FindReplaceManager = class {
34
35
  * first match. The result is kept even when `matches` is empty so callers can
35
36
  * surface a "no results" state.
36
37
  */
37
- setMatches(matches) {
38
+ setMatches(matches, currentIndex = 0) {
38
39
  this.result = {
39
40
  matches,
40
41
  totalCount: matches.length,
41
- currentIndex: 0
42
+ currentIndex: Math.max(0, Math.min(currentIndex, matches.length - 1))
42
43
  };
43
44
  return this.result;
44
45
  }
@@ -86,7 +87,7 @@ var FindReplaceManager = class {
86
87
  const result = this.result;
87
88
  if (!result || result.matches.length === 0) return null;
88
89
  const match = result.matches[result.currentIndex];
89
- if (!match) return null;
90
+ if (!match || replaceEligibility(match).status === "blocked") return null;
90
91
  try {
91
92
  return replaceTextInDocument(document, toReplaceRange(match), replaceText);
92
93
  } catch {
@@ -95,26 +96,31 @@ var FindReplaceManager = class {
95
96
  }
96
97
  /**
97
98
  * Replace every match. Matches are replaced from the end of the document
98
- * backwards so earlier offsets stay valid as the text shifts. Clears the
99
- * active result and returns the new document plus the match count, or null
99
+ * backwards so earlier offsets stay valid as the text shifts. Ineligible
100
+ * matches are skipped and counted. Clears the active result, or returns null
100
101
  * when there is nothing to replace.
101
102
  */
102
103
  replaceAll(document, matches, replaceText) {
103
104
  if (matches.length === 0) return null;
104
- const sortedMatches = [...matches].toSorted((a, b) => {
105
+ const eligibleMatches = matches.filter((match) => replaceEligibility(match).status === "allowed");
106
+ let skippedCount = matches.length - eligibleMatches.length;
107
+ let replacedCount = 0;
108
+ const sortedMatches = [...eligibleMatches].toSorted((a, b) => {
105
109
  if (a.paragraphIndex !== b.paragraphIndex) return b.paragraphIndex - a.paragraphIndex;
106
110
  return b.startOffset - a.startOffset;
107
111
  });
108
112
  let nextDocument = document;
109
113
  for (const match of sortedMatches) try {
110
114
  nextDocument = replaceTextInDocument(nextDocument, toReplaceRange(match), replaceText);
115
+ replacedCount++;
111
116
  } catch {
112
- continue;
117
+ skippedCount++;
113
118
  }
114
- this.result = null;
119
+ if (replacedCount > 0) this.result = null;
115
120
  return {
116
121
  document: nextDocument,
117
- replacedCount: matches.length
122
+ replacedCount,
123
+ skippedCount
118
124
  };
119
125
  }
120
126
  };
@@ -1,6 +1,6 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
- import { TableAction, TableContext } from "../utils/tableOperations.js";
3
2
  import { Subscribable } from "./Subscribable.js";
3
+ import { TableAction, TableContext } from "../utils/tableOperations.js";
4
4
  import { CellCoordinates } from "./types.js";
5
5
  //#region src/managers/TableSelectionManager.d.ts
6
6
  /** Data attributes for table elements in the rendered DOM */
@@ -1,5 +1,6 @@
1
1
  import { validateFolioDocumentModel } from "../docx/modelValidation.js";
2
2
  import { bytesToBase64 } from "../utils/base64.js";
3
+ import { base64ToBytes } from "../utils/textEncoding.js";
3
4
  //#region src/managers/autoSaveCodec.ts
4
5
  /**
5
6
  * Version 3 renamed the model's `bidiWrapper` to `inlineWrapper` and gave it a
@@ -10,12 +11,7 @@ import { bytesToBase64 } from "../utils/base64.js";
10
11
  * re-taken rather than migrated.
11
12
  */
12
13
  const AUTOSAVE_FORMAT_VERSION = 3;
13
- const decodeBase64 = (value) => {
14
- const binary = atob(value);
15
- const bytes = new Uint8Array(binary.length);
16
- for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
17
- return bytes.buffer;
18
- };
14
+ const decodeBase64 = (value) => base64ToBytes(value).buffer;
19
15
  const encodeValue = (value) => {
20
16
  if (value === void 0) return { type: "undefined" };
21
17
  if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
@@ -0,0 +1,8 @@
1
+ import { DisplayMode } from "./EditorModeManager.js";
2
+ //#region src/managers/reviewDisplayMode.d.ts
3
+ /** The host's reading of pending revisions; it never changes the saved document. */
4
+ declare const REVIEW_DISPLAY_MODES: readonly ["all-markup", "final", "original"];
5
+ type ReviewDisplayMode = (typeof REVIEW_DISPLAY_MODES)[number];
6
+ declare const reviewDisplayModeToDisplayMode: (mode: ReviewDisplayMode) => DisplayMode;
7
+ //#endregion
8
+ export { REVIEW_DISPLAY_MODES, ReviewDisplayMode, reviewDisplayModeToDisplayMode };
@@ -0,0 +1,15 @@
1
+ //#region src/managers/reviewDisplayMode.ts
2
+ /** The host's reading of pending revisions; it never changes the saved document. */
3
+ const REVIEW_DISPLAY_MODES = [
4
+ "all-markup",
5
+ "final",
6
+ "original"
7
+ ];
8
+ const REVIEW_DISPLAY_MODE_VIEWS = {
9
+ "all-markup": "all-markup",
10
+ final: "no-markup",
11
+ original: "original"
12
+ };
13
+ const reviewDisplayModeToDisplayMode = (mode) => REVIEW_DISPLAY_MODE_VIEWS[mode];
14
+ //#endregion
15
+ export { REVIEW_DISPLAY_MODES, reviewDisplayModeToDisplayMode };
@@ -8,8 +8,10 @@ type ScrollEditorElementOptions = {
8
8
  behavior?: ScrollBehavior;
9
9
  /** Space to leave between the target and the visible root edge. */
10
10
  margin?: number;
11
+ /** Measured target range when only part of the element should be revealed. */
12
+ rect?: DOMRectReadOnly;
11
13
  };
12
14
  /** Scroll only the marked root, including when the target is inside a scaled viewport. */
13
- declare const scrollEditorElementIntoView: (element: HTMLElement | null, { block, behavior, margin }?: ScrollEditorElementOptions) => void;
15
+ declare const scrollEditorElementIntoView: (element: HTMLElement | null, { block, behavior, margin, rect }?: ScrollEditorElementOptions) => void;
14
16
  //#endregion
15
17
  export { getEditorScrollRoot, scrollEditorBy, scrollEditorElementIntoView, scrollEditorTo };
@@ -25,10 +25,10 @@ const nearestScrollOffset = ({ targetStart, targetEnd, viewportStart, viewportEn
25
25
  return 0;
26
26
  };
27
27
  /** Scroll only the marked root, including when the target is inside a scaled viewport. */
28
- const scrollEditorElementIntoView = (element, { block = "center", behavior = prefersReducedMotionBehavior(), margin = 0 } = {}) => {
28
+ const scrollEditorElementIntoView = (element, { block = "center", behavior = prefersReducedMotionBehavior(), margin = 0, rect } = {}) => {
29
29
  const root = getEditorScrollRoot(element);
30
30
  if (!root || !element) return;
31
- const targetRect = element.getBoundingClientRect();
31
+ const targetRect = rect ?? element.getBoundingClientRect();
32
32
  const rootRect = root.getBoundingClientRect();
33
33
  const visibleTop = rootRect.top + root.clientTop;
34
34
  const targetTop = targetRect.top - visibleTop + root.scrollTop;
@@ -1,14 +1,23 @@
1
1
  import { Layout } from "../layout-engine/types.js";
2
2
  import "../layout-engine/index.js";
3
+ import { MarkupViewProjection } from "../prosemirror/markupViewProjection.js";
3
4
  import { Node } from "prosemirror-model";
4
5
  //#region src/paged-layout/pageText.d.ts
6
+ type PageTextOptions = {
7
+ layout: Layout | null;
8
+ doc: Node;
9
+ page: number;
10
+ projection?: MarkupViewProjection;
11
+ };
5
12
  /**
6
13
  * The plain text of a single rendered page (1-based), read by joining each of
7
14
  * the page's layout fragments' `[pmStart, pmEnd)` document text with a
8
- * newline. Fragments with no `pmStart`/`pmEnd` (e.g. a page-shell placeholder)
15
+ * newline. A review projection maps those authored ranges to the displayed
16
+ * document, excluding text the view hides. Fragments with no `pmStart`/`pmEnd`
17
+ * (e.g. a page-shell placeholder)
9
18
  * are skipped. Returns `null` when the layout hasn't been computed yet or the
10
19
  * page number is out of range.
11
20
  */
12
- declare const getPageTextFromLayout: (layout: Layout | null, doc: Node, page: number) => string | null;
21
+ declare const getPageTextFromLayout: ({ layout, doc, page, projection }: PageTextOptions) => string | null;
13
22
  //#endregion
14
23
  export { getPageTextFromLayout };