@stll/folio-core 0.28.0 → 0.29.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 (56) hide show
  1. package/dist/docx/styleParser.js +15 -14
  2. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +1 -2
  3. package/dist/layout-bridge/convert/toFlowBlocks.js +135 -118
  4. package/dist/layout-engine/footnoteColumnReflow.d.ts +15 -0
  5. package/dist/layout-engine/footnoteColumnReflow.js +74 -0
  6. package/dist/layout-engine/index.d.ts +3 -5
  7. package/dist/layout-engine/index.js +36 -15
  8. package/dist/layout-engine/measure/cache.d.ts +1 -1
  9. package/dist/layout-engine/measure/cache.js +11 -2
  10. package/dist/layout-engine/measure/listMarkerWidth.d.ts +3 -1
  11. package/dist/layout-engine/measure/listMarkerWidth.js +6 -4
  12. package/dist/layout-engine/paginator.d.ts +6 -0
  13. package/dist/layout-engine/paginator.js +30 -11
  14. package/dist/layout-engine/types.d.ts +11 -6
  15. package/dist/markdown/renderParagraph.js +7 -2
  16. package/dist/prosemirror/attrs/index.js +5 -3
  17. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +8 -0
  18. package/dist/prosemirror/bookmarkBoundaryAttrs.js +66 -0
  19. package/dist/prosemirror/commands/formatPainter.js +45 -1
  20. package/dist/prosemirror/conversion/fromProseDoc.d.ts +4 -1
  21. package/dist/prosemirror/conversion/fromProseDoc.js +224 -91
  22. package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -1
  23. package/dist/prosemirror/conversion/toProseDoc.js +325 -94
  24. package/dist/prosemirror/extensions/StarterKit.js +11 -3
  25. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +8 -4
  26. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  27. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +9 -5
  28. package/dist/prosemirror/extensions/features/pasteCleanup.d.ts +10 -23
  29. package/dist/prosemirror/extensions/features/pasteCleanup.js +77 -22
  30. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +22 -22
  31. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +9 -0
  32. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +67 -0
  33. package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +10 -4
  34. package/dist/prosemirror/extensions/nodes/FieldExtension.js +77 -59
  35. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +4 -1
  36. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +4 -3
  37. package/dist/prosemirror/listMarker.d.ts +58 -0
  38. package/dist/prosemirror/listMarker.js +185 -0
  39. package/dist/prosemirror/numberedRefFields.d.ts +24 -0
  40. package/dist/prosemirror/numberedRefFields.js +276 -0
  41. package/dist/prosemirror/paraText.js +56 -17
  42. package/dist/prosemirror/plugins/anonymizationDecorations.js +1 -1
  43. package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -1
  44. package/dist/prosemirror/plugins/pmTextScan.js +28 -7
  45. package/dist/prosemirror/plugins/templateDirectives.js +2 -2
  46. package/dist/prosemirror/schema/index.d.ts +2 -2
  47. package/dist/prosemirror/schema/marks.d.ts +3 -1
  48. package/dist/prosemirror/schema/nodes.d.ts +13 -1
  49. package/dist/prosemirror/styles/styleResolver.d.ts +0 -1
  50. package/dist/prosemirror/styles/styleResolver.js +22 -29
  51. package/dist/prosemirror/styles/styleToggleCascade.d.ts +37 -0
  52. package/dist/prosemirror/styles/styleToggleCascade.js +52 -0
  53. package/dist/prosemirror/validation.js +93 -0
  54. package/dist/utils/textFormattingMerge.d.ts +9 -1
  55. package/dist/utils/textFormattingMerge.js +32 -1
  56. package/package.json +1 -1
@@ -4,10 +4,13 @@ import { OUTLINE_STYLE_CSS_ALIASES } from "../../types/documentEnumValues.js";
4
4
  import { pixelsToEmu } from "../../utils/units.js";
