@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
@@ -6,8 +6,10 @@ import { mergeTextFormatting } from "../../utils/textFormattingMerge.js";
6
6
  import { emuToPixels } from "../../utils/units.js";
7
7
  import { setAutospacingBaseValue } from "../autospacingBase.js";
8
8
  import { buildRunFormattingOverrideAttrs } from "../extensions/marks/RunFormattingOverrideExtension.js";
9
+ import { stampNumberedRefFieldBaselines } from "../numberedRefFields.js";
9
10
  import { directionFromBidi } from "../paragraphDirection.js";
10
11
  import { schema } from "../schema/index.js";
12
+ import { cascadeStyleTextFormatting } from "../styles/styleToggleCascade.js";
11
13
  import { assertValidProseMirrorDocument } from "../validation.js";
12
14
  import { resolveEffectiveTableCellFormatting } from "./effectiveTableCellFormatting.js";
13
15
  import { marksToTextFormatting } from "./fromProseDoc.js";
@@ -35,6 +37,56 @@ const createHyperlinkInstanceIndexAllocator = () => {
35
37
  return () => index++;
36
38
  };
37
39
  /**
40
+ * Find bookmark pairs across every editable inline wrapper in a document story.
41
+ * Each endpoint is converted at its own structural position, so a range may
42
+ * start outside a hyperlink and end inside it (or the inverse).
43
+ */
44
+ const collectPairedBookmarkIds = (blocks) => {
45
+ const counts = /* @__PURE__ */ new Map();
46
+ let position = 0;
47
+ const countBoundary = (id, type) => {
48
+ const count = counts.get(id) ?? {
49
+ starts: 0,
50
+ ends: 0
51
+ };
52
+ if (type === "start") {
53
+ count.starts += 1;
54
+ count.firstStart ??= position;
55
+ } else {
56
+ count.ends += 1;
57
+ count.firstEnd ??= position;
58
+ }
59
+ position += 1;
60
+ counts.set(id, count);
61
+ };
62
+ const visitRun = (run) => {
63
+ for (const content of run.content) if (content.type === "shape" && content.shape.textBody) visitBlocks(content.shape.textBody.content);
64
+ };
65
+ const visitHyperlink = (hyperlink) => {
66
+ for (const child of hyperlink.children) if (child.type === "bookmarkStart") countBoundary(child.id, "start");
67
+ else if (child.type === "bookmarkEnd") countBoundary(child.id, "end");
68
+ else visitRun(child);
69
+ };
70
+ const visitParagraphContent = (content) => {
71
+ if (content.type === "bookmarkStart") countBoundary(content.id, "start");
72
+ else if (content.type === "bookmarkEnd") countBoundary(content.id, "end");
73
+ else if (content.type === "run") visitRun(content);
74
+ else if (content.type === "hyperlink") visitHyperlink(content);
75
+ else if (content.type === "simpleField") for (const child of content.content) if (child.type === "hyperlink") visitHyperlink(child);
76
+ else visitRun(child);
77
+ else if (content.type === "complexField") for (const run of [...content.fieldCode ?? [], ...content.fieldResult]) visitRun(run);
78
+ else if (content.type === "inlineSdt") for (const child of content.content) visitParagraphContent(child);
79
+ else if (content.type === "insertion" || content.type === "deletion" || content.type === "moveFrom" || content.type === "moveTo") for (const child of content.content) visitParagraphContent(child);
80
+ };
81
+ const visitBlocks = (nestedBlocks) => {
82
+ for (const block of nestedBlocks) if (block.type === "paragraph") for (const content of block.content) visitParagraphContent(content);
83
+ else if (block.type === "table") for (const row of block.rows) for (const cell of row.cells) visitBlocks(cell.content);
84
+ else visitBlocks(block.content);
85
+ };
86
+ visitBlocks(blocks);
87
+ return new Set([...counts].flatMap(([id, count]) => count.starts === 1 && count.ends === 1 && count.firstStart !== void 0 && count.firstEnd !== void 0 && count.firstStart < count.firstEnd ? [id] : []));
88
+ };
89
+ /**
38
90
  * Convert a Document to a ProseMirror document
39
91
  *
40
92
  * @param document - The Document to convert
@@ -49,7 +101,8 @@ function toProseDoc(document, options) {
49
101
  const conversionContext = {
50
102
  theme,
51
103
  nextTextBoxGroupId,
52
- nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator()
104
+ nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator(),
105
+ pairedBookmarkIds: collectPairedBookmarkIds(paragraphs)
53
106
  };
54
107
  const convertBodyBlocks = (blocks) => {
55
108
  const out = [];
@@ -84,7 +137,7 @@ function toProseDoc(document, options) {
84
137
  };
85
138
  nodes.push(...convertBodyBlocks(paragraphs));
86
139
  if (nodes.length === 0) nodes.push(schema.node("paragraph", {}, []));
87
- const pmDoc = schema.node("doc", null, nodes);
140
+ const pmDoc = stampNumberedRefFieldBaselines(schema.node("doc", null, nodes));
88
141
  assertValidProseMirrorDocument(pmDoc, "Document conversion produced an invalid ProseMirror document");
89
142
  return pmDoc;
90
143
  }
@@ -125,7 +178,7 @@ function convertBlockSdt(blockSdt, convertBlocks) {
125
178
  * Resolves style-based text formatting and passes it to runs so that
126
179
  * paragraph styles (like Heading1) apply their font size, color, etc.
127
180
  */
