@stll/folio-core 0.37.2 → 0.37.4

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 (52) hide show
  1. package/dist/ai-edits/table-cell-mutations.js +10 -5
  2. package/dist/ai-edits/table-template.js +19 -5
  3. package/dist/controller/hiddenEditorManager.js +11 -0
  4. package/dist/controller/layoutPipeline.js +1 -1
  5. package/dist/display-list/build/textBoxPrimitives.js +34 -1
  6. package/dist/display-list/dom/renderDisplayListToDom.js +5 -2
  7. package/dist/display-list/types.d.ts +8 -4
  8. package/dist/docx/paragraphPropertySource.d.ts +82 -2
  9. package/dist/docx/paragraphPropertySource.js +569 -3
  10. package/dist/docx/parser.js +9 -0
  11. package/dist/docx/server/materializeYjsDocx.d.ts +1 -1
  12. package/dist/docx/server/materializeYjsDocx.js +21 -2
  13. package/dist/headless-layout.js +1 -1
  14. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +7 -1
  15. package/dist/layout-bridge/convert/headerFooterLayout.js +20 -3
  16. package/dist/layout-bridge/convert/toFlowBlocks.js +43 -12
  17. package/dist/layout-engine/index.js +20 -10
  18. package/dist/layout-engine/measure/measureBlocks.d.ts +7 -3
  19. package/dist/layout-engine/measure/measureBlocks.js +10 -7
  20. package/dist/layout-engine/measure/measureParagraph.d.ts +2 -0
  21. package/dist/layout-engine/measure/measureParagraph.js +1 -1
  22. package/dist/layout-engine/paginator.d.ts +1 -1
  23. package/dist/layout-engine/paginator.js +41 -4
  24. package/dist/layout-engine/textBoxFlow.d.ts +2 -0
  25. package/dist/layout-engine/textBoxFlow.js +9 -5
  26. package/dist/layout-engine/types.d.ts +6 -0
  27. package/dist/layout-painter/documentColors.d.ts +10 -1
  28. package/dist/layout-painter/documentColors.js +16 -1
  29. package/dist/layout-painter/renderParagraph.js +9 -24
  30. package/dist/layout-painter/renderTable.js +3 -3
  31. package/dist/layout-painter/renderTextBox.js +2 -1
  32. package/dist/pdf/pageSpace.d.ts +12 -1
  33. package/dist/pdf/pageSpace.js +24 -1
  34. package/dist/pdf/paint.js +2 -2
  35. package/dist/prosemirror/attrs/index.js +32 -8
  36. package/dist/prosemirror/commands/comments.js +12 -3
  37. package/dist/prosemirror/commands/tableCellMergeResolution.js +18 -11
  38. package/dist/prosemirror/conversion/fromProseDoc.js +114 -25
  39. package/dist/prosemirror/conversion/toProseDoc.js +101 -8
  40. package/dist/prosemirror/extensions/core/DocExtension.js +2 -0
  41. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
  42. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +5 -1
  43. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +132 -41
  44. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -61
  45. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +16 -0
  46. package/dist/prosemirror/schema/nodes.d.ts +14 -2
  47. package/dist/prosemirror/schema/nodes.js +7 -0
  48. package/dist/prosemirror/yjsParagraphSourceContract.d.ts +9 -0
  49. package/dist/prosemirror/yjsParagraphSourceContract.js +26 -0
  50. package/dist/utils/rotationBoundingBox.d.ts +5 -1
  51. package/dist/utils/rotationBoundingBox.js +9 -1
  52. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  import { numPrEqual } from "../../docx/numberingParser.js";
2
- import { copyParagraphPropertySource, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceTransferId, linkParagraphPropertySourceCandidate, recreateProseNodeWithParagraphPropertySource } from "../../docx/paragraphPropertySource.js";
2
+ import { PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR, ParagraphPropertySourceValidationError, copyDocumentParagraphPropertySourceContract, copyDocumentParagraphPropertySources, copyParagraphPropertyCapture, copyParagraphPropertySource, decodeTableCellParagraphSourcePayload, getDocumentParagraphPropertySourceContract, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceToken, getParagraphPropertySourceTransferId, getProseDocumentParagraphPropertySourceContract, getProseParagraphPropertySourceToken, isParagraphPropertySourceToken, linkParagraphPropertySourceCandidate, paragraphPropertySourceBelongsToDocument, paragraphPropertySourceTokenMatchesContract, recreateProseNodeWithParagraphPropertySource, restoreTableCellsWithParagraphPropertySources, visitDocumentStoryParagraphs, visitTableCellParagraphPropertySourceBindings } from "../../docx/paragraphPropertySource.js";
3
3
  import { visitDocxParagraphs } from "../../docx/paragraphTraversal.js";