5
5
  import { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCharacterStyleMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectLanguageMarkAttrs, expectMathAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunPropertyChangeMarkAttrs, expectRunShadingMarkAttrs, expectSdtAttrs, expectShapeAttrs, expectStrikeMarkAttrs, expectSymbolAttrs, expectTabAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs } from "../attrs/index.js";
6
6
  import { autospacingMatchesBase, hasAutospacingBaseSide } from "../autospacingBase.js";
7
+ import { expectBookmarkBoundaryAttrs } from "../bookmarkBoundaryAttrs.js";
7
8
  import { applyRunFormattingOverrideAttrs } from "../extensions/marks/RunFormattingOverrideExtension.js";
9
+ import { resolveNumberedRefFields } from "../numberedRefFields.js";
8
10
  import { directionToBidi } from "../paragraphDirection.js";
9
11
  import { RUN_FORMATTING_MARK_NAMES } from "../runFormattingMarkNames.js";
10
12
  import { parseShapeGeometryAdjustments } from "../shapeGeometryAdjustments.js";
13
+ import { cascadeStyleTextFormatting } from "../styles/styleToggleCascade.js";
11
14
  import { expectTextBoxAnchorAttrs } from "../textBoxAnchorAttrs.js";
12
15
  import { assertValidProseMirrorDocument } from "../validation.js";
13
16
  import { runShadingAttrsToShading } from "./runShadingMark.js";
