@stll/folio-core 0.3.1 → 0.5.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 (120) hide show
  1. package/dist/ai-edits/apply.d.ts +3 -9
  2. package/dist/ai-edits/apply.js +94 -14
  3. package/dist/ai-edits/blockRange.d.ts +33 -0
  4. package/dist/ai-edits/blockRange.js +58 -0
  5. package/dist/ai-edits/headless.d.ts +35 -5
  6. package/dist/ai-edits/headless.js +92 -22
  7. package/dist/ai-edits/index.d.ts +5 -3
  8. package/dist/ai-edits/index.js +3 -2
  9. package/dist/ai-edits/snapshot.d.ts +14 -2
  10. package/dist/ai-edits/snapshot.js +12 -1
  11. package/dist/ai-edits/types.d.ts +42 -3
  12. package/dist/compat/eigenpal.d.ts +3 -2
  13. package/dist/compat/eigenpal.js +6 -5
  14. package/dist/controller/layoutPipeline.d.ts +1 -0
  15. package/dist/controller/layoutPipeline.js +35 -115
  16. package/dist/controller/layoutScheduler.d.ts +2 -1
  17. package/dist/controller/layoutScheduler.js +6 -0
  18. package/dist/document-operations.d.ts +122 -0
  19. package/dist/document-operations.js +575 -0
  20. package/dist/docx/blockContentParser.js +17 -7
  21. package/dist/docx/encryption/agileDecryption.d.ts +6 -0
  22. package/dist/docx/encryption/agileDecryption.js +209 -0
  23. package/dist/docx/encryption/compoundFile.d.ts +20 -0
  24. package/dist/docx/encryption/compoundFile.js +251 -0
  25. package/dist/docx/encryption/containerFormat.d.ts +15 -0
  26. package/dist/docx/encryption/containerFormat.js +40 -0
  27. package/dist/docx/encryption/cryptoBytes.d.ts +11 -0
  28. package/dist/docx/encryption/cryptoBytes.js +55 -0
  29. package/dist/docx/encryption/encryptionInfo.d.ts +32 -0
  30. package/dist/docx/encryption/encryptionInfo.js +128 -0
  31. package/dist/docx/encryption/errors.d.ts +18 -0
  32. package/dist/docx/encryption/errors.js +13 -0
  33. package/dist/docx/encryption/index.d.ts +4 -0
  34. package/dist/docx/encryption/index.js +4 -0
  35. package/dist/docx/encryption/openEncryptedDocx.d.ts +28 -0
  36. package/dist/docx/encryption/openEncryptedDocx.js +65 -0
  37. package/dist/docx/footnoteParser.d.ts +1 -17
  38. package/dist/docx/footnoteParser.js +1 -27
  39. package/dist/docx/groupDrawingParser.d.ts +8 -0
  40. package/dist/docx/groupDrawingParser.js +131 -0
  41. package/dist/docx/index.d.ts +3 -1
  42. package/dist/docx/index.js +3 -1
  43. package/dist/docx/numberingParser.d.ts +3 -1
  44. package/dist/docx/numberingParser.js +8 -2
  45. package/dist/docx/paragraphParser.js +9 -3
  46. package/dist/docx/parser.d.ts +4 -5
  47. package/dist/docx/parser.js +8 -2
  48. package/dist/docx/runParser.js +8 -1
  49. package/dist/docx/selectiveXmlPatch.js +4 -10
  50. package/dist/docx/serializer/numberingSerializer.js +3 -1
  51. package/dist/docx/settingsParser.d.ts +6 -3
  52. package/dist/docx/settingsParser.js +2 -0
  53. package/dist/docx/unzip.d.ts +7 -5
  54. package/dist/docx/unzip.js +8 -2
  55. package/dist/docx/xmlParser.js +11 -4
  56. package/dist/index.d.ts +3 -2
  57. package/dist/index.js +6 -5
  58. package/dist/layout-bridge/convert/footnoteLayout.d.ts +37 -1
  59. package/dist/layout-bridge/convert/footnoteLayout.js +138 -14
  60. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +6 -8
  61. package/dist/layout-bridge/convert/headerFooterLayout.js +21 -13
  62. package/dist/layout-bridge/convert/toFlowBlocks.js +58 -19
  63. package/dist/layout-engine/index.d.ts +6 -5
  64. package/dist/layout-engine/index.js +116 -27
  65. package/dist/layout-engine/keep-together.d.ts +13 -8
  66. package/dist/layout-engine/keep-together.js +40 -25
  67. package/dist/layout-engine/layoutInstrumentation.d.ts +11 -1
  68. package/dist/layout-engine/layoutInstrumentation.js +10 -1
  69. package/dist/layout-engine/measure/measureBlocks.js +43 -19
  70. package/dist/layout-engine/measure/measureParagraph.js +91 -14
  71. package/dist/layout-engine/paginator.d.ts +2 -1
  72. package/dist/layout-engine/paginator.js +9 -3
  73. package/dist/layout-engine/textBoxGroup.d.ts +7 -0
  74. package/dist/layout-engine/textBoxGroup.js +8 -0
  75. package/dist/layout-engine/types.d.ts +33 -9
  76. package/dist/layout-engine/types.js +20 -2
  77. package/dist/layout-painter/renderPage.d.ts +15 -1
  78. package/dist/layout-painter/renderPage.js +29 -12
  79. package/dist/layout-painter/renderParagraph.js +44 -9
  80. package/dist/layout-painter/renderTable.js +27 -7
  81. package/dist/managers/DocumentLoaderManager.d.ts +3 -1
  82. package/dist/managers/DocumentLoaderManager.js +13 -5
  83. package/dist/managers/TableSelectionManager.js +1 -1
  84. package/dist/model.js +1 -1
  85. package/dist/paged-layout/pageText.d.ts +14 -0
  86. package/dist/paged-layout/pageText.js +21 -0
  87. package/dist/prosemirror/attrs/index.js +3 -0
  88. package/dist/prosemirror/commands/comments.js +75 -5
  89. package/dist/prosemirror/commands/hyperlink.js +4 -2
  90. package/dist/prosemirror/commands/image.js +2 -1
  91. package/dist/prosemirror/commands/index.js +1 -1
  92. package/dist/prosemirror/commands/propertyChangeScope.d.ts +68 -0
  93. package/dist/prosemirror/commands/propertyChangeScope.js +242 -0
  94. package/dist/prosemirror/conversion/fromProseDoc.js +21 -5
  95. package/dist/prosemirror/conversion/toProseDoc.js +43 -15
  96. package/dist/prosemirror/extensions/core/ParagraphExtension.js +3 -0
  97. package/dist/prosemirror/extensions/features/ListExtension.js +6 -0
  98. package/dist/prosemirror/extensions/nodes/TableExtension.js +12 -11
  99. package/dist/prosemirror/findReplaceSelection.js +2 -1
  100. package/dist/prosemirror/index.js +1 -1
  101. package/dist/prosemirror/insertOperations.js +1 -1
  102. package/dist/prosemirror/schema/nodes.d.ts +14 -7
  103. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  104. package/dist/prosemirror/utils/tabCalculator.js +4 -2
  105. package/dist/redline.d.ts +20 -0
  106. package/dist/redline.js +127 -0
  107. package/dist/server.d.ts +7 -3
  108. package/dist/server.js +6 -2
  109. package/dist/symbols.d.ts +35 -0
  110. package/dist/symbols.js +512 -0
  111. package/dist/utils/clipboard.js +3 -3
  112. package/dist/utils/fontLoader.js +3 -0
  113. package/dist/utils/fontResolver.js +65 -1
  114. package/dist/utils/zoom.d.ts +21 -0
  115. package/dist/utils/zoom.js +21 -0
  116. package/dist/version-comparison.d.ts +92 -0
  117. package/dist/version-comparison.js +436 -0
  118. package/package.json +7 -3
  119. package/dist/paged-layout/headerFooterMargins.d.ts +0 -73
  120. package/dist/paged-layout/headerFooterMargins.js +0 -120