4
4
  import { ShapeOutlineStyleSchema, narrowEnum } from "../../docx/parserEnums.js";
5
5
  import { DATE_UTC_ATTRIBUTE } from "../../docx/trackedChangeInfo.js";
@@ -103,23 +103,84 @@ const uniqueParagraphsById = (content, includeTransferIds = false) => {
103
103
  });
104
104
  return paragraphs;
105
105
  };
106
+ const restoreParagraphPropertySource = (paragraph, baseParagraph) => {
107
+ copyParagraphPropertySource(paragraph, baseParagraph);
108
+ const baseFormatting = baseParagraph.formatting;
109
+ if (!baseFormatting?.numPr || !baseFormatting.numPrFromStyle || !numPrEqual(baseFormatting.numPr, baseFormatting.numPrFromStyle)) return;
110
+ const { numPr, numPrFromStyle, ...authoredFormatting } = baseFormatting;
111
+ if (canonicalJson(paragraph.formatting ?? {}) !== canonicalJson(authoredFormatting)) return;
112
+ paragraph.formatting = {
113
+ ...paragraph.formatting,
114
+ numPr,
115
+ numPrFromStyle
116
+ };
117
+ };
106
118
  const restoreParagraphPropertySources = (content, baseContent, linkedTargets, linkedSources) => {
107
119
  const baseParagraphs = uniqueParagraphsById(baseContent, true);
108
120
  for (const [paraId, paragraph] of uniqueParagraphsById(content)) {
109
121
  const baseParagraph = baseParagraphs.get(paraId);
110
122
  if (!paragraph || !baseParagraph || linkedTargets.has(paragraph) || linkedSources.has(baseParagraph) || !getParagraphPropertySource(baseParagraph)) continue;
111
- copyParagraphPropertySource(paragraph, baseParagraph);
112
- const baseFormatting = baseParagraph.formatting;
113
- if (!baseFormatting?.numPr || !baseFormatting.numPrFromStyle || !numPrEqual(baseFormatting.numPr, baseFormatting.numPrFromStyle)) continue;
114
- const { numPr, numPrFromStyle, ...authoredFormatting } = baseFormatting;
115
- if (canonicalJson(paragraph.formatting ?? {}) !== canonicalJson(authoredFormatting)) continue;
116
- paragraph.formatting = {
117
- ...paragraph.formatting,
118
- numPr,
119
- numPrFromStyle
120
- };
123
+ restoreParagraphPropertySource(paragraph, baseParagraph);
121
124
  }
122
125
  };