128
- function convertParagraph(paragraph, styleResolver, nextHyperlinkInstanceIndex, activeCommentIds, extraRunFormatting, tableParagraphOverlay, textBoxAnchors) {
181
+ function convertParagraph(paragraph, styleResolver, nextHyperlinkInstanceIndex, pairedBookmarkIds, activeCommentIds, extraRunFormatting, tableParagraphOverlay, textBoxAnchors) {
129
182
  const attrs = paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOverlay);
130
183
  const isTocParagraph = attrs._tableOfContentsLevel !== void 0;
131
184
  const inlineNodes = [];
@@ -144,62 +197,120 @@ function convertParagraph(paragraph, styleResolver, nextHyperlinkInstanceIndex,
144
197
  emitInlineNodes([node]);
145
198
  };
146
199
  let styleRunFormatting;
200
+ let paragraphStyleRunFormatting;
147
201
  let paragraphStyleFontFamily;
148
202
  if (styleResolver) {
149
203
  styleRunFormatting = styleResolver.resolveParagraphStyle(paragraph.formatting?.styleId).runFormatting;
204
+ const paragraphStyle = paragraph.formatting?.styleId ? styleResolver.getStyle(paragraph.formatting.styleId) ?? styleResolver.getDefaultParagraphStyle() : styleResolver.getDefaultParagraphStyle();
205
+ paragraphStyleRunFormatting = paragraphStyle?.type === "paragraph" ? paragraphStyle.rPr : void 0;
150
206
  paragraphStyleFontFamily = resolveParagraphStyleFontFamily(paragraph.formatting?.styleId, styleResolver);
151
207
  }
152
208
  const paragraphRunFormatting = resolveRunFormattingWithoutDefaults(paragraph.formatting?.runProperties, styleResolver);
153
209
  let inheritableParagraphRunFormatting;
154
210
  if (paragraphRunFormatting && !isTocParagraph) inheritableParagraphRunFormatting = stripParagraphMarkFormattingForBodyRuns(paragraphRunFormatting);
155
- let baseRunFormatting = mergeTextFormatting(styleRunFormatting, extraRunFormatting);
211
+ const orderedToggleFormatting = cascadeStyleTextFormatting([
212
+ {
213
+ formatting: styleResolver?.getDocDefaults()?.rPr,
214
+ type: "defaults"
215
+ },
216
+ {
217
+ formatting: extraRunFormatting,
218
+ type: "style"
219
+ },
220
+ {
221
+ formatting: paragraphStyleRunFormatting,
222
+ type: "style"
223
+ }
224
+ ], { ordinaryFormatting: mergeTextFormatting(styleRunFormatting, extraRunFormatting) });
225
+ let baseRunFormatting = orderedToggleFormatting.formatting;
156
226
  if (paragraphStyleFontFamily) baseRunFormatting = mergeTextFormatting(baseRunFormatting, { fontFamily: paragraphStyleFontFamily });
157
227
  const paragraphMarkPrecedesStyle = paragraph.formatting?.styleId !== void 0;
158
- const defaultRunFormatting = paragraphMarkPrecedesStyle ? mergeTextFormatting(inheritableParagraphRunFormatting, baseRunFormatting) : mergeTextFormatting(baseRunFormatting, inheritableParagraphRunFormatting);
228
+ const ordinaryDefaultRunFormatting = paragraphMarkPrecedesStyle ? mergeTextFormatting(inheritableParagraphRunFormatting, baseRunFormatting) : mergeTextFormatting(baseRunFormatting, inheritableParagraphRunFormatting);
229
+ const defaultToggleCascade = paragraphMarkPrecedesStyle ? cascadeStyleTextFormatting([{
230
+ cascade: orderedToggleFormatting,
231
+ type: "carried"
232
+ }], { ordinaryFormatting: ordinaryDefaultRunFormatting }) : cascadeStyleTextFormatting([{
233
+ cascade: orderedToggleFormatting,
234
+ type: "carried"
235
+ }, {
236
+ formatting: inheritableParagraphRunFormatting,
237
+ type: "direct"
238
+ }], { ordinaryFormatting: ordinaryDefaultRunFormatting });
239
+ const defaultRunFormatting = defaultToggleCascade.formatting;
159
240
  const getInheritedRunFormatting = (formatting, fieldType) => {
160
- if (fieldType === "TOC") return hasDirectRunFormatting(formatting) ? suppressParagraphMarkFormatting(baseRunFormatting, void 0, formatting) : baseRunFormatting;
161
- if (!(hasDirectRunFormatting(formatting) || formatting?.styleId !== void 0)) return defaultRunFormatting;
162
- return suppressParagraphMarkFormatting(baseRunFormatting, inheritableParagraphRunFormatting, formatting, paragraphMarkPrecedesStyle);
241
+ if (fieldType === "TOC") return {
242
+ formatting: hasDirectRunFormatting(formatting) ? suppressParagraphMarkFormatting(baseRunFormatting, void 0, formatting) : baseRunFormatting,
243
+ toggleCascade: orderedToggleFormatting
244
+ };
245
+ if (!(hasDirectRunFormatting(formatting) || formatting?.styleId !== void 0)) return {
246
+ formatting: defaultRunFormatting,
247
+ toggleCascade: defaultToggleCascade
248
+ };
249
+ const suppressedFormatting = suppressParagraphMarkFormatting(baseRunFormatting, inheritableParagraphRunFormatting, formatting, paragraphMarkPrecedesStyle);
250
+ const paragraphMarkOverrides = getParagraphMarkSuppressionOverrides({
251
+ directFormatting: formatting,
252
+ paragraphMarkFormatting: inheritableParagraphRunFormatting,
253
+ suppressedFormatting
254
+ });
255
+ return {
256
+ formatting: suppressedFormatting,
257
+ ...paragraphMarkOverrides ? { paragraphMarkOverrides } : {},
258
+ toggleCascade: orderedToggleFormatting
259
+ };
163
260
  };
164
261
  const emitTrackedChange = (change, markType, moveKind) => {
165
262
  emitInlineNodes(convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, moveKind, textBoxAnchors));
166
263
  };