@@ -197,12 +200,33 @@ function mapSuggestionStrippedNode(node) {
197
200
  function stripSuggestedProvenance(doc) {
198
201
  return mapSuggestionStrippedNode(doc) ?? doc;
199
202
  }
200
- /**
201
- * Extract block content (paragraphs, tables, block SDTs) from a ProseMirror
202
- * document.
203
- */
204
- function extractBlocks(inputDoc) {
205
- const pmDoc = stripSuggestedProvenance(inputDoc);
203
+ function materializeNumberedRefValues(doc) {
204
+ const results = resolveNumberedRefFields(doc);
205
+ if (results.size === 0) return doc;
206
+ const visit = (node) => {
207
+ if (node.type.name === "field" || node.type.name === "structuredField") {
208
+ const displayText = results.get(node);
209
+ if (displayText !== void 0) return node.type.create({
210
+ ...node.attrs,
211
+ displayText
212
+ }, node.content, node.marks);
213
+ return node;
214
+ }
215
+ if (node.childCount === 0) return node;
216
+ const children = [];
217
+ let changed = false;
218
+ node.forEach((child) => {
219
+ const mappedChild = visit(child);
220
+ children.push(mappedChild);
221
+ changed ||= mappedChild !== child;
222
+ });
223
+ return changed ? node.copy(Fragment.fromArray(children)) : node;
224
+ };
225
+ return visit(doc);
226
+ }
227
+ function extractBlocks(inputDoc, refResolution = "resolve") {
228
+ const strippedDoc = stripSuggestedProvenance(inputDoc);
229
+ const pmDoc = refResolution === "resolve" ? materializeNumberedRefValues(strippedDoc) : strippedDoc;
206
230
  const blocks = [];
207
231
  const textBoxAnchorMarkers = /* @__PURE__ */ new Map();
208
232
  const documentCounts = buildDocumentTrackedChangeCounts(pmDoc);
@@ -270,7 +294,7 @@ function convertPMBlockSdt(node) {
270
294
  if (attrs.rawEndPropertiesXml) properties.rawEndPropertiesXml = attrs.rawEndPropertiesXml;
271
295
  if (attrs.rawSdtChildrenBeforeContent) properties.rawSdtChildrenBeforeContent = attrs.rawSdtChildrenBeforeContent;
272
296
  if (attrs.rawSdtChildrenAfterContent) properties.rawSdtChildrenAfterContent = attrs.rawSdtChildrenAfterContent;
273
- const extracted = extractBlocks(node.type.schema.node("doc", null, node.content));
297
+ const extracted = extractBlocks(node.type.schema.node("doc", null, node.content), "inherit");
274
298
  return {
275
299
  type: "blockSdt",
276
300
  properties,
@@ -390,6 +414,7 @@ function editTextBoxAnchorInContent(content, marker, replacement) {
390
414
  }
391
415
  let nestedContent;
392
416
  if (item?.type === "hyperlink") nestedContent = item.children;
417
+ else if (item?.type === "simpleField") nestedContent = item.content;
393
418
  else if (item?.type === "inlineSdt" || item?.type === "insertion" || item?.type === "deletion" || item?.type === "moveFrom" || item?.type === "moveTo") nestedContent = item.content;
394
419
  if (!nestedContent) continue;
395
420
  const hadContent = nestedContent.length > 0;
@@ -639,14 +664,31 @@ function paragraphAttrsToFormatting(attrs) {
639
664
  if (attrs.suppressAutoHyphens != null) f.suppressAutoHyphens = attrs.suppressAutoHyphens;
640
665
  return f;
641
666
  }
642
- /**
643
- * Extract paragraph content (runs, hyperlinks) from ProseMirror paragraph
644
- *
645
- * Coalesces consecutive text with the same marks into single Runs
646
- * for efficient DOCX representation.
647
- */
667
+ function createTrackedRunWrapper(type, info, child) {
668
+ if (type === "insertion") return {
669
+ type,
670
+ info,
671
+ content: [child]
672
+ };
673
+ if (type === "deletion") return {
674
+ type,
675
+ info,
676
+ content: [child]
677
+ };
678
+ if (type === "moveFrom") return {
679
+ type,
680
+ info,
681
+ content: [child]
682
+ };
683
+ return {
684
+ type,
685
+ info,
686
+ content: [child]
687
+ };
688
+ }
648
689
  function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, textBoxAnchorMarkers, skipLeadingRenderedPageBreak = false) {
649
690
  const content = [];
691
+ const inheritedFormatting = paragraph.type.name === "paragraph" ? expectParagraphAttrs(paragraph).defaultTextFormatting ?? void 0 : void 0;
650
692
  const sortedEmptyHyperlinks = (emptyHyperlinks ?? []).map((attrs, order) => ({
651
693
  attrs,
652
694
  order
@@ -657,6 +699,7 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
657
699
  let currentMarksKey = null;
658
700
  let currentHyperlink = null;
659
701
  let currentHyperlinkKey = null;
702
+ let currentTrackedChange;
660
703
  const openedComments = /* @__PURE__ */ new Set();
661
704
  const commentLastOffset = /* @__PURE__ */ new Map();
662
705
  paragraph.forEach((node, offset) => {
@@ -685,6 +728,7 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
685
728
  for (const commentId of nodeCommentIds) if (!openedComments.has(commentId)) toOpen.push(commentId);
686
729
  if (toClose.length === 0 && toOpen.length === 0) return;
687
730
  flushCurrentInline();
731
+ currentTrackedChange = void 0;
688
732
  for (const commentId of toClose.toSorted((a, b) => a - b)) {
689
733
  content.push({
690
734
  type: "commentRangeEnd",
@@ -706,6 +750,7 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
706
750
  if (!item || item.attrs.offset > offset) break;
707
751
  nextEmptyHyperlink += 1;
708
752
  flushCurrentInline();
753
+ currentTrackedChange = void 0;
709
754
  content.push(createEmptyHyperlink(item.attrs));
710
755
  }
711
756
  };
@@ -720,8 +765,10 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
720
765
  const noteRefMark = node.marks.find((m) => m.type.name === "footnoteRef");
721
766
  const insertionMark = node.marks.find((m) => m.type.name === "insertion");
722
767
  const deletionMark = node.marks.find((m) => m.type.name === "deletion");
768
+ if (!insertionMark && !deletionMark) currentTrackedChange = void 0;
723
769
  if (node.type.name === "textBoxAnchor") {
724
770
  flushCurrentInline();
771
+ currentTrackedChange = void 0;
725
772
  if (!textBoxAnchorMarkers) return;
726
773
  const { anchorId } = expectTextBoxAnchorAttrs(node);
727
774
  const marker = {
@@ -763,38 +810,52 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
763
810
  const changeMark = insertionMark ?? deletionMark;
764
811
  if (!changeMark) return;
765
812
  const changeAttrs = expectTrackedChangeMarkAttrs(changeMark);
766
- const run = createTrackedChangeRun(node, node.marks.filter((m) => m.type.name !== "insertion" && m.type.name !== "deletion"));
767
- if (!run) return;
768
- const trackedContent = linkMark ? {
769
- ...createHyperlink(linkMark),
770
- children: [run]
771
- } : run;
813
+ const otherMarks = node.marks.filter((m) => m.type.name !== "insertion" && m.type.name !== "deletion");
772
814
  const info = {
773
815
  id: changeAttrs.revisionId,
774
816
  author: changeAttrs.author || "Unknown"
775
817
  };
776
818
  if (changeAttrs.date) info.date = changeAttrs.date;
777
819
  if (changeAttrs.initials) info.initials = changeAttrs.initials;
778
- if (insertionMark) if (changeAttrs.moveKind === "moveTo") content.push({
779
- type: "moveTo",
780
- info,
781
- content: [trackedContent]
782
- });
783
- else content.push({
784
- type: "insertion",
785
- info,
786
- content: [trackedContent]
787
- });
788
- else if (changeAttrs.moveKind === "moveFrom") content.push({
789
- type: "moveFrom",
790
- info,
791
- content: [trackedContent]
792
- });
793
- else content.push({
794
- type: "deletion",
795
- info,
796
- content: [trackedContent]
820
+ let type;
821
+ if (insertionMark) type = changeAttrs.moveKind === "moveTo" ? "moveTo" : "insertion";
822
+ else type = changeAttrs.moveKind === "moveFrom" ? "moveFrom" : "deletion";
823
+ const trackedChangeKey = `${type}:${JSON.stringify(info)}`;
824
+ if (linkMark) {
825
+ const linkKey = getLinkKey(linkMark);
826
+ if (!currentTrackedChange || currentTrackedChange.key !== trackedChangeKey || currentTrackedChange.hyperlinkKey !== linkKey) {
827
+ const hyperlink = createHyperlink(linkMark);
828
+ const wrapper = createTrackedRunWrapper(type, info, hyperlink);
829
+ content.push(wrapper);
830
+ currentTrackedChange = {
831
+ key: trackedChangeKey,
832
+ hyperlink,
833
+ hyperlinkKey: linkKey
834
+ };
835
+ }
836
+ if (node.type.name === "bookmarkBoundary") {
837
+ addNodeToHyperlink({
838
+ hyperlink: currentTrackedChange.hyperlink,
839
+ inheritedFormatting,
840
+ node
841
+ });
842
+ return;
843
+ }
844
+ const run = createTrackedChangeRun({
845
+ inheritedFormatting,
846
+ marks: otherMarks,
847
+ node
848
+ });
849
+ if (run) currentTrackedChange.hyperlink.children.push(run);
850
+ return;
851
+ }
852
+ currentTrackedChange = void 0;
853
+ const run = createTrackedChangeRun({
854
+ inheritedFormatting,
855
+ marks: otherMarks,
856
+ node
797
857
  });
858
+ if (run) content.push(createTrackedRunWrapper(type, info, run));
798
859
  return;
799
860
  }
800
861
  if (noteRefMark && !linkMark) {
@@ -809,16 +870,38 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
809
870
  currentHyperlink = createHyperlink(linkMark);
810
871
  currentHyperlinkKey = linkKey;
811
872
  }
812
- addNodeToHyperlink(currentHyperlink, node);
873
+ addNodeToHyperlink({
874
+ hyperlink: currentHyperlink,
875
+ inheritedFormatting,
876
+ node
877
+ });
813
878
  return;
814
879
  }
815
880
  if (currentHyperlink) flushCurrentInline();
816
- if (node.isText) {
881
+ if (node.type.name === "bookmarkBoundary") {
882
+ flushCurrentInline();
883
+ const attrs = expectBookmarkBoundaryAttrs(node);
884
+ if (attrs.type === "start") content.push({
885
+ type: "bookmarkStart",
886
+ id: attrs.id,
887
+ name: attrs.name,
888
+ ...attrs.colFirst !== void 0 ? { colFirst: attrs.colFirst } : {},
889
+ ...attrs.colLast !== void 0 ? { colLast: attrs.colLast } : {}
890
+ });
891
+ else content.push({
892
+ type: "bookmarkEnd",
893
+ id: attrs.id
894
+ });
895
+ } else if (node.isText) {
817
896
  const marksKey = getMarksKey(node.marks);
818
897
  if (currentRun && currentMarksKey === marksKey) appendTextToRun(currentRun, node.text || "");
819
898
  else {
820
899
  if (currentRun) content.push(currentRun);
821
- currentRun = createRunFromText(node.text || "", node.marks);
900
+ currentRun = createRunFromText({
901
+ inheritedFormatting,
902
+ marks: node.marks,
903
+ text: node.text || ""
904
+ });
822
905
  currentMarksKey = marksKey;
823
906
  }
824
907
  } else if (node.type.name === "symbol") {
@@ -839,9 +922,12 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
839
922
  } else if (node.type.name === "renderedPageBreak") {
840
923
  flushCurrentInline();
841
924
  content.push(createRenderedPageBreakRun());
842
- } else if (node.type.name === "field") {
925
+ } else if (node.type.name === "field" || node.type.name === "structuredField") {
843
926
  flushCurrentInline();
844
- content.push(createFieldFromNode(node, node.marks));
927
+ content.push(createFieldFromNode(node, {
928
+ marks: node.marks,
929
+ textBoxAnchorMarkers
930
+ }));
845
931
  } else if (node.type.name === "sdt") {
846
932
  flushCurrentInline();
847
933
  content.push(createInlineSdtFromNode(node, textBoxAnchorMarkers));
@@ -880,11 +966,11 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
880
966
  });
881
967
  return content;
882
968
  }
883
- function createTrackedChangeRun(node, marks) {
969
+ function createTrackedChangeRun({ inheritedFormatting, marks, node }) {
884
970
  const noteRefMark = marks.find((mark) => mark.type.name === "footnoteRef");
885
971
  if (noteRefMark) return createNoteReferenceRun(noteRefMark, marks);
886
972
  if (node.isText) {
887
- const formatting = marksToTextFormatting(marks);
973
+ const formatting = marksToTextFormatting(marks, { inheritedFormatting });
888
974
  const run = {
889
975
  type: "run",
890
976
  content: node.text ? [{
@@ -989,10 +1075,22 @@ function createHyperlink(linkMark) {
989
1075
  if (attrs.rId) hyperlink.rId = attrs.rId;
990
1076
  return hyperlink;
991
1077
  }
992
- /**
993
- * Add a node to a hyperlink
994
- */
995
- function addNodeToHyperlink(hyperlink, node) {
1078
+ function addNodeToHyperlink({ hyperlink, inheritedFormatting, node }) {
1079
+ if (node.type.name === "bookmarkBoundary") {
1080
+ const attrs = expectBookmarkBoundaryAttrs(node);
1081
+ if (attrs.type === "start") hyperlink.children.push({
1082
+ type: "bookmarkStart",
1083
+ id: attrs.id,
1084
+ name: attrs.name,
1085
+ ...attrs.colFirst !== void 0 ? { colFirst: attrs.colFirst } : {},
1086
+ ...attrs.colLast !== void 0 ? { colLast: attrs.colLast } : {}
1087
+ });
1088
+ else hyperlink.children.push({
1089
+ type: "bookmarkEnd",
1090
+ id: attrs.id
1091
+ });
1092
+ return;
1093
+ }
996
1094
  const noteRefMark = node.marks.find((m) => m.type.name === "footnoteRef");
997
1095
  if (noteRefMark) {
998
1096
  hyperlink.children.push(createNoteReferenceRun(noteRefMark, node.marks));
@@ -1000,7 +1098,11 @@ function addNodeToHyperlink(hyperlink, node) {
1000
1098
  }
1001
1099
  const nonLinkMarks = node.marks.filter((m) => m.type.name !== "hyperlink");
1002
1100
  if (node.isText && node.text) {
1003
- const run = createRunFromText(node.text, nonLinkMarks);
1101
+ const run = createRunFromText({
1102
+ inheritedFormatting,
1103
+ marks: nonLinkMarks,
1104
+ text: node.text
1105
+ });
1004
1106
  hyperlink.children.push(run);
1005
1107
  return;
1006
1108
  }
@@ -1044,11 +1146,8 @@ function createNoteReferenceRun(noteRefMark, marks) {
1044
1146
  if (vertAlign) run.formatting = { vertAlign };
1045
1147
  return run;
1046
1148
  }
1047
- /**
1048
- * Create a Run from text and marks
1049
- */
1050
- function createRunFromText(text, marks) {
1051
- const formatting = getRunFormattingFromMarks(marks);
1149
+ function createRunFromText({ inheritedFormatting, marks, text }) {
1150
+ const formatting = getRunFormattingFromMarks(marks, { inheritedFormatting });
1052
1151
  const run = {
1053
1152
  type: "run",
1054
1153
  content: [{
@@ -1082,9 +1181,9 @@ function restoreRunPropertyChanges(run, marks) {
1082
1181
  if (changes.length === 0) return;
1083
1182
  run.propertyChanges = [...changes];
1084
1183
  }
1085
- function getRunFormattingFromMarks(marks) {
1184
+ function getRunFormattingFromMarks(marks, options) {
1086
1185
  if (!marks || marks.length === 0) return;
1087
- const formatting = marksToTextFormatting(marks);
1186
+ const formatting = marksToTextFormatting(marks, options);
1088
1187
  return Object.keys(formatting).length > 0 ? formatting : void 0;
1089
1188
  }
1090
1189
  /**
@@ -1138,13 +1237,10 @@ function createTabRun(node, marks) {
1138
1237
  if (formatting) run.formatting = formatting;
1139
1238
  return run;
1140
1239
  }
1141
- /**
1142
- * Create a SimpleField or ComplexField from a PM field node
1143
- */
1144
- function createFieldFromNode(node, marks) {
1240
+ function createFieldFromNode(node, { marks, textBoxAnchorMarkers }) {
1145
1241
  const attrs = expectFieldAttrs(node);
1146
1242
  const formatting = marks && marks.length > 0 ? marksToTextFormatting(marks) : void 0;
1147
- let displayText = attrs.displayText || "";
1243
+ let displayText = attrs.displayText ?? "";
1148
1244
  if (!displayText) switch (attrs.fieldType) {
1149
1245
  case "PAGE":
1150
1246
  displayText = "1";
@@ -1164,13 +1260,15 @@ function createFieldFromNode(node, marks) {
1164
1260
  }],
1165
1261
  ...formatting && Object.keys(formatting).length > 0 ? { formatting } : {}
1166
1262
  };
1263
+ const extractedContent = extractParagraphContent(node, void 0, void 0, textBoxAnchorMarkers).filter((content) => content.type === "run" || content.type === "hyperlink");
1264
+ const fieldContent = extractedContent.length > 0 ? synchronizeFieldDisplayText(extractedContent, displayText, displayRun) : [];
1167
1265
  if (attrs.fieldKind === "complex") {
1168
1266
  const complex = {
1169
1267
  type: "complexField",
1170
1268
  instruction: attrs.instruction,
1171
1269
  fieldType: attrs.fieldType,
1172
1270
  fieldCode: [],
1173
- fieldResult: [displayRun]
1271
+ fieldResult: fieldContent.length > 0 ? fieldContent.filter((content) => content.type === "run") : [displayRun]
1174
1272
  };
1175
1273
  if (attrs.fldLock) complex.fldLock = true;
1176
1274
  if (attrs.dirty) complex.dirty = true;
@@ -1180,12 +1278,45 @@ function createFieldFromNode(node, marks) {
1180
1278
  type: "simpleField",
1181
1279
  instruction: attrs.instruction,
1182
1280
  fieldType: attrs.fieldType,
1183
- content: [displayRun]
1281
+ content: fieldContent.length > 0 ? fieldContent : [displayRun]
1184
1282
  };
1185
1283
  if (attrs.fldLock) simple.fldLock = true;
1186
1284
  if (attrs.dirty) simple.dirty = true;
1187
1285
  return simple;
1188
1286
  }
1287
+ const synchronizeFieldDisplayText = (content, displayText, fallbackRun) => {
1288
+ let currentText = "";
1289
+ const visitRuns = (visit) => {
1290
+ for (const child of content) {
1291
+ if (child.type === "run") {
1292
+ visit(child);
1293
+ continue;
1294
+ }
1295
+ for (const hyperlinkChild of child.children) if (hyperlinkChild.type === "run") visit(hyperlinkChild);
1296
+ }
1297
+ };
1298
+ visitRuns((run) => {
1299
+ for (const runContent of run.content) if (runContent.type === "text") currentText += runContent.text;
1300
+ });
1301
+ if (currentText === displayText) return content;
1302
+ let replacedText = false;
1303
+ visitRuns((run) => {
1304
+ for (const runContent of run.content) {
1305
+ if (runContent.type !== "text") continue;
1306
+ runContent.text = replacedText ? "" : displayText;
1307
+ replacedText = true;
1308
+ }
1309
+ });
1310
+ if (replacedText) return content;
1311
+ const hyperlink = content.find((child) => child.type === "hyperlink");
1312
+ if (!hyperlink) {
1313
+ content.push(fallbackRun);
1314
+ return content;
1315
+ }
1316
+ const firstEnd = hyperlink.children.findIndex((child) => child.type === "bookmarkEnd");
1317
+ hyperlink.children.splice(firstEnd < 0 ? hyperlink.children.length : firstEnd, 0, fallbackRun);
1318
+ return content;
1319
+ };
1189
1320
  /**
1190
1321
  * Create a MathEquation from a PM math node
1191
1322
  */
@@ -1354,11 +1485,9 @@ function createShapeRun(node) {
1354
1485
  }]
1355
1486
  };
1356
1487
  }
1357
- /**
1358
- * Convert ProseMirror marks to TextFormatting
1359
- */
1360
- function marksToTextFormatting(marks) {
1488
+ function marksToTextFormatting(marks, options) {
1361
1489
  const formatting = {};
1490
+ let directOverrideFormatting;
1362
1491
  let characterStyleRPr;
1363
1492
  let runFormattingOverrideMark;
1364
1493
  for (const mark of marks) switch (mark.type.name) {
@@ -1483,8 +1612,18 @@ function marksToTextFormatting(marks) {
1483
1612
  }
1484
1613
  default: break;
1485
1614
  }
1486
- if (runFormattingOverrideMark) applyRunFormattingOverrideAttrs(formatting, expectRunFormattingOverrideMarkAttrs(runFormattingOverrideMark));
1487
- if (characterStyleRPr) return subtractCharacterStyleFormatting(formatting, characterStyleRPr);
1615
+ if (runFormattingOverrideMark) {
1616
+ const overrideAttrs = expectRunFormattingOverrideMarkAttrs(runFormattingOverrideMark);
1617
+ applyRunFormattingOverrideAttrs(formatting, overrideAttrs);
1618
+ directOverrideFormatting = {};
1619
+ applyRunFormattingOverrideAttrs(directOverrideFormatting, overrideAttrs);
1620
+ }
1621
+ if (characterStyleRPr) return subtractCharacterStyleFormatting({
1622
+ directOverrideFormatting,
1623
+ formatting,
1624
+ inheritedFormatting: options?.inheritedFormatting,
1625
+ styleRPr: characterStyleRPr
1626
+ });
1488
1627
  return formatting;
1489
1628
  }
1490
1629
  /**
@@ -1509,35 +1648,29 @@ const NEGATABLE_STYLE_KEYS = [
1509
1648
  ["outline", null],
1510
1649
  ["rtl", null]
1511
1650
  ];
1512
- /**
1513
- * Drop run formatting values that the run's character style already provides
1514
- * (the `w:rStyle` reference re-imposes them on load), so a styled run
1515
- * serializes back to a style reference instead of baked direct formatting.
1516
- *
1517
- * `styleRPr` is the load-time snapshot from the characterStyle mark, captured
1518
- * in the same normal form this module produces from marks, so value equality
1519
- * is a faithful "came from the style and is unchanged" check. Values that
1520
- * differ (user edits, direct overrides from the source document) stay as
1521
- * direct formatting, which wins over the style per the OOXML cascade.
1522
- *
1523
- * When the user *removes* a boolean property the style supplied (e.g. toggling
1524
- * off bold on text whose `w:rStyle` is bold), that key is no longer present in
1525
- * `formatting`, so the subtraction loop above never sees it. Without a counter,
1526
- * the run keeps the style reference and Word re-applies the removed formatting
1527
- * on load. The second pass emits an explicit negative override (`false`) for
1528
- * each negatable boolean the style set to `true` but the run no longer carries.
1529
- */
1530
- function subtractCharacterStyleFormatting(formatting, styleRPr) {
1651
+ function subtractCharacterStyleFormatting({ directOverrideFormatting, formatting, inheritedFormatting, styleRPr }) {
1531
1652
  const result = {};
1653
+ const effectiveStyleFormatting = cascadeStyleTextFormatting([{
1654
+ formatting: inheritedFormatting,
1655
+ type: "direct"
1656
+ }, {
1657
+ formatting: styleRPr,
1658
+ type: "style"
1659
+ }]).formatting;
1532
1660
  for (const key of Object.keys(formatting)) {
1533
1661
  const value = formatting[key];
1534
1662
  if (value === void 0) continue;
1535
- const styleValue = key === "styleId" ? void 0 : styleRPr[key];
1663
+ if (directOverrideFormatting?.[key] !== void 0) {
1664
+ result[key] = value;
1665
+ continue;
1666
+ }
1667
+ const styleValue = key === "styleId" ? void 0 : effectiveStyleFormatting?.[key];
1668
+ if ((key === "boldCs" || key === "italicCs") && styleValue === false && value === true) continue;
1536
1669
  if (styleValue !== void 0 && JSON.stringify(value) === JSON.stringify(styleValue)) continue;
1537
1670
  result[key] = value;
1538
1671
  }
1539
1672
  for (const [key, csKey] of NEGATABLE_STYLE_KEYS) {
1540
- if (styleRPr[key] !== true || formatting[key] !== void 0) continue;
1673
+ if (effectiveStyleFormatting?.[key] !== true || formatting[key] !== void 0) continue;
1541
1674
  result[key] = false;
1542
1675
  if (csKey !== null) result[csKey] = false;
1543
1676
  }
@@ -22,7 +22,10 @@ declare function standaloneTableCellToProseMirror(cell: document_d_exports.Table
22
22
  /**
23
23
  * Convert TextFormatting to ProseMirror marks
24
24
  */
25
- declare function textFormattingToMarks(formatting: document_d_exports.TextFormatting | undefined): ReturnType<typeof schema.mark>[];
25
+ type TextFormattingToMarksOptions = {
26
+ overrideFormatting: document_d_exports.TextFormatting | undefined;
27
+ };
28
+ declare function textFormattingToMarks(formatting: document_d_exports.TextFormatting | undefined, options?: TextFormattingToMarksOptions): ReturnType<typeof schema.mark>[];
26
29
  /**
27
30
  * Convert HeaderFooter content (array of Paragraph/Table blocks) to a ProseMirror document.
28
31
  * Used for editing headers/footers in their own ProseMirror editor and for