126
+ const sourceValidationError = (code, message) => new ParagraphPropertySourceValidationError({
127
+ code,
128
+ message
129
+ });
130
+ const validateParagraphPropertySourceTokens = (pmDoc, baseDocument, contract) => {
131
+ const sourceParagraphs = copyDocumentParagraphPropertySources(baseDocument);
132
+ if (!sourceParagraphs) panic("A paragraph-property source contract lost its source registry");
133
+ const currentTokens = /* @__PURE__ */ new Set();
134
+ visitDocumentStoryParagraphs(baseDocument.package.document.content, (paragraph) => {
135
+ const token = getParagraphPropertySourceToken(paragraph);
136
+ if (!token) {
137
+ if (paragraphPropertySourceBelongsToDocument(paragraph, baseDocument)) throw sourceValidationError("invalid_token", "A source-bound paragraph is missing its paragraph-property token.");
138
+ return;
139
+ }
140
+ if (!paragraphPropertySourceTokenMatchesContract(token, contract)) throw sourceValidationError("invalid_token", "The source document contains an invalid paragraph-property token.");
141
+ if (!sourceParagraphs.has(token)) throw sourceValidationError("unknown_token", "The source document contains an unknown paragraph-property token.");
142
+ if (currentTokens.has(token)) throw sourceValidationError("duplicate_token", "The source document contains a duplicate paragraph-property token.");
143
+ currentTokens.add(token);
144
+ });
145
+ const seen = /* @__PURE__ */ new Set();
146
+ const validateToken = (token) => {
147
+ if (token === null || token === void 0) return;
148
+ if (!isParagraphPropertySourceToken(token)) throw sourceValidationError("invalid_token", "A paragraph contains a malformed paragraph-property token.");
149
+ if (!paragraphPropertySourceTokenMatchesContract(token, contract)) throw sourceValidationError("unknown_token", "A paragraph-property token belongs to a different source document.");
150
+ if (seen.has(token)) throw sourceValidationError("duplicate_token", "A paragraph-property token is attached to more than one paragraph.");
151
+ if (!sourceParagraphs.has(token)) throw sourceValidationError("unknown_token", "A paragraph-property token is not present in the source document.");
152
+ seen.add(token);
153
+ };
154
+ const validateTableCellBinding = (binding) => {
155
+ switch (binding.type) {
156
+ case "authored": return;
157
+ case "source":
158
+ validateToken(binding.token);
159
+ return;
160
+ default: return binding;
161
+ }
162
+ };
163
+ pmDoc.descendants((node) => {
164
+ if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
165
+ const continuationCells = expectTableCellAttrs(node)._docxVMergeContinuationCells;
166
+ if (continuationCells !== void 0 && continuationCells !== null) visitTableCellParagraphPropertySourceBindings(decodeTableCellParagraphSourcePayload(continuationCells), (binding) => validateTableCellBinding(binding));
167
+ return true;
168
+ }
169
+ if (node.type.name !== "paragraph") return true;
170
+ validateToken(getProseParagraphPropertySourceToken(node));
171
+ return false;
172
+ });
173
+ return sourceParagraphs;
174
+ };
175
+ const restoreParagraphPropertySourcesByToken = (content, baseParagraphs) => {
176
+ visitDocumentStoryParagraphs(content, (paragraph) => {
177
+ const token = getParagraphPropertySourceToken(paragraph);
178
+ if (!token) return;
179
+ const baseParagraph = baseParagraphs.get(token);
180
+ if (!baseParagraph) panic("Validated paragraph-property token lost its source owner");
181
+ restoreParagraphPropertySource(paragraph, baseParagraph);
182
+ });
183
+ };
123
184
  const restoreLinkedParagraphPropertySources = (content) => {
124
185
  const targetsBySource = /* @__PURE__ */ new Map();
125
186
  const linkedTargets = /* @__PURE__ */ new Set();
@@ -133,7 +194,7 @@ const restoreLinkedParagraphPropertySources = (content) => {
133
194
  });
134
195
  for (const [source, targets] of targetsBySource) if (targets.length === 1) {
135
196
  const target = targets.at(0);
136
- if (target) copyParagraphPropertySource(target, source);
197
+ if (target) copyParagraphPropertyCapture(target, source);
137
198
  }