167
- for (const content of paragraph.content) {
168
- if (content.type === "commentRangeStart") commentIds.add(content.id);
169
- else if (content.type === "commentRangeEnd") commentIds.delete(content.id);
170
- else if (content.type === "commentReference") anchorPointComment(inlineNodes, content.id);
171
- else if (content.type === "run") emitInlineNodes(convertRun(content, getInheritedRunFormatting(content.formatting), styleResolver, textBoxAnchors));
172
- else if (content.type === "hyperlink") {
173
- const linkNodes = convertHyperlink(content, {
174
- getInheritedRunFormatting,
175
- styleResolver,
176
- hyperlinkIndex: nextHyperlinkInstanceIndex(),
177
- textBoxAnchors
178
- });
179
- if (linkNodes.length === 0) {
180
- emptyHyperlinks ??= [];
181
- emptyHyperlinks.push({
182
- offset: inlineOffset,
183
- ...content.href !== void 0 ? { href: content.href } : {},
184
- ...content.anchor !== void 0 ? { anchor: content.anchor } : {},
185
- ...content.tooltip !== void 0 ? { tooltip: content.tooltip } : {},
186
- ...content.rId !== void 0 ? { rId: content.rId } : {}
187
- });
188
- continue;
189
- }
190
- emitInlineNodes(linkNodes);
191
- } else if (content.type === "simpleField" || content.type === "complexField") emitInlineNode(convertField(content, getInheritedRunFormatting, styleResolver));
192
- else if (content.type === "inlineSdt") emitInlineNode(convertInlineSdt(content, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, textBoxAnchors));
193
- else if (content.type === "insertion" || content.type === "moveTo") emitTrackedChange(content, "insertion", content.type === "moveTo" ? "moveTo" : null);
194
- else if (content.type === "deletion" || content.type === "moveFrom") emitTrackedChange(content, "deletion", content.type === "moveFrom" ? "moveFrom" : null);
195
- else if (content.type === "mathEquation") emitInlineNode(convertMathEquation(content));
196
- if (content.type === "bookmarkStart") {
197
- if (!bookmarksArr) bookmarksArr = [];
198
- bookmarksArr.push({
199
- id: content.id,
200
- name: content.name
264
+ for (const content of paragraph.content) if (content.type === "commentRangeStart") commentIds.add(content.id);
265
+ else if (content.type === "commentRangeEnd") commentIds.delete(content.id);
266
+ else if (content.type === "commentReference") anchorPointComment(inlineNodes, content.id);
267
+ else if (content.type === "run") emitInlineNodes(convertRun(content, getInheritedRunFormatting(content.formatting), styleResolver, textBoxAnchors));
268
+ else if (content.type === "hyperlink") {
269
+ const linkNodes = convertHyperlink(content, {
270
+ getInheritedRunFormatting,
271
+ styleResolver,
272
+ hyperlinkIndex: nextHyperlinkInstanceIndex(),
273
+ textBoxAnchors
274
+ });
275
+ if (linkNodes.length === 0) {
276
+ emptyHyperlinks ??= [];
277
+ emptyHyperlinks.push({
278
+ offset: inlineOffset,
279
+ ...content.href !== void 0 ? { href: content.href } : {},
280
+ ...content.anchor !== void 0 ? { anchor: content.anchor } : {},
281
+ ...content.tooltip !== void 0 ? { tooltip: content.tooltip } : {},
282
+ ...content.rId !== void 0 ? { rId: content.rId } : {}
201
283
  });
284
+ continue;
202
285
  }
286
+ emitInlineNodes(linkNodes);
287
+ } else if (content.type === "simpleField" || content.type === "complexField") emitInlineNode(convertField(content, {
288
+ getInheritedRunFormatting,
289
+ styleResolver,
290
+ nextHyperlinkInstanceIndex,
291
+ textBoxAnchors
292
+ }));
293
+ else if (content.type === "inlineSdt") emitInlineNode(convertInlineSdt(content, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, textBoxAnchors));
294
+ else if (content.type === "insertion" || content.type === "moveTo") emitTrackedChange(content, "insertion", content.type === "moveTo" ? "moveTo" : null);
295
+ else if (content.type === "deletion" || content.type === "moveFrom") emitTrackedChange(content, "deletion", content.type === "moveFrom" ? "moveFrom" : null);
296
+ else if (content.type === "mathEquation") emitInlineNode(convertMathEquation(content));
297
+ else if (content.type === "bookmarkStart" && pairedBookmarkIds.has(content.id)) emitInlineNode(schema.node("bookmarkBoundary", {
298
+ type: "start",
299
+ id: content.id,
300
+ name: content.name,
301
+ colFirst: content.colFirst,
302
+ colLast: content.colLast
303
+ }));
304
+ else if (content.type === "bookmarkEnd" && pairedBookmarkIds.has(content.id)) emitInlineNode(schema.node("bookmarkBoundary", {
305
+ type: "end",
306
+ id: content.id
307
+ }));
308
+ else if (content.type === "bookmarkStart") {
309
+ if (!bookmarksArr) bookmarksArr = [];
310
+ bookmarksArr.push({
311
+ id: content.id,
312
+ name: content.name
313
+ });
203
314
  }
204
315
  if (bookmarksArr) attrs.bookmarks = bookmarksArr;
205
316
  if (emptyHyperlinks) attrs._emptyHyperlinks = emptyHyperlinks;
@@ -268,7 +379,7 @@ function convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, getI
268
379
  });
269
380
  }
270
381
  function canCarryTrackedRunMark(node, markType) {
271
- return node.isText || node.isInline && node.type.allowsMarkType(markType) && (node.type.name === "image" || node.type.name === "shape" || node.type.name === "hardBreak" || node.type.name === "tab" || node.type.name === "symbol" || node.type.name === "textBoxAnchor");
382
+ return node.isText || node.isInline && node.type.allowsMarkType(markType) && (node.type.name === "image" || node.type.name === "shape" || node.type.name === "hardBreak" || node.type.name === "tab" || node.type.name === "symbol" || node.type.name === "bookmarkBoundary" || node.type.name === "textBoxAnchor");
272
383
  }
273
384
  /**
274
385
  * Convert ParagraphFormatting to ProseMirror paragraph attrs
@@ -499,27 +610,37 @@ function stripParagraphMarkFormattingForBodyRuns(formatting) {
499
610
  const { fontFamily: _fontFamily, ...bodyRunFormatting } = paragraphMarkFormatting;
500
611
  return Object.keys(bodyRunFormatting).length > 0 ? bodyRunFormatting : void 0;
501
612
  }
613
+ const PARAGRAPH_MARK_BOOLEAN_KEYS = [
614
+ "bold",
615
+ "italic",
616
+ "strike",
617
+ "doubleStrike",
618
+ "allCaps",
619
+ "smallCaps",
620
+ "hidden",
621
+ "emboss",
622
+ "imprint",
623
+ "shadow",
624
+ "outline",
625
+ "rtl"
626
+ ];
502
627
  function suppressParagraphMarkFormatting(base, paragraphMark, direct, paragraphMarkPrecedesStyle = false) {
503
628
  if (!paragraphMark) return base;
504
629
  const result = (paragraphMarkPrecedesStyle ? mergeTextFormatting(paragraphMark, base) : mergeTextFormatting(base, paragraphMark)) ?? {};
505
- suppressBooleanParagraphMark(result, base, paragraphMark, direct, "bold");
506
- suppressBooleanParagraphMark(result, base, paragraphMark, direct, "italic");
507
- suppressBooleanParagraphMark(result, base, paragraphMark, direct, "strike");
508
- suppressBooleanParagraphMark(result, base, paragraphMark, direct, "doubleStrike");
509
- suppressBooleanParagraphMark(result, base, paragraphMark, direct, "allCaps");
510
- suppressBooleanParagraphMark(result, base, paragraphMark, direct, "smallCaps");
511
- suppressBooleanParagraphMark(result, base, paragraphMark, direct, "hidden");
512
- suppressBooleanParagraphMark(result, base, paragraphMark, direct, "emboss");
513
- suppressBooleanParagraphMark(result, base, paragraphMark, direct, "imprint");
514
- suppressBooleanParagraphMark(result, base, paragraphMark, direct, "shadow");
515
- suppressBooleanParagraphMark(result, base, paragraphMark, direct, "outline");
516
- suppressBooleanParagraphMark(result, base, paragraphMark, direct, "rtl");
630
+ for (const key of PARAGRAPH_MARK_BOOLEAN_KEYS) suppressBooleanParagraphMark(result, base, paragraphMark, direct, key);
517
631
  if (paragraphMark.fontSize !== void 0 && direct?.fontSize === void 0 && base?.fontSize !== void 0) result.fontSize = base.fontSize;
518
632
  if (paragraphMark.fontSizeCs !== void 0 && direct?.fontSizeCs === void 0 && base?.fontSizeCs !== void 0) result.fontSizeCs = base.fontSizeCs;
519
633
  if (paragraphMark.underline !== void 0 && direct?.underline === void 0) result.underline = { style: "none" };
520
634
  if (paragraphMark.spacing !== void 0 && direct?.spacing === void 0) result.spacing = 0;
521
635
  return Object.keys(result).length > 0 ? result : void 0;
522
636
  }
637
+ function getParagraphMarkSuppressionOverrides({ directFormatting, paragraphMarkFormatting, suppressedFormatting }) {
638
+ if (!paragraphMarkFormatting) return;
639
+ const overrides = {};
640
+ for (const key of PARAGRAPH_MARK_BOOLEAN_KEYS) if (paragraphMarkFormatting[key] !== void 0 && directFormatting?.[key] === void 0 && suppressedFormatting?.[key] === false) overrides[key] = false;
641
+ if (paragraphMarkFormatting.underline !== void 0 && directFormatting?.underline === void 0 && suppressedFormatting?.underline?.style === "none") overrides.underline = { style: "none" };
642
+ return Object.keys(overrides).length > 0 ? overrides : void 0;
643
+ }
523
644
  function suppressBooleanParagraphMark(result, base, paragraphMark, direct, key) {
524
645
  if (paragraphMark[key] === void 0 || direct?.[key] !== void 0) return;
525
646
  result[key] = base?.[key] ?? false;
@@ -536,15 +657,45 @@ function resolveTextFormatting(formatting, styleResolver) {
536
657
  */
537
658
  function resolveRunFormattingWithoutDefaults(formatting, styleResolver) {
538
659
  if (!formatting || !styleResolver) return formatting;
539
- return mergeTextFormatting(formatting.styleId ? styleResolver.getRunStyleOwnProperties(formatting.styleId) : void 0, formatting);
660
+ return cascadeStyleTextFormatting([{
661
+ formatting: formatting.styleId ? styleResolver.getRunStyleOwnProperties(formatting.styleId) : void 0,
662
+ type: "style"
663
+ }, {
664
+ formatting,
665
+ type: "direct"
666
+ }]).formatting;
540
667
  }
541
668
  function resolveParagraphDefaultTextFormatting(styleId, formatting, styleResolver, options = {}) {
542
669
  const style = styleId ? styleResolver.getStyle(styleId) ?? styleResolver.getDefaultParagraphStyle() : styleResolver.getDefaultParagraphStyle();
543
670
  const paragraphStyleRpr = style?.type === "paragraph" ? style.rPr : void 0;
544
671
  const rawParagraphMarkRpr = options.includeParagraphMarkRunProperties === false ? void 0 : formatting?.runProperties;
545
672
  const paragraphRunProperties = rawParagraphMarkRpr ? stripParagraphMarkOnlyFormatting(resolveRunFormattingWithoutDefaults(rawParagraphMarkRpr, styleResolver) ?? {}) : void 0;
546
- const bodyRunDefaults = mergeTextFormatting(mergeTextFormatting(styleResolver.getDocDefaults()?.rPr, styleResolver.getDefaultCharacterStyle()?.rPr), paragraphStyleRpr);
547
- return styleId === void 0 ? mergeTextFormatting(bodyRunDefaults, paragraphRunProperties) : mergeTextFormatting(paragraphRunProperties, bodyRunDefaults);
673
+ const orderedBodyToggleFormatting = cascadeStyleTextFormatting([
674
+ {
675
+ formatting: styleResolver.getDocDefaults()?.rPr,
676
+ type: "defaults"
677
+ },
678
+ {
679
+ formatting: paragraphStyleRpr,
680
+ type: "style"
681
+ },
682
+ {
683
+ formatting: styleResolver.getDefaultCharacterStyle()?.rPr,
684
+ type: "style"
685
+ }
686
+ ], { ordinaryFormatting: mergeTextFormatting(mergeTextFormatting(styleResolver.getDocDefaults()?.rPr, styleResolver.getDefaultCharacterStyle()?.rPr), paragraphStyleRpr) });
687
+ const bodyRunDefaults = orderedBodyToggleFormatting.formatting;
688
+ if (styleId !== void 0) return cascadeStyleTextFormatting([{
689
+ cascade: orderedBodyToggleFormatting,
690
+ type: "carried"
691
+ }], { ordinaryFormatting: mergeTextFormatting(paragraphRunProperties, bodyRunDefaults) }).formatting;
692
+ return cascadeStyleTextFormatting([{
693
+ cascade: orderedBodyToggleFormatting,
694
+ type: "carried"
695
+ }, {
696
+ formatting: paragraphRunProperties,
697
+ type: "direct"
698
+ }], { ordinaryFormatting: mergeTextFormatting(bodyRunDefaults, paragraphRunProperties) }).formatting;
548
699
  }
549
700
  function calculateRowSpans(table) {
550
701
  const result = /* @__PURE__ */ new Map();
@@ -926,7 +1077,8 @@ function standaloneTableCellToProseMirror(cell, nodeType) {
926
1077
  context: {
927
1078
  theme: null,
928
1079
  nextTextBoxGroupId: createTextBoxGroupIdFactory(),
929
- nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator()
1080
+ nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator(),
1081
+ pairedBookmarkIds: collectPairedBookmarkIds(cell.content)
930
1082
  },
931
1083
  isHeader: nodeType === "tableHeader",
932
1084
  gridWidthPercent: void 0,
@@ -939,31 +1091,47 @@ function standaloneTableCellToProseMirror(cell, nodeType) {
939
1091
  defaultCellMargins: void 0
940
1092
  });
941
1093
  }
942
- /**
943
- * Convert a SimpleField or ComplexField to a ProseMirror field node.
944
- * Preserves run formatting (bold, fontSize, color, etc.) as PM marks.
945
- * Accepts a run formatting resolver so fields inherit paragraph-level
946
- * formatting the same way regular text runs do.
947
- */
948
- function convertField(field, getInheritedRunFormatting, styleResolver) {
1094
+ function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyperlinkInstanceIndex, textBoxAnchors }) {
949
1095
  let displayText = "";
950
1096
  let fieldFormatting;
951
- const runs = field.type === "simpleField" ? field.content : field.fieldResult;
952
- for (const r of runs) if (r.type === "run") {
953
- for (const c of r.content) if (c.type === "text") displayText += c.text;
954
- if (!fieldFormatting && r.formatting) fieldFormatting = r.formatting;
1097
+ const inlineNodes = [];
1098
+ const hasStructuredSourceContent = field.type === "simpleField" && field.content.some((content) => content.type === "hyperlink");
1099
+ const appendRun = (run) => {
1100
+ for (const content of run.content) if (content.type === "text") displayText += content.text;
1101
+ fieldFormatting ??= run.formatting;
1102
+ if (!hasStructuredSourceContent) return;
1103
+ inlineNodes.push(...convertRun(run, getInheritedRunFormatting(run.formatting, field.fieldType), styleResolver, textBoxAnchors));
1104
+ };
1105
+ if (field.type === "simpleField") for (const content of field.content) {
1106
+ if (content.type === "run") {
1107
+ appendRun(content);
1108
+ continue;
1109
+ }
1110
+ for (const child of content.children) if (child.type === "run") {
1111
+ for (const runContent of child.content) if (runContent.type === "text") displayText += runContent.text;
1112
+ fieldFormatting ??= child.formatting;
1113
+ }
1114
+ inlineNodes.push(...convertHyperlink(content, {
1115
+ getInheritedRunFormatting: (formatting) => getInheritedRunFormatting(formatting, field.fieldType),
1116
+ styleResolver,
1117
+ hyperlinkIndex: nextHyperlinkInstanceIndex(),
1118
+ textBoxAnchors
1119
+ }));
955
1120
  }
1121
+ else for (const run of field.fieldResult) appendRun(run);
956
1122
  if (!fieldFormatting && field.type === "complexField" && field.fieldResult.length === 0) fieldFormatting = field.formatting;
957
1123
  const inheritedFormatting = getInheritedRunFormatting(fieldFormatting, field.fieldType);
958
1124
  const { marks } = buildRunMarks(fieldFormatting, inheritedFormatting, styleResolver);
959
- return schema.node("field", {
1125
+ const hasConvertedHyperlinkContent = inlineNodes.some((node) => node.marks.some((mark) => mark.type.name === "hyperlink"));
1126
+ const createStructuredField = hasStructuredSourceContent && hasConvertedHyperlinkContent;
1127
+ return schema.node(createStructuredField ? "structuredField" : "field", {
960
1128
  fieldType: field.fieldType,
961
1129
  instruction: field.instruction,
962
1130
  displayText,
963
1131
  fieldKind: field.type === "simpleField" ? "simple" : "complex",
964
1132
  fldLock: field.fldLock ?? false,
965
1133
  dirty: field.dirty ?? false
966
- }, void 0, marks);
1134
+ }, createStructuredField ? inlineNodes : void 0, marks);
967
1135
  }
968
1136
  /**
969
1137
  * Convert a MathEquation to a ProseMirror math node.
@@ -993,7 +1161,12 @@ function convertInlineSdt(sdt, nextHyperlinkInstanceIndex, getInheritedRunFormat
993
1161
  });
994
1162
  inlineNodes.push(...linkNodes);
995
1163
  } else if (content.type === "simpleField" || content.type === "complexField") {
996
- const fieldNode = convertField(content, getInheritedRunFormatting, styleResolver);
1164
+ const fieldNode = convertField(content, {
1165
+ getInheritedRunFormatting,
1166
+ styleResolver,
1167
+ nextHyperlinkInstanceIndex,
1168
+ textBoxAnchors
1169
+ });
997
1170
  if (fieldNode) inlineNodes.push(fieldNode);
998
1171
  } else if (content.type === "inlineSdt") {
999
1172
  const nestedSdt = convertInlineSdt(content, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, textBoxAnchors);
@@ -1012,11 +1185,11 @@ function convertInlineSdt(sdt, nextHyperlinkInstanceIndex, getInheritedRunFormat
1012
1185
  * Convert a Run to ProseMirror text nodes with marks
1013
1186
  *
1014
1187
  * @param run - The run to convert
1015
- * @param styleFormatting - Text formatting from the paragraph's style (e.g., Heading1's font size/color)
1188
+ * @param resolvedStyleFormatting - Paragraph formatting and its explicit style-toggle state
1016
1189
  */
1017
- function convertRun(run, styleFormatting, styleResolver, textBoxAnchors) {
1190
+ function convertRun(run, resolvedStyleFormatting, styleResolver, textBoxAnchors) {
1018
1191
  const nodes = [];
1019
- const { marks, mergedFormatting } = buildRunMarks(run.formatting, styleFormatting, styleResolver);
1192
+ const { marks, mergedFormatting } = buildRunMarks(run.formatting, resolvedStyleFormatting, styleResolver);
1020
1193
  if (run.propertyChanges && run.propertyChanges.length > 0) marks.push(schema.mark("runPropertyChange", { changes: [...run.propertyChanges] }));
1021
1194
  for (const content of run.content) {
1022
1195
  const contentNodes = convertRunContent(content, marks, mergedFormatting, textBoxAnchors);
@@ -1024,13 +1197,34 @@ function convertRun(run, styleFormatting, styleResolver, textBoxAnchors) {
1024
1197
  }
1025
1198
  return nodes;
1026
1199
  }
1027
- function buildRunMarks(runFormatting, inheritedFormatting, styleResolver) {
1200
+ function buildRunMarks(runFormatting, inherited, styleResolver) {
1028
1201
  const styleId = runFormatting?.styleId;
1029
- const runStyleFormatting = styleId ? styleResolver?.getRunStyleOwnProperties(styleId) : void 0;
1030
- const mergedFormatting = mergeTextFormatting(mergeTextFormatting(inheritedFormatting, runStyleFormatting), runFormatting);
1031
- const marks = textFormattingToMarks(mergedFormatting);
1202
+ const characterStyleFormatting = styleId ? styleResolver?.getRunStyleOwnProperties(styleId) : styleResolver?.getDefaultCharacterStyle()?.rPr;
1203
+ let ordinaryRunStyleFormatting = inherited.formatting ? { ...inherited.formatting } : {};
1204
+ if (styleId) ordinaryRunStyleFormatting = mergeTextFormatting(inherited.formatting, characterStyleFormatting) ?? {};
1205
+ const cascadedStyleFormatting = cascadeStyleTextFormatting([{
1206
+ cascade: inherited.toggleCascade,
1207
+ type: "carried"
1208
+ }, {
1209
+ formatting: characterStyleFormatting,
1210
+ type: "style"
1211
+ }], { ordinaryFormatting: ordinaryRunStyleFormatting });
1212
+ const runStyleFormatting = cascadedStyleFormatting.formatting;
1213
+ const mergedFormatting = cascadeStyleTextFormatting([{
1214
+ cascade: cascadedStyleFormatting,
1215
+ type: "carried"
1216
+ }, {
1217
+ formatting: runFormatting,
1218
+ type: "direct"
1219
+ }], { ordinaryFormatting: mergeTextFormatting(runStyleFormatting, runFormatting) }).formatting;
1220
+ const marks = textFormattingToMarks(mergedFormatting, { overrideFormatting: getRunFormattingOverrides({
1221
+ directFormatting: runFormatting,
1222
+ effectiveStyleFormatting: runStyleFormatting,
1223
+ hasCharacterStyle: styleId !== void 0,
1224
+ paragraphMarkOverrides: inherited.paragraphMarkOverrides
1225
+ }) });
1032
1226
  if (styleId) {
1033
- const styleRPr = runStyleFormatting ? marksToTextFormatting(textFormattingToMarks(runStyleFormatting)) : void 0;
1227
+ const styleRPr = characterStyleFormatting ? marksToTextFormatting(textFormattingToMarks(characterStyleFormatting)) : void 0;
1034
1228
  marks.push(schema.mark("characterStyle", {
1035
1229
  styleId,
1036
1230
  _styleRPr: styleRPr && Object.keys(styleRPr).length > 0 ? styleRPr : null
@@ -1041,6 +1235,26 @@ function buildRunMarks(runFormatting, inheritedFormatting, styleResolver) {
1041
1235
  mergedFormatting
1042
1236
  };
1043
1237
  }
1238
+ const ORDINARY_STYLE_TOGGLE_KEYS = [
1239
+ "bold",
1240
+ "italic",
1241
+ "strike",
1242
+ "allCaps",
1243
+ "smallCaps",
1244
+ "hidden",
1245
+ "emboss",
1246
+ "imprint",
1247
+ "shadow",
1248
+ "outline"
1249
+ ];
1250
+ function getRunFormattingOverrides({ directFormatting, effectiveStyleFormatting, hasCharacterStyle, paragraphMarkOverrides }) {
1251
+ const overrides = mergeTextFormatting(paragraphMarkOverrides, directFormatting);
1252
+ if (!overrides || !directFormatting) return overrides;
1253
+ for (const key of ORDINARY_STYLE_TOGGLE_KEYS) if (directFormatting[key] === true && (!hasCharacterStyle || effectiveStyleFormatting?.[key] !== true)) Reflect.deleteProperty(overrides, key);
1254
+ if (directFormatting.boldCs === true && directFormatting.boldCs === directFormatting.bold && (!hasCharacterStyle || effectiveStyleFormatting?.boldCs === void 0)) Reflect.deleteProperty(overrides, "boldCs");
1255
+ if (directFormatting.italicCs === true && directFormatting.italicCs === directFormatting.italic && (!hasCharacterStyle || effectiveStyleFormatting?.italicCs === void 0)) Reflect.deleteProperty(overrides, "italicCs");
1256
+ return overrides;
1257
+ }
1044
1258
  /**
1045
1259
  * Convert RunContent to ProseMirror nodes
1046
1260
  */
@@ -1212,21 +1426,37 @@ function convertHyperlink(hyperlink, { getInheritedRunFormatting, styleResolver,
1212
1426
  rId: hyperlink.rId,
1213
1427
  _docxHyperlinkIndex: hyperlinkIndex
1214
1428
  });
1215
- for (const child of hyperlink.children) if (child.type === "run") {
1216
- const inheritedFormatting = getInheritedRunFormatting(child.formatting);
1217
- const { marks: runMarks, mergedFormatting } = buildRunMarks(child.formatting, inheritedFormatting, styleResolver);
1218
- const allMarks = [...runMarks, linkMark];
1219
- for (const content of child.content) nodes.push(...convertRunContent(content, allMarks, mergedFormatting, textBoxAnchors));
1429
+ for (const child of hyperlink.children) {
1430
+ if (child.type === "bookmarkStart") {
1431
+ nodes.push(schema.node("bookmarkBoundary", {
1432
+ type: "start",
1433
+ id: child.id,
1434
+ name: child.name,
1435
+ colFirst: child.colFirst,
1436
+ colLast: child.colLast
1437
+ }, void 0, [linkMark]));
1438
+ continue;
1439
+ }
1440
+ if (child.type === "bookmarkEnd") {
1441
+ nodes.push(schema.node("bookmarkBoundary", {
1442
+ type: "end",
1443
+ id: child.id
1444
+ }, void 0, [linkMark]));
1445
+ continue;
1446
+ }
1447
+ if (child.type === "run") {
1448
+ const inheritedFormatting = getInheritedRunFormatting(child.formatting);
1449
+ const { marks: runMarks, mergedFormatting } = buildRunMarks(child.formatting, inheritedFormatting, styleResolver);
1450
+ const allMarks = [...runMarks, linkMark];
1451
+ for (const content of child.content) nodes.push(...convertRunContent(content, allMarks, mergedFormatting, textBoxAnchors));
1452
+ }
1220
1453
  }
1221
1454
  return nodes;
1222
1455
  }
1223
- /**
1224
- * Convert TextFormatting to ProseMirror marks
1225
- */
1226
- function textFormattingToMarks(formatting) {
1456
+ function textFormattingToMarks(formatting, options) {
1227
1457
  if (!formatting) return [];
1228
1458
  const marks = [];
1229
- const overrideAttrs = buildRunFormattingOverrideAttrs(formatting);
1459
+ const overrideAttrs = buildRunFormattingOverrideAttrs(options ? options.overrideFormatting : formatting);
1230
1460
  if (overrideAttrs) marks.push(schema.mark("runFormattingOverride", overrideAttrs));
1231
1461
  if (formatting.bold) marks.push(schema.mark("bold"));
1232
1462
  if (formatting.italic) marks.push(schema.mark("italic"));
@@ -1388,7 +1618,7 @@ function convertShape(shape) {
1388
1618
  }
1389
1619
  function convertParagraphWithTextBoxes(block, styleResolver, { textBoxGroupId, context, extraRunFormatting, tableParagraphOverlay }) {
1390
1620
  const { textBoxes, textBoxAnchors } = extractTextBoxesFromParagraph(block, textBoxGroupId);
1391
- const pmParagraph = convertParagraph(block, styleResolver, context.nextHyperlinkInstanceIndex, void 0, extraRunFormatting, tableParagraphOverlay, textBoxAnchors);
1621
+ const pmParagraph = convertParagraph(block, styleResolver, context.nextHyperlinkInstanceIndex, context.pairedBookmarkIds, void 0, extraRunFormatting, tableParagraphOverlay, textBoxAnchors);
1392
1622
  const nodes = [];
1393
1623
  const isEmptyAfterExtraction = textBoxes.length > 0 && !hasContentBesidesTextBoxAnchors(pmParagraph);
1394
1624
  const keepWrapperParagraph = isEmptyAfterExtraction && hasParagraphBoundaryPayload(block, pmParagraph);
@@ -1650,7 +1880,8 @@ function headerFooterToProseDoc(content, options) {
1650
1880
  const conversionContext = {
1651
1881
  theme,
1652
1882
  nextTextBoxGroupId,
1653
- nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator()
1883
+ nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator(),
1884
+ pairedBookmarkIds: collectPairedBookmarkIds(content)
1654
1885
  };
1655
1886
  const convertBlocks = (blocks) => {
1656
1887
  const out = [];
@@ -1664,7 +1895,7 @@ function headerFooterToProseDoc(content, options) {
1664
1895
  };
1665
1896
  nodes.push(...convertBlocks(content));
1666
1897
  if (nodes.length === 0) nodes.push(schema.node("paragraph", {}, []));
1667
- const pmDoc = schema.node("doc", null, nodes);
1898
+ const pmDoc = stampNumberedRefFieldBaselines(schema.node("doc", null, nodes));
1668
1899
  assertValidProseMirrorDocument(pmDoc, "Header/footer conversion produced an invalid ProseMirror document");
1669
1900
  return pmDoc;
1670
1901
  }
@@ -43,7 +43,8 @@ import { EmbossExtension, EmphasisMarkExtension, ImprintExtension, TextOutlineEx
43
43
  import { DeletionExtension, InsertionExtension, RunPropertyChangeExtension } from "./marks/TrackedChangeExtensions.js";
44
44
  import { UnderlineExtension } from "./marks/UnderlineExtension.js";
45
45
  import { BlockSdtExtension } from "./nodes/BlockSdtExtension.js";
46
- import { FieldExtension } from "./nodes/FieldExtension.js";
46
+ import { BookmarkBoundaryExtension } from "./nodes/BookmarkBoundaryExtension.js";
47
+ import { FieldExtension, StructuredFieldExtension } from "./nodes/FieldExtension.js";
47
48
  import { HardBreakExtension } from "./nodes/HardBreakExtension.js";
48
49
  import { HorizontalRuleExtension } from "./nodes/HorizontalRuleExtension.js";
49
50
  import { ImageExtension } from "./nodes/ImageExtension.js";
@@ -64,6 +65,11 @@ import { TextBoxExtension } from "./nodes/TextBoxExtension.js";
64
65
  function createStarterKit(options = {}) {
65
66
  const disabled = options.disable ? new Set(options.disable) : /* @__PURE__ */ new Set();
66
67
  const extensions = [];
68
+ let internalClipboardToken;
69
+ const getInternalClipboardToken = () => {
70
+ internalClipboardToken ??= globalThis.crypto.randomUUID();
71
+ return internalClipboardToken;
72
+ };
67
73
  function add(name, ext) {
68
74
  if (!disabled.has(name)) extensions.push(ext);
69
75
  }
@@ -105,12 +111,13 @@ function createStarterKit(options = {}) {
105
111
  add("insertion", InsertionExtension());
106
112
  add("deletion", DeletionExtension());
107
113
  add("runPropertyChange", RunPropertyChangeExtension());
114
+ add("bookmarkBoundary", BookmarkBoundaryExtension({ getInternalClipboardToken }));
108
115
  add("hardBreak", HardBreakExtension());
109
116
  add("tab", TabExtension());
110
117
  add("symbol", SymbolExtension());
111
118
  add("image", ImageExtension());
112
119
  add("textBox", TextBoxExtension());
113
- add("textBoxAnchor", TextBoxAnchorExtension());
120
+ add("textBoxAnchor", TextBoxAnchorExtension({ getInternalClipboardToken }));
114
121
  add("shape", ShapeExtension());
115
122
  add("imageDrag", ImageDragExtension());
116
123
  add("imagePaste", ImagePasteExtension());
@@ -120,11 +127,12 @@ function createStarterKit(options = {}) {
120
127
  add("pageBreak", PageBreakExtension());
121
128
  add("renderedPageBreak", RenderedPageBreakExtension());
122
129
  add("field", FieldExtension());
130
+ add("field", StructuredFieldExtension({ getInternalClipboardToken }));
123
131
  add("sdt", SdtExtension());
124
132
  add("blockSdt", BlockSdtExtension());
125
133
  add("math", MathExtension());
126
134
  if (!disabled.has("table")) extensions.push(...createTableExtensions());
127
- add("pasteCleanup", PasteCleanupExtension());
135
+ add("pasteCleanup", PasteCleanupExtension({ getInternalClipboardToken }));
128
136
  add("pasteStyleInliner", PasteStyleInlinerExtension());
129
137
  add("list", ListExtension());
130
138
  add("baseKeymap", BaseKeymapExtension());