@@ -42,6 +42,20 @@ type PageFloatingImage = {
42
42
  */
43
43
  behindDoc: boolean;
44
44
  };
45
+ type FloatingTableBandOptions = {
46
+ contentX: number;
47
+ tableWidth: number;
48
+ contentWidth: number;
49
+ distLeft: number;
50
+ distRight: number;
51
+ };
52
+ declare const floatingTableReservesBand: ({
53
+ contentX,
54
+ tableWidth,
55
+ contentWidth,
56
+ distLeft,
57
+ distRight
58
+ }: FloatingTableBandOptions) => boolean;
45
59
  /**
46
60
  * CSS class names for page elements
47
61
  */
@@ -276,4 +290,4 @@ declare function findPageShellForPmPos(container: HTMLElement, pmPos: number): {
276
290
  isExact: boolean;
277
291
  } | null;
278
292
  //#endregion
279
- export { AnchoredImagePosition, FootnoteRenderItem, type HeaderFooterContent, HeaderFooterLayoutInfo, PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, PageFloatingImage, PageGeometry, PainterPaintedDetail, type RenderContext, RenderPageOptions, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft };
293
+ export { AnchoredImagePosition, FootnoteRenderItem, type HeaderFooterContent, HeaderFooterLayoutInfo, PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, PageFloatingImage, PageGeometry, PainterPaintedDetail, type RenderContext, RenderPageOptions, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft };
@@ -19,6 +19,11 @@ import { panic } from "better-result";
19
19
  * Renders a single page from Layout data to DOM elements.
20
20
  * Each page contains positioned fragments within a content area.
21
21
  */
22
+ const floatingTableReservesBand = ({ contentX, tableWidth, contentWidth, distLeft, distRight }) => {
23
+ const leftClearance = Math.max(0, contentX - distLeft);
24
+ const rightClearance = Math.max(0, contentWidth - contentX - tableWidth - distRight);
25
+ return Math.max(leftClearance, rightClearance) < 24;
26
+ };
22
27
  /**
23
28
  * CSS class names for page elements
24
29
  */
@@ -874,7 +879,8 @@ function renderPage(page, context, options = {}) {
874
879
  ...img.wrapType !== void 0 ? { wrapType: img.wrapType } : {}
875
880
  });
876
881
  }