138
199
  return {
139
200
  targets: linkedTargets,
@@ -143,20 +204,30 @@ const restoreLinkedParagraphPropertySources = (content) => {
143
204
  /** Convert a ProseMirror document to the document model. */
144
205
  function fromProseDoc(pmDoc, baseDocument) {
145
206
  assertValidProseMirrorDocument(pmDoc, "Cannot convert invalid ProseMirror document to DOCX model");
207
+ const baseContract = baseDocument ? getDocumentParagraphPropertySourceContract(baseDocument) : void 0;
208
+ const proseContractAttribute = pmDoc.attrs[PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR];
209
+ const proseContract = getProseDocumentParagraphPropertySourceContract(pmDoc);
210
+ if (proseContractAttribute !== null && proseContractAttribute !== void 0 && proseContract === null || (baseContract ?? null) !== proseContract) throw sourceValidationError("contract_mismatch", "The ProseMirror document does not match its paragraph-property source document.");
211
+ const tokenSources = baseContract && proseContract && baseDocument ? validateParagraphPropertySourceTokens(pmDoc, baseDocument, baseContract) : null;
146
212
  const blocks = extractBlocks(pmDoc, "resolve", baseDocument?.package.styles ? createStyleEngine(baseDocument.package.styles) : null);
147
213
  const linkedSources = restoreLinkedParagraphPropertySources(blocks);
148
- if (baseDocument) restoreParagraphPropertySources(blocks, baseDocument.package.document.content, linkedSources.targets, linkedSources.sources);
214
+ if (tokenSources) restoreParagraphPropertySourcesByToken(blocks, tokenSources);
215
+ else if (baseDocument) restoreParagraphPropertySources(blocks, baseDocument.package.document.content, linkedSources.targets, linkedSources.sources);
149
216
  const documentBody = { content: blocks };
150
217
  if (baseDocument?.package.document.finalSectionProperties) documentBody.finalSectionProperties = baseDocument.package.document.finalSectionProperties;
151
218
  if (baseDocument?.package.document.sections) documentBody.sections = baseDocument.package.document.sections;
152
219
  if (baseDocument?.package.document.comments) documentBody.comments = baseDocument.package.document.comments;
153
- if (baseDocument) return {
154
- ...baseDocument,
155
- package: {
156
- ...baseDocument.package,
157
- document: documentBody
158
- }
159
- };
220
+ if (baseDocument) {
221
+ const updatedDocument = {
222
+ ...baseDocument,
223
+ package: {
224
+ ...baseDocument.package,
225
+ document: documentBody
226
+ }
227
+ };
228
+ copyDocumentParagraphPropertySourceContract(updatedDocument, baseDocument);
229
+ return updatedDocument;
230
+ }
160
231
  return { package: { document: documentBody } };
161
232
  }
162
233
  const isSuggestedMark = (mark) => mark.attrs["provenance"] === "suggested";
@@ -2606,7 +2677,7 @@ function convertPMTableRow(node, documentCounts, activeVerticalMerges, styleReso
2606
2677
  const colspan = Math.max(cellAttrs.colspan, 1);
2607
2678
  cells.push(convertPMTableCell(cellNode, documentCounts, styleResolver));
2608
2679
  if (cellAttrs.rowspan > 1) {
2609
- const continuationCells = cellAttrs._docxVMergeContinuationCells;
2680
+ const continuationCells = cellAttrs._docxVMergeContinuationCells !== void 0 && cellAttrs._docxVMergeContinuationCells !== null ? restoreTableCellsWithParagraphPropertySources(decodeTableCellParagraphSourcePayload(cellAttrs._docxVMergeContinuationCells)) : void 0;
2610
2681
  activeVerticalMerges?.set(gridColumn, {
2611
2682
  remainingRows: cellAttrs.rowspan - 1,
2612
2683
  colspan,
@@ -2791,6 +2862,24 @@ function tableCellAttrsToFormatting(attrs) {
2791
2862
  if (attrs.margins) f.margins = buildCellMarginsFromAttrs(attrs.margins);
2792
2863
  return f;
2793
2864
  }
2865
+ const isUnchangedSourceEmptyPlaceholder = (blocks) => {
2866
+ const paragraph = blocks.at(0);
2867
+ if (blocks.length !== 1 || paragraph?.type !== "paragraph" || paragraph.content.length !== 0) return false;
2868
+ return Object.keys(paragraph).every((key) => key === "type" || key === "content" || key === "paraId" || key === "textId");
2869
+ };
2870
+ const projectTextBoxBodyContent = (state, blocks) => {
2871
+ switch (state.type) {
2872
+ case "source-empty": return isUnchangedSourceEmptyPlaceholder(blocks) ? { type: "source-empty" } : {
2873
+ type: "authored",
2874
+ content: blocks
2875
+ };
2876
+ case "authored": return {
2877
+ type: "authored",
2878
+ content: blocks
2879
+ };
2880
+ default: return state;
2881
+ }
2882
+ };
2794
2883
  /**
2795
2884
  * Convert a ProseMirror textBox node back to a Paragraph wrapping a ShapeContent run.
2796
2885
  * The text box content becomes a Shape with textBody.
@@ -2803,6 +2892,7 @@ function convertPMTextBox(node, styleResolver = null) {
2803
2892
  if (child.type.name === "paragraph") childBlocks.push(convertPMParagraph(child, void 0, void 0, styleResolver));
2804
2893
  else if (child.type.name === "table") childBlocks.push(convertPMTable(child, void 0, styleResolver));
2805
2894
  });
2895
+ const textBodyContent = projectTextBoxBodyContent(attrs._docxTextBodyContentState, childBlocks);
2806
2896
  const shape = {
2807
2897
  type: "shape",
2808
2898
  shapeType: "textBox",
@@ -2811,10 +2901,7 @@ function convertPMTextBox(node, styleResolver = null) {
2811
2901
  height: attrs.height ? pixelsToEmu(attrs.height) : 0
2812
2902
  },
2813
2903
  textBody: {
2814
- content: childBlocks.length > 0 ? childBlocks : [{
2815
- type: "paragraph",
2816
- content: []
2817
- }],
2904
+ content: textBodyContent.type === "source-empty" ? [] : textBodyContent.content,
2818
2905
  ...attrs.autoFit !== void 0 ? { autoFit: attrs.autoFit } : {},
2819
2906
  ...attrs.textWrap !== void 0 ? { textWrap: attrs.textWrap } : {},
2820
2907
  ...verticalAlign !== void 0 ? { anchor: verticalAlign } : {},
@@ -2829,6 +2916,8 @@ function convertPMTextBox(node, styleResolver = null) {
2829
2916
  }
2830
2917
  };
2831
2918
  if (attrs.textBoxId) shape.id = attrs.textBoxId;
2919
+ const transform = parseTransformAttr(attrs.transform);
2920
+ if (transform) shape.transform = transform;
2832
2921
  if (attrs.fillColor) shape.fill = {
2833
2922
  type: "solid",
2834
2923
  color: { rgb: attrs.fillColor.replace("#", "") }
@@ -1,5 +1,5 @@
1
1
  import { resolveColorValueToHex } from "../../docx/drawingUtils.js";
2
- import { linkProseParagraphPropertySource, recreateProseNodeWithParagraphPropertySource } from "../../docx/paragraphPropertySource.js";
2
+ import { PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR, createProseParagraphWithPropertySource, getDocumentParagraphPropertySourceContract, recreateProseNodeWithParagraphPropertySource, transportTableCellsWithParagraphPropertySources } from "../../docx/paragraphPropertySource.js";
3
3
  import { buildPageBreakRunSourceDescendantIndex } from "../../internal/pageBreakRunSourceDescendantIndex.js";
4
4
  import { createStyleEngine } from "../../style-engine/styleEngine.js";
5
5
  import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js";
@@ -131,6 +131,7 @@ function toProseDoc(document, options) {
131
131
  const finalSectionStart = document.package.document.sections?.at(-1)?.properties.sectionStart ?? null;
132
132
  const adjustLineHeightInTable = document.package.settings?.adjustLineHeightInTable === true;
133
133
  const pmDoc = stampNumberedRefFieldBaselines(schema.node("doc", {
134
+ [PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR]: getDocumentParagraphPropertySourceContract(document) ?? null,
134
135
  _finalSectionStart: finalSectionStart,
135
136
  _adjustLineHeightInTable: adjustLineHeightInTable
136
137
  }, nodes));
@@ -205,7 +206,8 @@ function convertParagraph(paragraph, styleResolver, context, activeCommentIds, e
205
206
  let paragraphStyleRunFormatting;
206
207
  let paragraphStyleFontFamily;
207
208
  if (styleResolver) {
208
- styleRunFormatting = styleResolver.resolveParagraphStyle(paragraph.formatting?.styleId).runFormatting;
209
+ const resolved = styleResolver.resolveParagraphStyle(paragraph.formatting?.styleId);
210
+ styleRunFormatting = extraRunFormatting === void 0 ? resolved.runFormatting : withoutFontFamily(resolved.runFormatting);
209
211
  const paragraphStyle = paragraph.formatting?.styleId ? styleResolver.getStyle(paragraph.formatting.styleId) ?? styleResolver.getDefaultParagraphStyle() : styleResolver.getDefaultParagraphStyle();
210
212
  paragraphStyleRunFormatting = paragraphStyle?.type === "paragraph" ? paragraphStyle.rPr : void 0;
211
213
  paragraphStyleFontFamily = resolveParagraphStyleFontFamily(paragraph.formatting?.styleId, styleResolver);
@@ -343,18 +345,28 @@ function convertParagraph(paragraph, styleResolver, context, activeCommentIds, e
343
345
  }
344
346
  if (bookmarksArr) attrs.bookmarks = bookmarksArr;
345
347
  if (emptyHyperlinks) attrs._emptyHyperlinks = emptyHyperlinks;
346
- const proseParagraph = schema.node("paragraph", attrs, inlineNodes);
347
- linkProseParagraphPropertySource(proseParagraph, paragraph);
348
- return proseParagraph;
348
+ return createProseParagraphWithPropertySource(schema.nodes["paragraph"], paragraph, {
349
+ attrs,
350
+ content: inlineNodes
351
+ });
349
352
  }
353
+ const withoutFontFamily = (formatting) => {
354
+ if (!formatting?.fontFamily) return formatting;
355
+ const { fontFamily: _fontFamily, ...withoutFont } = formatting;
356
+ return Object.keys(withoutFont).length > 0 ? withoutFont : void 0;
357
+ };
350
358
  const resolveParagraphStyleFontFamily = (styleId, styleResolver) => {
351
359
  let style = styleId ? styleResolver.getStyle(styleId) : styleResolver.getDefaultParagraphStyle();
352
360
  const visited = /* @__PURE__ */ new Set();
361
+ const styleChain = [];
353
362
  while (style?.type === "paragraph" && !visited.has(style.styleId)) {
354
363
  visited.add(style.styleId);
355
- if (style.rPr?.fontFamily) return style.rPr.fontFamily;
364
+ if (style.rPr?.fontFamily) styleChain.push({ fontFamily: style.rPr.fontFamily });
356
365
  style = style.basedOn ? styleResolver.getStyle(style.basedOn) : void 0;
357
366
  }
367
+ let formatting;
368
+ for (const styleFormatting of styleChain.toReversed()) formatting = mergeTextFormatting(formatting, styleFormatting);
369
+ return formatting?.fontFamily;
358
370
  };
359
371
  /**
360
372
  * Apply comment marks to PM nodes within a comment range.
@@ -789,7 +801,14 @@ function paragraphContentHasMeaningfulContent(content) {
789
801
  return true;
790
802
  }
791
803
  function convertTable(table, styleResolver, context) {
792
- for (const row of table.rows) for (const cell of row.cells) assertSourceContainerHasNoPageBreakRun(cell.content, "table-cell", context.pageBreakRunSourceDescendants);
804
+ for (const row of table.rows) for (const cell of row.cells) {
805
+ if (cell.formatting?.vMerge === "continue" && context.pageBreakRunSourceDescendants.containsPageBreakRun(cell.content)) throw new UnsupportedDocxToProseMirrorConversionError({
806
+ message: `${PAGE_BREAK_CONTAINER_DESCRIPTIONS["table-cell"]} cannot be represented in the editor model`,
807
+ owner: "table-cell",
808
+ contentType: "break"
809
+ });
810
+ assertSourceContainerHasNoPageBreakRun(cell.content, "table-cell", context.pageBreakRunSourceDescendants);
811
+ }
793
812
  const rowSpanMap = calculateRowSpans(table);
794
813
  const columnWidths = table.columnWidths;
795
814
  const totalWidth = columnWidths?.reduce((sum, w) => sum + w, 0) ?? 0;
@@ -1077,7 +1096,7 @@ function convertTableCell({ cell, styleResolver, context, isHeader, gridWidthPer
1077
1096
  ...cell.structuralChange.verticalMergeOriginal !== void 0 ? { verticalMergeOriginal: cell.structuralChange.verticalMergeOriginal } : {}
1078
1097
  };
1079
1098
  if (preserveVMergeRestart) attrs._preserveVMergeRestart = true;
1080
- if (vMergeContinuationCells && vMergeContinuationCells.length > 0) attrs._docxVMergeContinuationCells = vMergeContinuationCells;
1099
+ if (vMergeContinuationCells && vMergeContinuationCells.length > 0) attrs._docxVMergeContinuationCells = transportTableCellsWithParagraphPropertySources(vMergeContinuationCells);
1081
1100
  const contentNodes = [];
1082
1101
  for (const content of cell.content) if (content.type === "paragraph") contentNodes.push(...convertParagraphWithTextBoxes(content, styleResolver, {
1083
1102
  textBoxGroupId: context.nextTextBoxGroupId(),
@@ -1255,12 +1274,74 @@ const PAGE_BREAK_PARAGRAPH_OWNERS = {
1255
1274
  };
1256
1275
  function assertSourceContainerHasNoPageBreakRun(content, owner, sourceDescendants) {
1257
1276
  if (!sourceDescendants.containsPageBreakRun(content)) return;
1277
+ if (owner === "table-cell" && hasSingleLeadingTableCellPageBreak(content)) return;
1258
1278
  throw new UnsupportedDocxToProseMirrorConversionError({
1259
1279
  message: `${PAGE_BREAK_CONTAINER_DESCRIPTIONS[owner]} cannot be represented in the editor model`,
1260
1280
  owner,
1261
1281
  contentType: "break"
1262
1282
  });
1263
1283
  }
1284
+ /** A leading page-break run in a cell advances the whole row during pagination. This
1285
+ * narrow shape has a lossless PM representation and a row-wide layout
1286
+ * projection; interior breaks still require table-fragment ownership. */
1287
+ function hasSingleLeadingTableCellPageBreak(content) {
1288
+ const paragraph = content.length === 1 && content[0]?.type === "paragraph" ? content[0] : void 0;
1289
+ return paragraph !== void 0 && hasSingleLeadingParagraphPageBreak(paragraph);
1290
+ }
1291
+ const scanLeadingPageBreakRun = (run, scan) => {
1292
+ for (const content of run.content) {
1293
+ if (content.type === "renderedPageBreak") continue;
1294
+ if (content.type === "break" && content.breakType === "page") {
1295
+ scan.pageBreaks += 1;
1296
+ continue;
1297
+ }
1298
+ if (scan.pageBreaks === 0) scan.contentBeforeBreak = true;
1299
+ }
1300
+ };
1301
+ const scanLeadingPageBreakContent = (content, scan) => {
1302
+ switch (content.type) {
1303
+ case "run":
1304
+ scanLeadingPageBreakRun(content, scan);
1305
+ return;
1306
+ case "hyperlink":
1307
+ for (const child of content.children) scanLeadingPageBreakContent(child, scan);
1308
+ return;
1309
+ case "insertion":
1310
+ case "deletion":
1311
+ case "moveFrom":
1312
+ case "moveTo":
1313
+ case "inlineSdt":
1314
+ for (const child of content.content) scanLeadingPageBreakContent(child, scan);
1315
+ return;
1316
+ case "simpleField":
1317
+ for (const child of content.content) scanLeadingPageBreakContent(child, scan);
1318
+ return;
1319
+ case "complexField":
1320
+ for (const child of content.fieldResult) scanLeadingPageBreakRun(child, scan);
1321
+ return;
1322
+ case "bookmarkStart":
1323
+ case "bookmarkEnd":
1324
+ case "commentRangeStart":
1325
+ case "commentRangeEnd":
1326
+ case "moveFromRangeStart":
1327
+ case "moveFromRangeEnd":
1328
+ case "moveToRangeStart":
1329
+ case "moveToRangeEnd": return;
1330
+ case "commentReference":
1331
+ case "mathEquation":
1332
+ if (scan.pageBreaks === 0) scan.contentBeforeBreak = true;
1333
+ return;
1334
+ default: return content;
1335
+ }
1336
+ };
1337
+ const hasSingleLeadingParagraphPageBreak = (paragraph) => {
1338
+ const scan = {
1339
+ contentBeforeBreak: false,
1340
+ pageBreaks: 0
1341
+ };
1342
+ for (const content of paragraph.content) scanLeadingPageBreakContent(content, scan);
1343
+ return !scan.contentBeforeBreak && scan.pageBreaks === 1;
1344
+ };
1264
1345
  function assertParagraphPageBreakCanBeProjected({ paragraph, attrs, effectiveFrame, sourceDescendants }) {
1265
1346
  const sourceFeatures = sourceDescendants.paragraphFeatures(paragraph);
1266
1347
  if (!sourceFeatures.hasPageBreakRun) return;
@@ -1270,6 +1351,7 @@ function assertParagraphPageBreakCanBeProjected({ paragraph, attrs, effectiveFra
1270
1351
  hasTextBoxAnchor: sourceFeatures.hasTextBoxShape && !sourceFeatures.pageBreakSharesTextBoxShape
1271
1352
  });
1272
1353
  if (disposition.status === "supported") return;
1354
+ if (disposition.reason !== "textBoxAnchor" && hasSingleLeadingParagraphPageBreak(paragraph)) return;
1273
1355
  throw new UnsupportedDocxToProseMirrorConversionError({
1274
1356
  message: disposition.message,
1275
1357
  owner: PAGE_BREAK_PARAGRAPH_OWNERS[disposition.reason],
@@ -2065,6 +2147,7 @@ function textBoxFromShape(shape, textBody) {
2065
2147
  if (shape.wrap) textBox.wrap = shape.wrap;
2066
2148
  if (shape.fill) textBox.fill = shape.fill;
2067
2149
  if (shape.outline) textBox.outline = shape.outline;
2150
+ if (shape.transform) textBox.transform = shape.transform;
2068
2151
  if (textBody.margins) textBox.margins = textBody.margins;
2069
2152
  if (textBody.autoFit) textBox.autoFit = textBody.autoFit;
2070
2153
  if (textBody.textWrap) textBox.textWrap = textBody.textWrap;
@@ -2089,6 +2172,14 @@ function convertTextBox(textBox, styleResolver, options) {
2089
2172
  if (textBox.outline.color?.rgb) outlineColor = `#${textBox.outline.color.rgb}`;
2090
2173
  outlineStyle = textBox.outline.style || "solid";
2091
2174
  }
2175
+ let transform;
2176
+ if (textBox.transform) {
2177
+ const transforms = [];
2178
+ if (textBox.transform.rotation) transforms.push(`rotate(${textBox.transform.rotation}deg)`);
2179
+ if (textBox.transform.flipH) transforms.push("scaleX(-1)");
2180
+ if (textBox.transform.flipV) transforms.push("scaleY(-1)");
2181
+ if (transforms.length > 0) transform = transforms.join(" ");
2182
+ }
2092
2183
  const marginTop = textBox.margins?.top !== void 0 ? emuToPixels(textBox.margins.top) : 4;
2093
2184
  const marginBottom = textBox.margins?.bottom !== void 0 ? emuToPixels(textBox.margins.bottom) : 4;
2094
2185
  const marginLeft = textBox.margins?.left !== void 0 ? emuToPixels(textBox.margins.left) : 7;
@@ -2151,6 +2242,7 @@ function convertTextBox(textBox, styleResolver, options) {
2151
2242
  outlineWidth,
2152
2243
  outlineColor,
2153
2244
  outlineStyle,
2245
+ transform,
2154
2246
  marginTop,
2155
2247
  marginBottom,
2156
2248
  marginLeft,
@@ -2167,6 +2259,7 @@ function convertTextBox(textBox, styleResolver, options) {
2167
2259
  _docxPlacement: options.placement,
2168
2260
  _docxGroupId: options.groupId,
2169
2261
  _docxAnchorId: options.anchorId,
2262
+ _docxTextBodyContentState: textBox.content.length === 0 ? { type: "source-empty" } : { type: "authored" },
2170
2263
  _docxTrackedChange: options.trackedChange,
2171
2264
  _docxInlineSdts: options.inlineSdts.length > 0 ? options.inlineSdts : void 0
2172
2265
  }, contentNodes);
@@ -1,3 +1,4 @@
1
+ import { PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR } from "../../../docx/paragraphPropertySource.js";
1
2
  import { createNodeExtension } from "../create.js";
2
3
  //#region src/prosemirror/extensions/core/DocExtension.ts
3
4
  /**
@@ -8,6 +9,7 @@ const DocExtension = createNodeExtension({
8
9
  schemaNodeName: "doc",
9
10
  nodeSpec: {
10
11
  attrs: {
12
+ [PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR]: { default: null },
11
13
  _finalSectionStart: { default: null },
12
14
  _adjustLineHeightInTable: { default: false }
13
15
  },
@@ -1,3 +1,4 @@
1
+ import { PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR } from "../../../docx/paragraphPropertySource.js";
1
2
  import { PARAGRAPH_ALIGNMENT_VALUES } from "../../../types/documentEnumValues.js";
2
3
  import { paragraphToStyle } from "../../../utils/formatToStyle.js";
3
4
  import { collectHeadings } from "../../../utils/headingCollector.js";
@@ -184,6 +185,7 @@ const paragraphNodeSpec = {
184
185
  group: "block",
185
186
  attrs: {
186
187
  paraId: { default: null },
188
+ [PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR]: { default: null },
187
189
  idStability: { default: void 0 },
188
190
  textId: { default: null },
189
191
  alignment: { default: null },
@@ -2,7 +2,11 @@ import { Extension } from "../types.js";
2
2
  import { EditorState, PluginKey, Transaction } from "prosemirror-state";
3
3
  import { Node } from "prosemirror-model";
4
4
  //#region src/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts
5
- declare const paraIdAllocatorKey: PluginKey<any>;
5
+ type ParagraphPropertySourceSeed = {
6
+ contract: string | null;
7
+ tokens: ReadonlySet<string>;
8
+ };
9
+ declare const paraIdAllocatorKey: PluginKey<ParagraphPropertySourceSeed>;
6
10
  /**
7
11
  * Transaction meta asking this plugin to derive the ids it mints from a
8
12
  * caller-supplied seed instead of `Math.random()`.