877
- if (options.blockLookup) for (const fragment of page.fragments) {
882
+ if (options.blockLookup) for (let fragmentIndex = 0; fragmentIndex < page.fragments.length; fragmentIndex++) {
883
+ const fragment = page.fragments[fragmentIndex];
878
884
  if (fragment.kind !== "table") continue;
879
885
  const blockData = options.blockLookup.get(String(fragment.blockId));
880
886
  if (blockData?.block.kind !== "table") continue;
@@ -886,9 +892,8 @@ function renderPage(page, context, options = {}) {
886
892
  const distBottom = floating.bottomFromText ?? 0;
887
893
  const distLeft = floating.leftFromText ?? 12;
888
894
  const distRight = floating.rightFromText ?? 12;
889
- const side = contentX < contentWidth / 2 ? "left" : "right";
890
- floatingRects.push({
891
- side,
895
+ const rect = {
896
+ side: contentX < contentWidth / 2 ? "left" : "right",
892
897
  x: contentX,
893
898
  y: contentY,
894
899
  width: fragment.width,
@@ -897,7 +902,19 @@ function renderPage(page, context, options = {}) {
897
902
  distBottom,
898
903
  distLeft,
899
904
  distRight
905
+ };
906
+ if (floatingTableReservesBand({
907
+ contentX,
908
+ tableWidth: fragment.width,
909
+ contentWidth,
910
+ distLeft,
911
+ distRight
912
+ })) rect.wrapType = "topAndBottom";
913
+ if (floating.vertAnchor === "text") scopedFloatingRects.push({
914
+ startsAtFragmentIndex: fragmentIndex,
915
+ rect
900
916
  });
917
+ else floatingRects.push(rect);
901
918
  }
902
919
  if (options.blockLookup) for (let fragmentIndex = 0; fragmentIndex < page.fragments.length; fragmentIndex++) {
903
920
  const fragment = page.fragments[fragmentIndex];
@@ -1162,7 +1179,8 @@ function applySectionHeaderFooterOptions(page, pageOptions, options) {
1162
1179
  if (!refs) return false;
1163
1180
  const isFirstSectionPage = page.sectionPageNumber === 1;
1164
1181
  const useFirst = refs.titlePg === true && isFirstSectionPage;
1165
- const useEven = (page.sectionPageNumber ?? page.number) % 2 === 0;
1182
+ const sectionPageNumber = page.sectionPageNumber ?? page.number;
1183
+ const useEven = refs.evenAndOddHeaders === true && sectionPageNumber % 2 === 0;
1166
1184
  const headerRId = (() => {
1167
1185
  if (useFirst) return refs.headerFirst;
1168
1186
  if (useEven && refs.headerEven) return refs.headerEven;
@@ -1556,22 +1574,21 @@ function renderPages(pages, container, options = {}) {
1556
1574
  const pageShells = [];
1557
1575
  for (let i = 0; i < pages.length; i++) {
1558
1576
  const page = pages[i];
1577
+ let pageEl;
1559
1578
  if (!useVirtualization) {
1560
1579
  const { context, pageOptions } = buildPageRenderArgs(page, totalPages, options);
1561
- const pageEl = renderPage(page, context, pageOptions);
1562
- container.insertBefore(pageEl, overlayBefore);
1563
- pageShells.push(pageEl);
1580
+ pageEl = renderPage(page, context, pageOptions);
1564
1581
  } else {
1565
1582
  const doc = options.document ?? document;
1566
- const pageEl = doc.createElement("div");
1583
+ pageEl = doc.createElement("div");
1567
1584
  pageEl.className = options.pageClassName ?? PAGE_CLASS_NAMES.page;
1568
1585
  pageEl.dataset["pageNumber"] = String(page.number);
1569
1586
  pageEl.dataset["pageIndex"] = String(i);
1570
1587
  applyPageStyles(pageEl, page.size.w, page.size.h, options);
1571
1588
  syncPageBorderOverlay(pageEl, page, options, doc);
1572
- container.insertBefore(pageEl, overlayBefore);
1573
- pageShells.push(pageEl);
1574
1589
  }
1590
+ container.insertBefore(pageEl, overlayBefore);
1591
+ pageShells.push(pageEl);
1575
1592
  }
1576
1593
  if (!useVirtualization) {
1577
1594
  emitPainterPainted(container);
@@ -1819,4 +1836,4 @@ function depopulatePageShell(shell, pageDataMap) {
1819
1836
  }
1820
1837
  }
1821
1838
  //#endregion
1822
- export { PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft };
1839
+ export { PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft };
@@ -330,6 +330,10 @@ function renderTabRun(run, doc, width, leader) {
330
330
  } else span.textContent = "\xA0";
331
331
  return span;
332
332
  }
333
+ function canClampTabToRightEdge(alignment, hasPriorRenderedContent, hasPriorTab, isLastLine) {
334
+ if (alignment === "start" || alignment === "default") return hasPriorRenderedContent && (hasPriorTab || isLastLine);
335
+ return true;
336
+ }
333
337
  function applyTabUnderline(element, run) {
334
338
  if (!run.underline) return;
335
339
  removeUnderlineTextDecoration(element);
@@ -529,7 +533,9 @@ const EMPTY_SEQ_VALUES = /* @__PURE__ */ new Map();
529
533
  * Returns the cached fallback when no render context is available.
530
534
  */
531
535
  function resolveFieldText(run, context) {
532
- if (!context) return run.fallback ?? "";
536
+ const fallback = run.fallback ?? "";
537
+ if (run.fieldType === "OTHER" && run.pmStart === void 0) return fallback;
538
+ if (!context) return fallback;
533
539
  const fieldContext = {
534
540
  pageNumber: context.pageNumber,
535
541
  totalPages: context.totalPages,
@@ -540,7 +546,7 @@ function resolveFieldText(run, context) {
540
546
  ...context.sectionPages === void 0 ? {} : { sectionPages: context.sectionPages }
541
547
  };
542
548
  return evaluateFieldInstruction(run.instruction || run.fieldType, fieldContext, {
543
- fallback: run.fallback ?? "",
549
+ fallback,
544
550
  ...run.pmStart === void 0 ? {} : { instanceId: run.pmStart },
545
551
  ...run.fldLock ? { locked: true } : {}
546
552
  });
@@ -710,6 +716,17 @@ function splitTextRunsByEastAsia(runs) {
710
716
  * must trigger within this slack.
711
717
  */
712
718
  const RIGHT_EDGE_EPSILON_PX = .5;
719
+ function countShrinkableSpaces(runs) {
720
+ let count = 0;
721
+ for (const run of runs) if (isTextRun(run)) {
722
+ for (const char of run.text ?? "") if (char === " ") count++;
723
+ } else if (isFieldRun(run)) {
724
+ for (const char of run.fallback ?? "") if (char === " ") count++;
725
+ } else if (isMathRun(run)) {
726
+ for (const char of run.plainText ?? "") if (char === " ") count++;
727
+ }
728
+ return count;
729
+ }
713
730
  /**
714
731
  * Build a TextMeasureStyle from a TextRun or FieldRun's relevant fields.
715
732
  */
@@ -888,16 +905,32 @@ function renderLine(block, line, alignment, doc, options) {
888
905
  lineEl.append(emptySpan);
889
906
  return lineEl;
890
907
  }
908
+ const hasTabRuns = runsForLine.some(isTabRun);
891
909
  if (alignment === "justify" && options) {
892
910
  if (!options.isLastLine || options.paragraphEndsWithLineBreak) {
893
- lineEl.style.textAlign = "justify";
894
- lineEl.style.textAlignLast = "justify";
895
- lineEl.style.width = `${options.availableWidth}px`;
911
+ const firstLineIndentPx = options.isFirstLine ? options.firstLineIndentPx ?? 0 : 0;
912
+ const firstLineHangingPx = Math.max(0, -firstLineIndentPx);
913
+ const justifyCapacityPx = options.availableWidth + firstLineHangingPx;
914
+ const overfullPx = line.width - justifyCapacityPx;
915
+ const shrinkableSpaces = countShrinkableSpaces(runsForLine);
916
+ if (overfullPx > RIGHT_EDGE_EPSILON_PX && shrinkableSpaces > 0) {
917
+ lineEl.style.textAlign = "left";
918
+ lineEl.style.textAlignLast = "auto";
919
+ lineEl.style.wordSpacing = `${-overfullPx / shrinkableSpaces}px`;
920
+ } else if (hasTabRuns && shrinkableSpaces > 0 && -overfullPx > RIGHT_EDGE_EPSILON_PX) {
921
+ lineEl.style.textAlign = "left";
922
+ lineEl.style.textAlignLast = "auto";
923
+ lineEl.style.wordSpacing = `${-overfullPx / shrinkableSpaces}px`;
924
+ } else {
925
+ lineEl.style.textAlign = "justify";
926
+ lineEl.style.textAlignLast = "justify";
927
+ }
928
+ const listFirstLineOffset = options.isFirstLine && block.attrs?.listMarker && !block.attrs.listMarkerHidden ? options.firstLineIndentPx ?? 0 : 0;
929
+ lineEl.style.width = `${options.availableWidth - listFirstLineOffset}px`;
896
930
  }
897
931
  }
898
932
  lineEl.style.whiteSpace = "pre";
899
933
  lineEl.style.overflow = "visible";
900
- const hasTabRuns = runsForLine.some(isTabRun);
901
934
  let tabContext;
902
935
  const measureText = createTextMeasurer(doc);
903
936
  if (hasTabRuns) {
@@ -905,6 +938,7 @@ function renderLine(block, line, alignment, doc, options) {
905
938
  const leftIndentTwips = options?.leftIndentPx ? Math.round(options.leftIndentPx * 15) : 0;
906
939
  tabContext = {
907
940
  ...explicitStops !== void 0 ? { explicitStops } : {},
941
+ ...block.attrs?.defaultTabStopTwips !== void 0 ? { defaultTabInterval: block.attrs.defaultTabStopTwips } : {},
908
942
  leftIndent: leftIndentTwips
909
943
  };
910
944
  }
@@ -967,7 +1001,7 @@ function renderLine(block, line, alignment, doc, options) {
967
1001
  break;
968
1002
  }
969
1003
  let tabWidth = tabResult.width;
970
- if (lineRightEdgeX !== void 0 && currentX + tabWidth + followingWidthForCheck > lineRightEdgeX) tabWidth = Math.max(1, lineRightEdgeX - currentX - followingWidthForCheck);
1004
+ if (lineRightEdgeX !== void 0 && canClampTabToRightEdge(tabResult.alignment, i > 0, runsForLine.slice(0, i).some(isTabRun), options?.isLastLine === true) && currentX + tabWidth + followingWidthForCheck > lineRightEdgeX) tabWidth = Math.max(1, lineRightEdgeX - currentX - followingWidthForCheck);
971
1005
  const tabEl = renderTabRun(run, doc, tabWidth, tabResult.leader);
972
1006
  lineEl.append(tabEl);
973
1007
  currentX += tabWidth;
@@ -1215,7 +1249,7 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
1215
1249
  } else if (indentLeft > 0) lineEl.style.paddingLeft = `${indentLeft}px`;
1216
1250
  else if (hasFirstLine && !isFlexLine) lineEl.style.textIndent = `${indent.firstLine ?? 0}px`;
1217
1251
  } else if (indentLeft > 0) lineEl.style.paddingLeft = `${indentLeft}px`;
1218
- else if (hasHanging) lineEl.style.paddingLeft = `${indent.hanging ?? 0}px`;
1252
+ else if (hasHanging && indentLeft === 0) lineEl.style.paddingLeft = `${indent.hanging ?? 0}px`;
1219
1253
  if (indentRight > 0) lineEl.style.paddingRight = `${indentRight}px`;
1220
1254
  if (isFirstLine && block.attrs?.listMarker && !block.attrs.listMarkerHidden) {
1221
1255
  const hanging = indent?.hanging ?? 0;
@@ -1234,7 +1268,8 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
1234
1268
  const markerFontFamily = block.attrs.listMarkerFontFamily ?? firstTextRun?.fontFamily ?? block.attrs.defaultFontFamily;
1235
1269
  const markerFontSize = block.attrs.listMarkerFontSize ?? firstTextRun?.fontSize ?? block.attrs.defaultFontSize;
1236
1270
  const marker = renderListMarker(block.attrs.listMarker, getListMarkerInlineWidth(block), doc, markerFontFamily, markerFontSize, block.attrs.listMarkerRevision, block.attrs.listMarkerSecondSlotOffsetTwips);
1237
- if (markerStart < 0 && indentLeft > 0) marker.style.marginLeft = `${markerStart}px`;
1271
+ const markerMarginLeft = markerStart - Math.min(indentLeft, 0);
1272
+ if (markerMarginLeft < 0 && indentLeft !== 0) marker.style.marginLeft = `${markerMarginLeft}px`;
1238
1273
  lineEl.prepend(marker);
1239
1274
  }
1240
1275
  fragmentEl.append(lineEl);
@@ -1,3 +1,4 @@
1
+ import { tableColumnsArePinned } from "../layout-engine/types.js";
1
2
  import { measureParagraph } from "../layout-engine/measure/measureParagraph.js";
2
3
  import { getAutomaticTextColorForBackground } from "./documentColors.js";
3
4
  import { renderParagraphFragment } from "./renderParagraph.js";
@@ -97,6 +98,10 @@ function renderCellContent(cell, cellMeasure, context, doc) {
97
98
  };
98
99
  const fragEl = renderParagraphFragment(syntheticFragment, paragraphBlock, paragraphMeasure, cellContext, { document: doc });
99
100
  fragEl.style.position = "relative";
101
+ fragEl.style.boxSizing = "border-box";
102
+ fragEl.style.height = `${paragraphMeasure.totalHeight}px`;
103
+ const spaceBefore = paragraphBlock.attrs?.spacing?.before ?? 0;
104
+ if (spaceBefore > 0) fragEl.style.paddingTop = `${spaceBefore}px`;
100
105
  contentEl.append(fragEl);
101
106
  cumulativeY += paragraphMeasure.totalHeight;
102
107
  } else if (block?.kind === "table" && measure?.kind === "table") {
@@ -133,12 +138,17 @@ function renderNestedTable(block, measure, context, doc) {
133
138
  }
134
139
  rowYPositions.push(yPos);
135
140
  const spanningCells = /* @__PURE__ */ new Map();
141
+ const columnsPinned = tableColumnsArePinned(block);
136
142
  let y = 0;
137
143
  for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex++) {
138
144
  const row = block.rows[rowIndex];
139
145
  const rowMeasure = measure.rows[rowIndex];
140
146
  if (!row || !rowMeasure) continue;
141
- const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, void 0, block.bidi);
147
+ if (row.hidden) {
148
+ y += rowMeasure.height;
149
+ continue;
150
+ }
151
+ const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, void 0, block.bidi, columnsPinned);
142
152
  tableEl.append(rowEl);
143
153
  y += rowMeasure.height;
144
154
  }
@@ -161,7 +171,7 @@ function applyBorder(el, side, border) {
161
171
  /**
162
172
  * Render a single table cell
163
173
  */
164
- function renderTableCell(cell, cellMeasure, x, rowHeight, borderFlags, context, doc) {
174
+ function renderTableCell(cell, cellMeasure, x, rowHeight, borderFlags, columnsPinned, context, doc) {
165
175
  const cellEl = doc.createElement("div");
166
176
  cellEl.className = TABLE_CLASS_NAMES.cell;
167
177
  cellEl.style.position = "absolute";
@@ -187,7 +197,7 @@ function renderTableCell(cell, cellMeasure, x, rowHeight, borderFlags, context,
187
197
  const automaticTextColor = getAutomaticTextColorForBackground(cell.background);
188
198
  if (automaticTextColor) cellEl.style.color = automaticTextColor;
189
199
  }
190
- if (cell.noWrap) cellEl.style.whiteSpace = "nowrap";
200
+ if (cell.noWrap && !columnsPinned) cellEl.style.whiteSpace = "nowrap";
191
201
  if (cell.verticalAlign) {
192
202
  cellEl.style.display = "flex";
193
203
  cellEl.style.flexDirection = "column";
@@ -218,7 +228,7 @@ function renderTableCell(cell, cellMeasure, x, rowHeight, borderFlags, context,
218
228
  /**
219
229
  * Render a table row with rowSpan support
220
230
  */
221
- function renderTableRow(row, rowMeasure, rowIndex, y, columnWidths, totalRows, context, doc, spanningCells, rowYPositions, isFirstRowInFragment, bidi = false) {
231
+ function renderTableRow(row, rowMeasure, rowIndex, y, columnWidths, totalRows, context, doc, spanningCells, rowYPositions, isFirstRowInFragment, bidi = false, columnsPinned = false) {
222
232
  const rowEl = doc.createElement("div");
223
233
  rowEl.className = TABLE_CLASS_NAMES.row;
224
234
  const tableWidth = bidi ? columnWidths.reduce((sum, columnWidth) => sum + columnWidth, 0) : 0;
@@ -262,7 +272,7 @@ function renderTableRow(row, rowMeasure, rowIndex, y, columnWidths, totalRows, c
262
272
  isLastRow,
263
273
  isFirstCol: bidi ? atLogicalEnd : atLogicalStart,
264
274
  isLastCol: bidi ? atLogicalStart : atLogicalEnd
265
- }, context, doc);
275
+ }, columnsPinned, context, doc);
266
276
  cellEl.dataset["cellIndex"] = String(cellIndex);
267
277
  cellEl.dataset["columnIndex"] = String(columnIndex);
268
278
  if (rowSpan > 1) cellEl.dataset["rowSpan"] = String(rowSpan);
@@ -340,13 +350,18 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
340
350
  }
341
351
  rowYPositions.push(yPos);
342
352
  const spanningCells = /* @__PURE__ */ new Map();
353
+ const columnsPinned = tableColumnsArePinned(block);
343
354
  const headerRowCount = fragment.headerRowCount ?? 0;
344
355
  let y = 0;
345
356
  if (headerRowCount > 0 && fragment.continuesFromPrev) for (let hdrIdx = 0; hdrIdx < headerRowCount; hdrIdx++) {
346
357
  const hdrRow = block.rows[hdrIdx];
347
358
  const hdrRowMeasure = measure.rows[hdrIdx];
348
359
  if (!hdrRow || !hdrRowMeasure) continue;
349
- const rowEl = renderTableRow(hdrRow, hdrRowMeasure, hdrIdx, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, hdrIdx === 0, block.bidi);
360
+ if (hdrRow.hidden) {
361
+ y += hdrRowMeasure.height;
362
+ continue;
363
+ }
364
+ const rowEl = renderTableRow(hdrRow, hdrRowMeasure, hdrIdx, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, hdrIdx === 0, block.bidi, columnsPinned);
350
365
  rowEl.dataset["repeatedHeader"] = "true";
351
366
  tableEl.append(rowEl);
352
367
  y += hdrRowMeasure.height;
@@ -369,13 +384,18 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
369
384
  const row = block.rows[rowIndex];
370
385
  const rowMeasure = measure.rows[rowIndex];
371
386
  if (!row || !rowMeasure) continue;
387
+ if (row.hidden) {
388
+ y += rowMeasure.height;
389
+ continue;
390
+ }
372
391
  const isFirstRowInFragment = headerRowCount > 0 && fragment.continuesFromPrev ? false : fragment.continuesFromPrev && rowIndex === fragment.fromRow;
373
- const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, isFirstRowInFragment, block.bidi);
392
+ const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, isFirstRowInFragment, block.bidi, columnsPinned);
374
393
  contentParent.append(rowEl);
375
394
  y += rowMeasure.height;
376
395
  }
377
396
  let handleY = 0;
378
397
  for (let rowIdx = fragment.fromRow; rowIdx < fragment.toRow; rowIdx++) {
398
+ if (block.rows[rowIdx]?.hidden) continue;
379
399
  handleY += measure.rows[rowIdx]?.height ?? 0;
380
400
  if (rowIdx < fragment.toRow - 1) {
381
401
  const rowHandle = doc.createElement("div");
@@ -41,7 +41,9 @@ declare class DocumentLoaderManager {
41
41
  * the result (and any error) when a newer load started while this one was in
42
42
  * flight.
43
43
  */
44
- loadBuffer(buffer: DocxInput): Promise<void>;
44
+ loadBuffer(buffer: DocxInput, options?: {
45
+ password?: string | undefined;
46
+ }): Promise<void>;
45
47
  }
46
48
  //#endregion
47
49
  export { DocumentLoadState, DocumentLoaderCallbacks, DocumentLoaderHistory, DocumentLoaderManager };
@@ -1,6 +1,7 @@
1
1
  import { resetAuthorColors } from "../utils/authorColors.js";
2
2
  import { loadFontsWithMapping } from "../utils/fontLoader.js";
3
3
  import { parseDocx } from "../docx/parser.js";
4
+ import { recordDocumentLoadPhase } from "../layout-engine/layoutInstrumentation.js";
4
5
  import { inspectDocxCompatibility } from "../docx/compatibility.js";
5
6
  //#region src/managers/DocumentLoaderManager.ts
6
7
  /**
@@ -43,15 +44,22 @@ var DocumentLoaderManager = class {
43
44
  * the result (and any error) when a newer load started while this one was in
44
45
  * flight.
45
46
  */
46
- async loadBuffer(buffer) {
47
+ async loadBuffer(buffer, options = {}) {
47
48
  const { history, onError, setDocumentLoadState } = this.callbacks;
48
49
  const generation = ++this.loadGeneration;
49
50
  if (!(history.state !== null)) setDocumentLoadState({ status: "loading" });
50
51
  try {
51
- const doc = await parseDocx(buffer, {
52
- detectVariables: false,
53
- preloadFonts: false
54
- });
52
+ const parseStartedAt = performance.now();
53
+ let doc;
54
+ try {
55
+ doc = await parseDocx(buffer, {
56
+ detectVariables: false,
57
+ preloadFonts: false,
58
+ password: options.password
59
+ });
60
+ } finally {
61
+ recordDocumentLoadPhase("docx-parse", performance.now() - parseStartedAt);
62
+ }
55
63
  if (this.loadGeneration !== generation) return;
56
64
  this.loadParsedDocument(doc);
57
65
  } catch (error) {
@@ -1,5 +1,5 @@
1
- import { Subscribable } from "./Subscribable.js";
2
1
  import { addColumn, addRow, createTableContext, deleteColumn, deleteRow, getColumnCount, mergeCells, splitCell } from "../utils/tableOperations.js";
2
+ import { Subscribable } from "./Subscribable.js";
3
3
  //#region src/managers/TableSelectionManager.ts
4
4
  /** Data attributes for table elements in the rendered DOM */
5
5
  const TABLE_DATA_ATTRIBUTES = {
package/dist/model.js CHANGED
@@ -1,4 +1,4 @@
1
- import { BORDER_STYLE_VALUES, CONDITIONAL_STYLE_TYPE_VALUES, EMPHASIS_MARK_VALUES, FIELD_TYPE_VALUES, FLOATING_TABLE_X_SPEC_VALUES, FLOATING_TABLE_Y_SPEC_VALUES, FONT_THEME_VALUES, FRAME_WRAP_VALUES, FRAME_X_ALIGN_VALUES, FRAME_Y_ALIGN_VALUES, HIGHLIGHT_COLOR_VALUES, IMAGE_HORIZONTAL_ALIGNMENT_VALUES, IMAGE_HORIZONTAL_RELATIVE_TO_VALUES, IMAGE_VERTICAL_ALIGNMENT_VALUES, IMAGE_VERTICAL_RELATIVE_TO_VALUES, IMAGE_WRAP_TEXT_VALUES, IMAGE_WRAP_TYPE_VALUES, LEVEL_SUFFIX_VALUES, LINE_SPACING_RULE_VALUES, NUMBER_FORMAT_VALUES, OUTLINE_STYLE_ATTR_VALUES, OUTLINE_STYLE_CSS_ALIASES, OUTLINE_STYLE_CSS_ALIAS_VALUES, PARAGRAPH_ALIGNMENT_VALUES, SDT_LOCK_VALUES, SDT_TYPE_VALUES, SHADING_PATTERN_VALUES, SHAPE_OUTLINE_STYLE_VALUES, SHAPE_TYPE_VALUES, STYLE_TYPE_VALUES, TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_CELL_VERTICAL_ALIGNMENT_VALUES, TABLE_JUSTIFICATION_VALUES, TABLE_ROW_HEIGHT_RULE_VALUES, TABLE_WIDTH_TYPE_VALUES, TAB_LEADER_VALUES, TAB_STOP_ALIGNMENT_VALUES, TEXT_EFFECT_VALUES, THEME_COLOR_SLOT_VALUES, UNDERLINE_STYLE_VALUES } from "./types/documentEnumValues.js";
2
1
  import { deriveBlockId, getFolioParaIdFromBlockId, getSequentialFolioBlockIdIndex, isFolioBlockId, isSequentialFolioBlockId } from "./types/block-id.js";
2
+ import { BORDER_STYLE_VALUES, CONDITIONAL_STYLE_TYPE_VALUES, EMPHASIS_MARK_VALUES, FIELD_TYPE_VALUES, FLOATING_TABLE_X_SPEC_VALUES, FLOATING_TABLE_Y_SPEC_VALUES, FONT_THEME_VALUES, FRAME_WRAP_VALUES, FRAME_X_ALIGN_VALUES, FRAME_Y_ALIGN_VALUES, HIGHLIGHT_COLOR_VALUES, IMAGE_HORIZONTAL_ALIGNMENT_VALUES, IMAGE_HORIZONTAL_RELATIVE_TO_VALUES, IMAGE_VERTICAL_ALIGNMENT_VALUES, IMAGE_VERTICAL_RELATIVE_TO_VALUES, IMAGE_WRAP_TEXT_VALUES, IMAGE_WRAP_TYPE_VALUES, LEVEL_SUFFIX_VALUES, LINE_SPACING_RULE_VALUES, NUMBER_FORMAT_VALUES, OUTLINE_STYLE_ATTR_VALUES, OUTLINE_STYLE_CSS_ALIASES, OUTLINE_STYLE_CSS_ALIAS_VALUES, PARAGRAPH_ALIGNMENT_VALUES, SDT_LOCK_VALUES, SDT_TYPE_VALUES, SHADING_PATTERN_VALUES, SHAPE_OUTLINE_STYLE_VALUES, SHAPE_TYPE_VALUES, STYLE_TYPE_VALUES, TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_CELL_VERTICAL_ALIGNMENT_VALUES, TABLE_JUSTIFICATION_VALUES, TABLE_ROW_HEIGHT_RULE_VALUES, TABLE_WIDTH_TYPE_VALUES, TAB_LEADER_VALUES, TAB_STOP_ALIGNMENT_VALUES, TEXT_EFFECT_VALUES, THEME_COLOR_SLOT_VALUES, UNDERLINE_STYLE_VALUES } from "./types/documentEnumValues.js";
3
3
  import { types_exports } from "./layout-engine/types.js";
4
4
  export { BORDER_STYLE_VALUES, CONDITIONAL_STYLE_TYPE_VALUES, EMPHASIS_MARK_VALUES, FIELD_TYPE_VALUES, FLOATING_TABLE_X_SPEC_VALUES, FLOATING_TABLE_Y_SPEC_VALUES, FONT_THEME_VALUES, FRAME_WRAP_VALUES, FRAME_X_ALIGN_VALUES, FRAME_Y_ALIGN_VALUES, HIGHLIGHT_COLOR_VALUES, IMAGE_HORIZONTAL_ALIGNMENT_VALUES, IMAGE_HORIZONTAL_RELATIVE_TO_VALUES, IMAGE_VERTICAL_ALIGNMENT_VALUES, IMAGE_VERTICAL_RELATIVE_TO_VALUES, IMAGE_WRAP_TEXT_VALUES, IMAGE_WRAP_TYPE_VALUES, LEVEL_SUFFIX_VALUES, LINE_SPACING_RULE_VALUES, types_exports as Layout, NUMBER_FORMAT_VALUES, OUTLINE_STYLE_ATTR_VALUES, OUTLINE_STYLE_CSS_ALIASES, OUTLINE_STYLE_CSS_ALIAS_VALUES, PARAGRAPH_ALIGNMENT_VALUES, SDT_LOCK_VALUES, SDT_TYPE_VALUES, SHADING_PATTERN_VALUES, SHAPE_OUTLINE_STYLE_VALUES, SHAPE_TYPE_VALUES, STYLE_TYPE_VALUES, TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_CELL_VERTICAL_ALIGNMENT_VALUES, TABLE_JUSTIFICATION_VALUES, TABLE_ROW_HEIGHT_RULE_VALUES, TABLE_WIDTH_TYPE_VALUES, TAB_LEADER_VALUES, TAB_STOP_ALIGNMENT_VALUES, TEXT_EFFECT_VALUES, THEME_COLOR_SLOT_VALUES, UNDERLINE_STYLE_VALUES, deriveBlockId, getFolioParaIdFromBlockId, getSequentialFolioBlockIdIndex, isFolioBlockId, isSequentialFolioBlockId };
@@ -0,0 +1,14 @@
1
+ import { Layout } from "../layout-engine/types.js";
2
+ import { Node } from "prosemirror-model";
3
+
4
+ //#region src/paged-layout/pageText.d.ts
5
+ /**
6
+ * The plain text of a single rendered page (1-based), read by joining each of
7
+ * the page's layout fragments' `[pmStart, pmEnd)` document text with a
8
+ * newline. Fragments with no `pmStart`/`pmEnd` (e.g. a page-shell placeholder)
9
+ * are skipped. Returns `null` when the layout hasn't been computed yet or the
10
+ * page number is out of range.
11
+ */
12
+ declare const getPageTextFromLayout: (layout: Layout | null, doc: Node, page: number) => string | null;
13
+ //#endregion
14
+ export { getPageTextFromLayout };
@@ -0,0 +1,21 @@
1
+ //#region src/paged-layout/pageText.ts
2
+ /**
3
+ * The plain text of a single rendered page (1-based), read by joining each of
4
+ * the page's layout fragments' `[pmStart, pmEnd)` document text with a
5
+ * newline. Fragments with no `pmStart`/`pmEnd` (e.g. a page-shell placeholder)
6
+ * are skipped. Returns `null` when the layout hasn't been computed yet or the
7
+ * page number is out of range.
8
+ */
9
+ const getPageTextFromLayout = (layout, doc, page) => {
10
+ if (!layout || !Number.isInteger(page) || page < 1) return null;
11
+ const layoutPage = layout.pages[page - 1];
12
+ if (!layoutPage) return null;
13
+ const fragmentTexts = [];
14
+ for (const fragment of layoutPage.fragments) {
15
+ if (typeof fragment.pmStart !== "number" || typeof fragment.pmEnd !== "number") continue;
16
+ fragmentTexts.push(doc.textBetween(fragment.pmStart, fragment.pmEnd, "\n"));
17
+ }
18
+ return fragmentTexts.join("\n");
19
+ };
20
+ //#endregion
21
+ export { getPageTextFromLayout };
@@ -174,6 +174,7 @@ const readParagraphAttrs = (node) => {
174
174
  });
175
175
  optionalOneOf(attrs, "sectionBreakType", "paragraph.attrs.sectionBreakType", issues, SECTION_BREAK_TYPES);
176
176
  optionalStringArray(attrs, "listLevelNumFmts", "paragraph.attrs.listLevelNumFmts", issues);
177
+ optionalNumberArray(attrs, "listLevelStarts", "paragraph.attrs.listLevelStarts", issues);
177
178
  optionalNumber(attrs, "listAbstractNumId", "paragraph.attrs.listAbstractNumId", issues);
178
179
  optionalBorderMap(attrs, "borders", "paragraph.attrs.borders", issues, [
179
180
  "top",
@@ -186,6 +187,7 @@ const readParagraphAttrs = (node) => {
186
187
  optionalShading(attrs, "shading", "paragraph.attrs.shading", issues);
187
188
  optionalTabStops(attrs, "tabs", "paragraph.attrs.tabs", issues);
188
189
  optionalRecord(attrs, "spacingExplicit", "paragraph.attrs.spacingExplicit", issues);
190
+ optionalRecord(attrs, "spacingFromDocDefaults", "paragraph.attrs.spacingFromDocDefaults", issues);
189
191
  optionalTextFormatting(attrs, "defaultTextFormatting", "paragraph.attrs.defaultTextFormatting", issues);
190
192
  optionalRecord(attrs, "numPr", "paragraph.attrs.numPr", issues);
191
193
  validateNumPr(attrs["numPr"], issues);
@@ -236,6 +238,7 @@ const readTableRowAttrs = (node) => {
236
238
  optionalNumber(attrs, "height", "tableRow.attrs.height", issues);
237
239
  optionalOneOf(attrs, "heightRule", "tableRow.attrs.heightRule", issues, TABLE_ROW_HEIGHT_RULE_VALUES);
238
240
  optionalBoolean(attrs, "isHeader", "tableRow.attrs.isHeader", issues);
241
+ optionalBoolean(attrs, "hidden", "tableRow.attrs.hidden", issues);
239
242
  optionalRecord(attrs, "_originalFormatting", "tableRow.attrs._originalFormatting", issues);
240
243
  return attrsResult(attrs, issues);
241
244
  };
@@ -1,3 +1,4 @@
1
+ import { paragraphRejectAttrPatch, paragraphRejectOriginalFormatting, sectionRejectProperties, tableCellRejectAttrPatch, tableRejectAttrPatch, tableRowRejectAttrPatch } from "./propertyChangeScope.js";
1
2
  //#region src/prosemirror/commands/comments.ts
2
3
  /**
3
4
  * Add a comment mark to the current selection.
@@ -59,21 +60,47 @@ function resolveChange(from, to, mode, revisionIds) {
59
60
  if (node.type.name === "paragraph") {
60
61
  const op = collectPPrMarkOp(node, pos, from, to, mode, revisionSet);
61
62
  if (op) pPrMarkOps.push(op);
63
+ const boundaryCovered = rangeCoversParagraphBoundary(from, to, pos, node);
64
+ let nextAttrs = null;
62
65
  const propertyChanges = node.attrs["_propertyChanges"];
63
- if (Array.isArray(propertyChanges) && propertyChanges.length > 0 && rangeCoversParagraphBoundary(from, to, pos, node)) {
66
+ if (Array.isArray(propertyChanges) && propertyChanges.length > 0 && boundaryCovered) {
64
67
  const matches = propertyChanges.filter((c) => revisionSet === null || c.info && revisionSet.has(c.info.id));
65
68
  if (matches.length > 0) {
66
69
  const remaining = propertyChanges.filter((c) => revisionSet !== null && (!c.info || !revisionSet.has(c.info.id)));
67
- const nextAttrs = {
70
+ nextAttrs = {
68
71
  ...node.attrs,
69
72
  _propertyChanges: remaining.length > 0 ? remaining : null
70
73
  };
71
- if (mode === "reject") {
72
- for (const change of matches.toReversed()) if (change.previousFormatting) for (const [key, val] of Object.entries(change.previousFormatting)) nextAttrs[key] = val;
74
+ if (mode === "reject") for (const change of matches.toReversed()) {
75
+ Object.assign(nextAttrs, paragraphRejectAttrPatch(change.previousFormatting));
76
+ nextAttrs["_originalFormatting"] = paragraphRejectOriginalFormatting(change.previousFormatting, node.attrs["_originalFormatting"]);
73
77
  }
74
- tr.setNodeMarkup(pos, void 0, nextAttrs);
75
78
  }
76
79
  }
80
+ const sectionProperties = node.attrs["_sectionProperties"];
81
+ const sectionChanges = sectionProperties?.propertyChanges;
82
+ if (sectionProperties && Array.isArray(sectionChanges) && sectionChanges.length > 0 && boundaryCovered) {
83
+ const matches = sectionChanges.filter((c) => revisionSet === null || c.info && revisionSet.has(c.info.id));
84
+ if (matches.length > 0) {
85
+ const remaining = sectionChanges.filter((c) => revisionSet !== null && (!c.info || !revisionSet.has(c.info.id)));
86
+ let restored = { ...sectionProperties };
87
+ if (mode === "reject") for (const change of matches.toReversed()) restored = sectionRejectProperties(restored, change.previousProperties);
88
+ delete restored.propertyChanges;
89
+ if (remaining.length > 0) restored.propertyChanges = remaining;
90
+ nextAttrs = nextAttrs ?? { ...node.attrs };
91
+ nextAttrs["_sectionProperties"] = restored;
92
+ nextAttrs["sectionBreakType"] = sectionBreakTypeFromSectionStart(restored.sectionStart);
93
+ }
94
+ }
95
+ if (nextAttrs) tr.setNodeMarkup(pos, void 0, nextAttrs);
96
+ return true;
97
+ }
98
+ const tableChangeAttrName = TABLE_PROPERTY_CHANGE_ATTR_BY_NODE[node.type.name];
99
+ if (tableChangeAttrName !== void 0) {
100
+ if (rangeCoversNode(from, to, pos, node)) {
101
+ const nextAttrs = resolveTablePropertyChangeAttrs(node, tableChangeAttrName, mode, revisionSet);
102
+ if (nextAttrs) tr.setNodeMarkup(pos, void 0, nextAttrs);
103
+ }
77
104
  return true;
78
105
  }
79
106
  if (!node.isInline) return true;
@@ -124,6 +151,49 @@ function rangeCoversParagraphBoundary(from, to, pos, node) {
124
151
  const boundaryTo = pos + node.nodeSize;
125
152
  return from <= boundaryFrom && to >= boundaryTo;
126
153
  }
154
+ /** Whether [from, to] fully covers the node — the range property-change cards
155
+ * and accept-all / reject-all sweeps supply for table-level records. */
156
+ function rangeCoversNode(from, to, pos, node) {
157
+ return from <= pos && to >= pos + node.nodeSize;
158
+ }
159
+ const SECTION_BREAK_TYPE_VALUES = [
160
+ "nextPage",
161
+ "continuous",
162
+ "oddPage",
163
+ "evenPage"
164
+ ];
165
+ function sectionBreakTypeFromSectionStart(sectionStart) {
166
+ return SECTION_BREAK_TYPE_VALUES.find((value) => value === sectionStart) ?? null;
167
+ }
168
+ /** PM node type name → the attr its tracked property-change records live on. */
169
+ const TABLE_PROPERTY_CHANGE_ATTR_BY_NODE = {
170
+ table: "tblPrChange",
171
+ tableRow: "trPrChange",
172
+ tableCell: "tcPrChange",
173
+ tableHeader: "tcPrChange"
174
+ };
175
+ /**
176
+ * Resolve the tracked property-change records on one table / row / cell node.
177
+ * Accept keeps the live formatting and clears the matched records; reject
178
+ * additionally restores the stored previous formatting wholesale (the change
179
+ * element stores the complete old property set — see propertyChangeScope.ts).
180
+ * Returns the next attrs, or `null` when no record matches.
181
+ */
182
+ function resolveTablePropertyChangeAttrs(node, attrName, mode, revisionSet) {
183
+ const changes = node.attrs[attrName];
184
+ if (!Array.isArray(changes) || changes.length === 0) return null;
185
+ const matches = changes.filter((c) => revisionSet === null || c.info && revisionSet.has(c.info.id));
186
+ if (matches.length === 0) return null;
187
+ const remaining = changes.filter((c) => revisionSet !== null && (!c.info || !revisionSet.has(c.info.id)));
188
+ const nextAttrs = {
189
+ ...node.attrs,
190
+ [attrName]: remaining.length > 0 ? remaining : null
191
+ };
192
+ if (mode === "reject") for (const change of matches.toReversed()) if (attrName === "tblPrChange") Object.assign(nextAttrs, tableRejectAttrPatch(change.previousFormatting));
193
+ else if (attrName === "trPrChange") Object.assign(nextAttrs, tableRowRejectAttrPatch(change.previousFormatting));
194
+ else Object.assign(nextAttrs, tableCellRejectAttrPatch(change.previousFormatting, node.attrs["_originalFormatting"]));
195
+ return nextAttrs;
196
+ }
127
197
  function isPPrMarkAttr(value) {
128
198
  if (typeof value !== "object" || value === null) return false;
129
199
  const kind = value.kind;
@@ -39,7 +39,8 @@ const editHyperlinkAtCursor = (view, { displayText, href }) => {
39
39
  const { $from } = view.state.selection;
40
40
  const linkMark = $from.marks().find((m) => m.type === hlType);
41
41
  if (!linkMark) return true;
42
- const targetRange = rangeAtCursor(collectHyperlinkRanges($from.parent, $from.start(), hlType, hyperlinkHref(linkMark)), $from.pos);
42
+ const ranges = collectHyperlinkRanges($from.parent, $from.start(), hlType, hyperlinkHref(linkMark));
43
+ const targetRange = rangeAtCursor(ranges, $from.pos);
43
44
  if (!targetRange) return false;
44
45
  const { tooltip } = expectHyperlinkMarkAttrs(linkMark);
45
46
  const newMark = hlType.create({
@@ -69,7 +70,8 @@ const removeHyperlinkAtCursor = (view, { popupHref }) => {
69
70
  }
70
71
  });
71
72
  if (!linkMark) return false;
72
- const targetRange = rangeAtCursor(collectHyperlinkRanges($from.parent, $from.start(), hlType, hyperlinkHref(linkMark)), $from.pos);
73
+ const ranges = collectHyperlinkRanges($from.parent, $from.start(), hlType, hyperlinkHref(linkMark));
74
+ const targetRange = rangeAtCursor(ranges, $from.pos);
73
75
  if (!targetRange) return false;
74
76
  const tr = view.state.tr.removeMark(targetRange.start, targetRange.end, hlType);
75
77
  view.dispatch(tr.scrollIntoView());
@@ -124,7 +124,8 @@ const applyImageWrapType = (view, pos, wrapType) => {
124
124
  const applyImageTransform = (view, pos, action) => {
125
125
  const node = imageNodeAt(view, pos);
126
126
  if (!node) return false;
127
- const transform = computeImageTransform(expectImageAttrs(node).transform ?? "", action);
127
+ const currentTransform = expectImageAttrs(node).transform ?? "";
128
+ const transform = computeImageTransform(currentTransform, action);
128
129
  const tr = view.state.tr.setNodeMarkup(pos, void 0, mergeImageAttrs(node, { transform }));
129
130
  view.dispatch(tr.scrollIntoView());
130
131
  return true;
@@ -5,9 +5,9 @@ import { getTableContext, isInTable as isInTableCell } from "../extensions/nodes
5
5
  import { getListInfo, isInList } from "../extensions/features/ListExtension.js";
6
6
  import { CLIPBOARD_READ_ERROR_EVENT, buildPlainTextSlice, pasteWithoutFormatting } from "./pastePlainText.js";
7
7
  import { getHyperlinkAttrs, getSelectedText, isHyperlinkActive } from "../extensions/marks/HyperlinkExtension.js";
8
- import { addColumnLeft, addColumnRight, addRowAbove, addRowBelow, applyTableStyle, autoFitContents, deleteColumn, deleteRow, deleteTable, distributeColumns, insertTable, mergeCells, removeTableBorders, selectColumn, selectRow, selectTable, setAllTableBorders, setCellBorder, setCellFillColor, setCellMargins, setCellTextDirection, setCellVerticalAlign, setInsideTableBorders, setOutsideTableBorders, setRowHeight, setTableBorderColor, setTableBorderPreset, setTableBorderWidth, setTableBorders, setTableProperties, splitCell, toggleHeaderRow, toggleNoWrap } from "./table.js";
9
8
  import { clearFontFamily, clearFontSize, clearHighlight, clearTextColor, insertHyperlink, removeHyperlink, setFontFamily, setFontSize, setHighlight, setHyperlink, setTextColor, setUnderlineStyle, toggleBold, toggleItalic, toggleStrike, toggleSubscript, toggleSuperscript, toggleUnderline } from "./formatting.js";
10
9
  import { addTabStop, alignCenter, alignJustify, alignLeft, alignRight, applyStyle, clearStyle, decreaseIndent, decreaseListLevel, doubleSpacing, generateTOC, increaseIndent, increaseListLevel, oneAndHalfSpacing, removeList, removeTabStop, setAlignment, setIndentFirstLine, setIndentLeft, setIndentRight, setLineSpacing, setLtr, setRtl, setSpaceAfter, setSpaceBefore, singleSpacing, toggleBidi, toggleBulletList, toggleNumberedList } from "./paragraph.js";
10
+ import { addColumnLeft, addColumnRight, addRowAbove, addRowBelow, applyTableStyle, autoFitContents, deleteColumn, deleteRow, deleteTable, distributeColumns, insertTable, mergeCells, removeTableBorders, selectColumn, selectRow, selectTable, setAllTableBorders, setCellBorder, setCellFillColor, setCellMargins, setCellTextDirection, setCellVerticalAlign, setInsideTableBorders, setOutsideTableBorders, setRowHeight, setTableBorderColor, setTableBorderPreset, setTableBorderWidth, setTableBorders, setTableProperties, splitCell, toggleHeaderRow, toggleNoWrap } from "./table.js";
11
11
  import { PAINTABLE_MARK_NAMES, applyFormatMarks, captureFormatMarks } from "./formatPainter.js";
12
12
  import { insertPageBreak } from "./pageBreak.js";
13
13
  export { CLIPBOARD_READ_ERROR_EVENT, PAINTABLE_MARK_NAMES, acceptAIEditRevision, acceptAllChanges, acceptChange, addColumnLeft, addColumnRight, addCommentMark, addRowAbove, addRowBelow, addTabStop, alignCenter, alignJustify, alignLeft, alignRight, applyFormatMarks, applyStyle, applyTableStyle, autoFitContents, buildPlainTextSlice, captureFormatMarks, clearFontFamily, clearFontSize, clearFormatting, clearHighlight, clearStyle, clearTextColor, createRemoveMarkCommand, createSetMarkCommand, decreaseIndent, decreaseListLevel, deleteColumn, deleteRow, deleteTable, distributeColumns, doubleSpacing, findAIEditRevisionRange, findNextChange, findPreviousChange, generateTOC, getHyperlinkAttrs, getListInfo, getMarkAttr, getParagraphAlignment, getParagraphBidi, getSelectedText, getStyleId, getTableContext, increaseIndent, increaseListLevel, insertHyperlink, insertPageBreak, insertTable, isHyperlinkActive, isInList, isInTableCell as isInTable, isMarkActive, mergeCells, oneAndHalfSpacing, pasteWithoutFormatting, rejectAIEditRevision, rejectAllChanges, rejectChange, removeCommentMark, removeHyperlink, removeList, removeTabStop, removeTableBorders, selectColumn, selectRow, selectTable, setAlignment, setAllTableBorders, setCellBorder, setCellFillColor, setCellMargins, setCellTextDirection, setCellVerticalAlign, setFontFamily, setFontSize, setHighlight, setHyperlink, setIndentFirstLine, setIndentLeft, setIndentRight, setInsideTableBorders, setLineSpacing, setLtr, setOutsideTableBorders, setRowHeight, setRtl, setSpaceAfter, setSpaceBefore, setTableBorderColor, setTableBorderPreset, setTableBorderWidth, setTableBorders, setTableProperties, setTextColor, setUnderlineStyle, singleSpacing, splitCell, toggleBidi, toggleBold, toggleBulletList, toggleHeaderRow, toggleItalic, toggleNoWrap, toggleNumberedList, toggleStrike, toggleSubscript, toggleSuperscript, toggleUnderline };