@stll/folio-core 0.36.0 → 0.37.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 (200) hide show
  1. package/README.md +59 -1
  2. package/dist/ai-edits/apply.js +545 -296
  3. package/dist/ai-edits/block-identity.d.ts +4 -1
  4. package/dist/ai-edits/block-identity.js +4 -1
  5. package/dist/ai-edits/blockRange.js +14 -14
  6. package/dist/ai-edits/clean-text.d.ts +40 -1
  7. package/dist/ai-edits/clean-text.js +52 -2
  8. package/dist/ai-edits/headless.d.ts +3 -1
  9. package/dist/ai-edits/headless.js +133 -71
  10. package/dist/ai-edits/index.d.ts +3 -3
  11. package/dist/ai-edits/index.js +2 -2
  12. package/dist/ai-edits/read.js +38 -20
  13. package/dist/ai-edits/snapshot.d.ts +14 -6
  14. package/dist/ai-edits/snapshot.js +99 -19
  15. package/dist/ai-edits/table-cell-mutations.js +1 -1
  16. package/dist/ai-edits/table-row-column-mutations.js +7 -0
  17. package/dist/ai-edits/table-template.d.ts +11 -1
  18. package/dist/ai-edits/table-template.js +49 -17
  19. package/dist/ai-edits/types.d.ts +45 -57
  20. package/dist/ai-suggestions/conflict.js +1 -4
  21. package/dist/ai-suggestions/text-positions.d.ts +8 -0
  22. package/dist/ai-suggestions/text-positions.js +27 -5
  23. package/dist/compare/__fixtures__/body-sequence.js +3 -4
  24. package/dist/compare/column-alignment.d.ts +13 -13
  25. package/dist/compare/column-alignment.js +78 -39
  26. package/dist/compare/compare.d.ts +4 -3
  27. package/dist/compare/compare.js +41 -5
  28. package/dist/compare/content-alignment.d.ts +95 -0
  29. package/dist/compare/content-alignment.js +1450 -0
  30. package/dist/compare/content-types.d.ts +100 -0
  31. package/dist/compare/content-types.js +0 -0
  32. package/dist/compare/content.d.ts +209 -0
  33. package/dist/compare/content.js +885 -0
  34. package/dist/compare/formatting.d.ts +5 -5
  35. package/dist/compare/formatting.js +6 -4
  36. package/dist/compare/plan.d.ts +6 -1
  37. package/dist/compare/plan.js +169 -567
  38. package/dist/compare/scenario.d.ts +2 -2
  39. package/dist/compare/types.d.ts +3 -3
  40. package/dist/compare/verification.d.ts +2 -2
  41. package/dist/compare/verification.js +25 -8
  42. package/dist/compat/eigenpal.d.ts +5 -3
  43. package/dist/compat/eigenpal.js +3 -2
  44. package/dist/controller/headerFooterEditorManager.js +14 -6
  45. package/dist/controller/hiddenEditorManager.d.ts +7 -2
  46. package/dist/controller/hiddenEditorManager.js +15 -6
  47. package/dist/controller/layoutPipeline.js +5 -3
  48. package/dist/controller/noteEditorManager.js +12 -7
  49. package/dist/display-list/build/headerFooterPrimitives.js +1 -0
  50. package/dist/display-list/build/storyPrimitives.d.ts +5 -2
  51. package/dist/display-list/build/storyPrimitives.js +60 -3
  52. package/dist/display-list/build/tablePrimitives.js +30 -0
  53. package/dist/display-list/primitives.d.ts +1 -1
  54. package/dist/document-operations.d.ts +6 -4
  55. package/dist/document-operations.js +77 -12
  56. package/dist/docx/blockContentParser.js +6 -27
  57. package/dist/docx/ensureParaIds.js +9 -4
  58. package/dist/docx/numberingParser.d.ts +3 -1
  59. package/dist/docx/numberingParser.js +4 -1
  60. package/dist/docx/paragraphParser.js +7 -6
  61. package/dist/docx/paragraphTraversal.js +30 -34
  62. package/dist/docx/rezip.d.ts +0 -8
  63. package/dist/docx/rezip.js +41 -16
  64. package/dist/docx/runParser.js +4 -2
  65. package/dist/docx/serializer/paragraphSerializer.js +2 -0
  66. package/dist/docx/serializer/runSerializer.js +5 -3
  67. package/dist/docx/serializer/trackedChangeAttributes.d.ts +3 -1
  68. package/dist/docx/serializer/trackedChangeAttributes.js +11 -1
  69. package/dist/docx/server/build.d.ts +1 -1
  70. package/dist/headless-layout.js +5 -4
  71. package/dist/index.d.ts +5 -3
  72. package/dist/index.js +3 -2
  73. package/dist/internal/headlessRevisionResolution.d.ts +23 -0
  74. package/dist/internal/headlessRevisionResolution.js +155 -0
  75. package/dist/internal/headlessRevisionResolutionGuard.d.ts +12 -0
  76. package/dist/internal/headlessRevisionResolutionGuard.js +28 -0
  77. package/dist/internal/pageBreakRunDescendantIndex.d.ts +9 -0
  78. package/dist/internal/pageBreakRunDescendantIndex.js +27 -0
  79. package/dist/internal/pageBreakRunPartition.d.ts +26 -0
  80. package/dist/internal/pageBreakRunPartition.js +35 -0
  81. package/dist/internal/pageBreakRunSourceDescendantIndex.d.ts +15 -0
  82. package/dist/internal/pageBreakRunSourceDescendantIndex.js +126 -0
  83. package/dist/internal/sectionEndpointResolution.d.ts +32 -0
  84. package/dist/internal/sectionEndpointResolution.js +38 -0
  85. package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
  86. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +16 -4
  87. package/dist/layout-bridge/convert/headerFooterLayout.js +48 -5
  88. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -0
  89. package/dist/layout-bridge/convert/toFlowBlocks.js +379 -75
  90. package/dist/layout-bridge/dom/clickToPositionDom.d.ts +8 -1
  91. package/dist/layout-bridge/dom/clickToPositionDom.js +114 -40
  92. package/dist/layout-bridge/dom/textStreamDom.d.ts +24 -0
  93. package/dist/layout-bridge/dom/textStreamDom.js +75 -0
  94. package/dist/layout-bridge/headerFooterLayout.js +16 -30
  95. package/dist/layout-engine/index.js +12 -3
  96. package/dist/layout-engine/measure/tableFragmentBorderGeometry.d.ts +17 -0
  97. package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +24 -0
  98. package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
  99. package/dist/layout-engine/types.d.ts +7 -0
  100. package/dist/layout-painter/imageLayout.d.ts +1 -1
  101. package/dist/layout-painter/renderTable.js +20 -0
  102. package/dist/model.d.ts +1 -1
  103. package/dist/paged-layout/rangeProjection.js +6 -9
  104. package/dist/paged-layout/transactionDirtyRange.d.ts +3 -1
  105. package/dist/paged-layout/transactionDirtyRange.js +23 -1
  106. package/dist/prosemirror/attrs/index.d.ts +7 -3
  107. package/dist/prosemirror/attrs/index.js +113 -7
  108. package/dist/prosemirror/bookmarkBoundaryIntegrity.d.ts +9 -0
  109. package/dist/prosemirror/bookmarkBoundaryIntegrity.js +22 -0
  110. package/dist/prosemirror/commands/comments.d.ts +10 -1
  111. package/dist/prosemirror/commands/comments.js +190 -59
  112. package/dist/prosemirror/commands/formatPainter.d.ts +45 -10
  113. package/dist/prosemirror/commands/formatPainter.js +319 -63
  114. package/dist/prosemirror/commands/index.d.ts +2 -2
  115. package/dist/prosemirror/commands/pageBreak.d.ts +5 -2
  116. package/dist/prosemirror/commands/pageBreak.js +18 -36
  117. package/dist/prosemirror/commands/propertyChangeScope.d.ts +7 -5
  118. package/dist/prosemirror/commands/propertyChangeScope.js +27 -18
  119. package/dist/prosemirror/conversion/fromProseDoc.d.ts +7 -2
  120. package/dist/prosemirror/conversion/fromProseDoc.js +809 -209
  121. package/dist/prosemirror/conversion/runShadingMark.js +2 -2
  122. package/dist/prosemirror/conversion/toProseDoc.d.ts +26 -1
  123. package/dist/prosemirror/conversion/toProseDoc.js +416 -283
  124. package/dist/prosemirror/extensions/StarterKit.js +4 -0
  125. package/dist/prosemirror/extensions/core/ParagraphExtension.js +12 -8
  126. package/dist/prosemirror/extensions/features/ListExtension.js +95 -123
  127. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +1 -1
  128. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +27 -1
  129. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +134 -30
  130. package/dist/prosemirror/extensions/features/pasteCleanup.js +9 -15
  131. package/dist/prosemirror/extensions/marks/BoldExtension.js +4 -3
  132. package/dist/prosemirror/extensions/marks/CharacterStyleExtension.d.ts +4 -7
  133. package/dist/prosemirror/extensions/marks/CharacterStyleExtension.js +5 -11
  134. package/dist/prosemirror/extensions/marks/FontSizeExtension.js +3 -3
  135. package/dist/prosemirror/extensions/marks/ItalicExtension.js +4 -3
  136. package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.d.ts +13 -0
  137. package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.js +39 -0
  138. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts +7 -1
  139. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +67 -21
  140. package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -1
  141. package/dist/prosemirror/extensions/marks/markUtils.js +88 -2
  142. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +44 -3
  143. package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
  144. package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +15 -4
  145. package/dist/prosemirror/extensions/nodes/PageBreakExtension.d.ts +6 -0
  146. package/dist/prosemirror/extensions/nodes/PageBreakExtension.js +6 -0
  147. package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.d.ts +6 -0
  148. package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.js +36 -0
  149. package/dist/prosemirror/findReplaceSelection.js +8 -3
  150. package/dist/prosemirror/index.d.ts +3 -3
  151. package/dist/prosemirror/listMarker.d.ts +26 -1
  152. package/dist/prosemirror/listMarker.js +108 -14
  153. package/dist/prosemirror/pageBreakRunProjection.d.ts +28 -0
  154. package/dist/prosemirror/pageBreakRunProjection.js +47 -0
  155. package/dist/prosemirror/paraText.js +6 -0
  156. package/dist/prosemirror/paragraphSpacing.d.ts +29 -0
  157. package/dist/prosemirror/paragraphSpacing.js +125 -0
  158. package/dist/prosemirror/plugins/documentNumbering.d.ts +8 -0
  159. package/dist/prosemirror/plugins/documentNumbering.js +18 -0
  160. package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -2
  161. package/dist/prosemirror/plugins/pmTextScan.js +14 -4
  162. package/dist/prosemirror/plugins/suggestionMode.js +8 -10
  163. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +16 -0
  164. package/dist/prosemirror/rebaseParagraphRunFormatting.js +61 -0
  165. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +64 -0
  166. package/dist/prosemirror/runFormattingInlineCarriers.js +121 -0
  167. package/dist/prosemirror/runFormattingProvenance.d.ts +12 -0
  168. package/dist/prosemirror/runFormattingProvenance.js +48 -0
  169. package/dist/prosemirror/runFormattingReconciliation.d.ts +25 -0
  170. package/dist/prosemirror/runFormattingReconciliation.js +54 -0
  171. package/dist/prosemirror/runPropertyChangeResolution.d.ts +18 -0
  172. package/dist/prosemirror/runPropertyChangeResolution.js +18 -0
  173. package/dist/prosemirror/runStyleFormatting.d.ts +50 -0
  174. package/dist/prosemirror/runStyleFormatting.js +105 -0
  175. package/dist/prosemirror/schema/index.d.ts +3 -3
  176. package/dist/prosemirror/schema/marks.d.ts +82 -9
  177. package/dist/prosemirror/schema/marks.js +73 -1
  178. package/dist/prosemirror/schema/nodes.d.ts +18 -5
  179. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +14 -2
  180. package/dist/prosemirror/styles/resolvedStyleAttrs.js +44 -23
  181. package/dist/prosemirror/styles/styleResolver.d.ts +10 -13
  182. package/dist/prosemirror/styles/styleResolver.js +3 -3
  183. package/dist/prosemirror/styles/styleToggleCascade.d.ts +8 -1
  184. package/dist/prosemirror/styles/styleToggleCascade.js +24 -1
  185. package/dist/prosemirror/tableGridMutation.d.ts +20 -0
  186. package/dist/prosemirror/tableGridMutation.js +30 -0
  187. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +31 -0
  188. package/dist/prosemirror/trackedRunInlineAtoms.js +39 -0
  189. package/dist/prosemirror/utils/visualLineNavigation.js +27 -31
  190. package/dist/prosemirror/validation.js +23 -4
  191. package/dist/render-dom/HeaderFooterSelectionOverlay.js +20 -20
  192. package/dist/render-dom/RenderedDomContext.js +17 -34
  193. package/dist/server.d.ts +4 -4
  194. package/dist/server.js +2 -2
  195. package/dist/style-engine/styleEngine.d.ts +1 -1
  196. package/dist/utils/clipboard.js +1 -1
  197. package/dist/utils/findReplace.js +38 -21
  198. package/dist/version-comparison.d.ts +38 -44
  199. package/dist/version-comparison.js +270 -401
  200. package/package.json +3 -2
@@ -1,6 +1,7 @@
1
- import { computeListRendering } from "../../docx/numberingParser.js";
1
+ import { computeListRendering, numberingLevelHasMarkerSlot } from "../../docx/numberingParser.js";
2
2
  import { tableOfContentsStyleLevel } from "../../utils/tableOfContentsStyle.js";
3
3
  import { setAutospacingBaseValue } from "../autospacingBase.js";
4
+ import { CLEARED_LIST_RENDERING_ATTRS } from "../listMarker.js";
4
5
  //#region src/prosemirror/styles/resolvedStyleAttrs.ts
5
6
  /**
6
7
  * Shared helper for projecting a resolved paragraph style onto ProseMirror
@@ -73,42 +74,62 @@ function autospacingBaseFromResolvedParagraphFormatting(ppr) {
73
74
  function listAttrsFromResolvedStyle(resolved, numbering) {
74
75
  const numPr = resolved.paragraphFormatting?.numPr;
75
76
  if (!numPr || numPr.numId === void 0 || numPr.numId === 0) return null;
76
- const rendering = numbering ? computeListRendering(numPr, numbering) : null;
77
+ const attrs = listAttrsFromNumbering({
78
+ numId: numPr.numId,
79
+ ilvl: numPr.ilvl ?? 0
80
+ }, numbering);
77
81
  const level = numbering?.getLevel(numPr.numId, numPr.ilvl ?? 0);
78
- const attrs = {
79
- numPr: {
80
- numId: numPr.numId,
81
- ilvl: numPr.ilvl ?? 0
82
- },
83
- numPrFromStyle: {
84
- numId: numPr.numId,
85
- ilvl: numPr.ilvl ?? 0
86
- },
82
+ attrs["numPrFromStyle"] = {
83
+ numId: numPr.numId,
84
+ ilvl: numPr.ilvl ?? 0
85
+ };
86
+ const ppr = resolved.paragraphFormatting;
87
+ if (level?.pPr) {
88
+ if (ppr?.indentLeft === void 0 && level.pPr.indentLeft !== void 0) attrs["indentLeft"] = level.pPr.indentLeft;
89
+ if (!(ppr?.indentFirstLine !== void 0 || ppr?.hangingIndent !== void 0) && numberingLevelHasMarkerSlot(level)) {
90
+ if (level.pPr.indentFirstLine !== void 0) attrs["indentFirstLine"] = level.pPr.indentFirstLine;
91
+ if (level.pPr.hangingIndent !== void 0) attrs["hangingIndent"] = level.pPr.hangingIndent;
92
+ }
93
+ }
94
+ return attrs;
95
+ }
96
+ /** Project one resolved numbering level into the complete editor attr group. */
97
+ function listAttrsFromNumbering(numPr, numbering) {
98
+ const targetNumPr = {
99
+ numId: numPr.numId,
100
+ ilvl: numPr.ilvl
101
+ };
102
+ const rendering = numbering ? computeListRendering(targetNumPr, numbering) : null;
103
+ return {
104
+ ...CLEARED_LIST_RENDERING_ATTRS,
105
+ numPr: targetNumPr,
87
106
  listNumFmt: rendering?.numFmt ?? null,
88
107
  listIsBullet: rendering?.isBullet ?? null,
89
108
  listIsLegal: rendering?.isLegal ?? null,
90
109
  listMarker: rendering?.marker ?? null,
110
+ listMarkerTemplate: rendering?.markerTemplate ?? null,
91
111
  listMarkerHidden: rendering?.markerHidden ?? null,
92
112
  listMarkerFormatting: rendering?.markerFormatting ?? null,
93
113
  listMarkerAlignment: rendering?.markerAlignment ?? null,
94
114
  listMarkerSuffix: rendering?.markerSuffix ?? null,
95
115
  listMarkerAllCaps: rendering?.markerAllCaps ?? null,
96
- listImplicitChildLevelAdvances: null,
97
- listMarkerSecondSlotOffsetTwips: null,
98
116
  listLevelNumFmts: rendering?.levelNumFmts ?? null,
99
117
  listLevelStarts: rendering?.levelStarts ?? null,
100
118
  listAbstractNumId: rendering?.abstractNumId ?? null,
101
119
  listStartOverride: rendering?.startOverride ?? null
102
120
  };
103
- const ppr = resolved.paragraphFormatting;
104
- if (level?.pPr) {
105
- if (ppr?.indentLeft === void 0 && level.pPr.indentLeft !== void 0) attrs["indentLeft"] = level.pPr.indentLeft;
106
- if (!(ppr?.indentFirstLine !== void 0 || ppr?.hangingIndent !== void 0)) {
107
- if (level.pPr.indentFirstLine !== void 0) attrs["indentFirstLine"] = level.pPr.indentFirstLine;
108
- if (level.pPr.hangingIndent !== void 0) attrs["hangingIndent"] = level.pPr.hangingIndent;
109
- }
110
- }
111
- return attrs;
121
+ }
122
+ /** Recompute every level-dependent attr when a paragraph changes list level. */
123
+ function listLevelAttrPatch(attrs, numPr, numbering) {
124
+ const level = numbering?.getLevel(numPr.numId, numPr.ilvl);
125
+ const hasMarkerSlot = level ? numberingLevelHasMarkerSlot(level) : false;
126
+ return {
127
+ ...listAttrsFromNumbering(numPr, numbering),
128
+ listImplicitChildLevelAdvances: attrs.listImplicitChildLevelAdvances ?? null,
129
+ indentLeft: level?.pPr?.indentLeft ?? null,
130
+ indentFirstLine: hasMarkerSlot ? level?.pPr?.indentFirstLine ?? null : null,
131
+ hangingIndent: hasMarkerSlot ? level?.pPr?.hangingIndent ?? null : null
132
+ };
112
133
  }
113
134
  //#endregion
114
- export { listAttrsFromResolvedStyle, paragraphAttrsFromResolvedStyle };
135
+ export { listAttrsFromNumbering, listAttrsFromResolvedStyle, listLevelAttrPatch, paragraphAttrsFromResolvedStyle };
@@ -10,21 +10,18 @@ type ResolvedParagraphStyle = {
10
10
  runFormatting?: document_d_exports.TextFormatting;
11
11
  };
12
12
  /**
13
- * Paragraph-spacing fields sourced from the table style enclosing a cell
14
- * paragraph: the table style's own `w:pPr`, then the applicable
13
+ * Paragraph fields sourced from the table style enclosing a cell paragraph:
14
+ * the table style's own `w:pPr`, then the applicable
15
15
  * `w:tblStylePr` conditional region's `w:pPr` layered on top (already merged
16
16
  * by the caller — see `resolveTableBaseStyle`/`resolveTableStyleConditional`
17
17
  * in toProseDoc.ts).
18
18
  *
19
- * Deliberately narrower than the full {@link ParagraphFormatting}: this is
20
- * the layer-2 overlay described on {@link StyleResolver.resolveParagraphStyleInTable}
21
- * and is scoped to the spacing fields responsible for folio's table-row
22
- * height bug (table styles like Word's `TableGrid` zero these out relative
23
- * to `docDefaults`). Alignment, indentation, borders, etc. are out of scope
24
- * here — applying the whole `pPr` would risk regressing unrelated cell
25
- * layout that isn't part of this fix.
19
+ * Deliberately narrower than the full {@link ParagraphFormatting}: spacing
20
+ * affects table-row height, while `frame` participates in lossless page-break
21
+ * ownership checks. Other fields remain out of scope until their table-style
22
+ * projection is modeled end to end.
26
23
  */
27
- type TableCellParagraphSpacingOverlay = Pick<document_d_exports.ParagraphFormatting, "spaceBefore" | "spaceAfter" | "lineSpacing" | "lineSpacingRule" | "contextualSpacing">;
24
+ type TableCellParagraphSpacingOverlay = Pick<document_d_exports.ParagraphFormatting, "spaceBefore" | "spaceAfter" | "lineSpacing" | "lineSpacingRule" | "contextualSpacing" | "frame">;
28
25
  /**
29
26
  * StyleResolver provides efficient access to resolved style properties
30
27
  */
@@ -52,7 +49,7 @@ declare class StyleResolver {
52
49
  resolveParagraphStyle(styleId: string | undefined | null): ResolvedParagraphStyle;
53
50
  /**
54
51
  * Resolve paragraph style properties for a paragraph inside a table cell,
55
- * layering the enclosing table style's paragraph-spacing fields into the
52
+ * layering the modeled enclosing-table paragraph fields into the
56
53
  * cascade between docDefaults and the paragraph's own style chain.
57
54
  *
58
55
  * Per ECMA-376 §17.7.2, a cell paragraph's properties resolve in this
@@ -68,10 +65,10 @@ declare class StyleResolver {
68
65
  * paragraph style still wins over the table for any field it sets.
69
66
  *
70
67
  * @param styleId - The paragraph's style ID (e.g., 'Heading1', 'Normal')
71
- * @param tableParagraphOverlay - Paragraph-spacing fields from the
68
+ * @param tableParagraphOverlay - Modeled paragraph fields from the
72
69
  * enclosing table style (base `pPr` merged with the applicable
73
70
  * conditional region), or `undefined` when the paragraph isn't in a
74
- * table cell, the table has no style, or the style sets no spacing.
71
+ * table cell, the table has no style, or the style sets none of them.
75
72
  * @returns Resolved paragraph and run formatting
76
73
  */
77
74
  resolveParagraphStyleInTable(styleId: string | undefined | null, tableParagraphOverlay: TableCellParagraphSpacingOverlay | undefined): ResolvedParagraphStyle;
@@ -74,7 +74,7 @@ var StyleResolver = class {
74
74
  }
75
75
  /**
76
76
  * Resolve paragraph style properties for a paragraph inside a table cell,
77
- * layering the enclosing table style's paragraph-spacing fields into the
77
+ * layering the modeled enclosing-table paragraph fields into the
78
78
  * cascade between docDefaults and the paragraph's own style chain.
79
79
  *
80
80
  * Per ECMA-376 §17.7.2, a cell paragraph's properties resolve in this
@@ -90,10 +90,10 @@ var StyleResolver = class {
90
90
  * paragraph style still wins over the table for any field it sets.
91
91
  *
92
92
  * @param styleId - The paragraph's style ID (e.g., 'Heading1', 'Normal')
93
- * @param tableParagraphOverlay - Paragraph-spacing fields from the
93
+ * @param tableParagraphOverlay - Modeled paragraph fields from the
94
94
  * enclosing table style (base `pPr` merged with the applicable
95
95
  * conditional region), or `undefined` when the paragraph isn't in a
96
- * table cell, the table has no style, or the style sets no spacing.
96
+ * table cell, the table has no style, or the style sets none of them.
97
97
  * @returns Resolved paragraph and run formatting
98
98
  */
99
99
  resolveParagraphStyleInTable(styleId, tableParagraphOverlay) {
@@ -23,6 +23,13 @@ type StyleToggleCascadeOptions = {
23
23
  /** Existing ordinary-property merge whose toggle fields this cascade replaces. */
24
24
  ordinaryFormatting: document_d_exports.TextFormatting | undefined;
25
25
  };
26
+ type CascadeStyleFromResolvedBaseOptions = {
27
+ baseFormatting: document_d_exports.TextFormatting | undefined;
28
+ defaultsActiveProperties: readonly StyleToggleKey[] | undefined;
29
+ styleFormatting: document_d_exports.TextFormatting | undefined;
30
+ };
31
+ /** Continue a style cascade from a compact resolved paragraph snapshot. */
32
+ declare const cascadeStyleFromResolvedBase: ({ baseFormatting, defaultsActiveProperties, styleFormatting }: CascadeStyleFromResolvedBaseOptions) => document_d_exports.TextFormatting | undefined;
26
33
  /**
27
34
  * Resolve run formatting across OOXML style hierarchy levels.
28
35
  *
@@ -34,4 +41,4 @@ type StyleToggleCascadeOptions = {
34
41
  */
35
42
  declare function cascadeStyleTextFormatting(levels: readonly StyleToggleLevel[], options?: StyleToggleCascadeOptions): StyleTextFormattingCascade;
36
43
  //#endregion
37
- export { cascadeStyleTextFormatting };
44
+ export { StyleToggleKey, cascadeStyleFromResolvedBase, cascadeStyleTextFormatting };
@@ -1,5 +1,28 @@
1
1
  import { STYLE_TOGGLE_KEYS, mergeTextFormatting } from "../../utils/textFormattingMerge.js";
2
2
  //#region src/prosemirror/styles/styleToggleCascade.ts
3
+ /** Continue a style cascade from a compact resolved paragraph snapshot. */
4
+ const cascadeStyleFromResolvedBase = ({ baseFormatting, defaultsActiveProperties, styleFormatting }) => {
5
+ const defaultsActive = new Set(defaultsActiveProperties);
6
+ const toggleStates = /* @__PURE__ */ new Map();
7
+ for (const key of STYLE_TOGGLE_KEYS) {
8
+ const value = baseFormatting?.[key];
9
+ if (value !== void 0) toggleStates.set(key, {
10
+ value,
11
+ defaultsActive: defaultsActive.has(key)
12
+ });
13
+ }
14
+ return cascadeStyleTextFormatting([{
15
+ cascade: {
16
+ formatting: baseFormatting,
17
+ toggleStates,
18
+ type: "styleToggleCascade"
19
+ },
20
+ type: "carried"
21
+ }, {
22
+ formatting: styleFormatting,
23
+ type: "style"
24
+ }]).formatting;
25
+ };
3
26
  /**
4
27
  * Resolve run formatting across OOXML style hierarchy levels.
5
28
  *
@@ -49,4 +72,4 @@ function cascadeStyleTextFormatting(levels, options) {
49
72
  };
50
73
  }
51
74
  //#endregion
52
- export { cascadeStyleTextFormatting };
75
+ export { cascadeStyleFromResolvedBase, cascadeStyleTextFormatting };
@@ -0,0 +1,20 @@
1
+ import { Transaction } from "prosemirror-state";
2
+ import { Node } from "prosemirror-model";
3
+ //#region src/prosemirror/tableGridMutation.d.ts
4
+ type ReconcileTableGridAfterColumnRemovalOptions = {
5
+ tr: Transaction;
6
+ tablePosition: number;
7
+ previousTable: Node;
8
+ removedColumn: number;
9
+ };
10
+ /**
11
+ * Keep the editable table grid aligned with a physical column removal.
12
+ *
13
+ * `prosemirror-tables` owns cell topology, but Folio separately retains the
14
+ * authored `w:tblGrid` widths and source XML on the table node. A topology
15
+ * edit that leaves those attrs untouched serializes a new row shape against
16
+ * the old grid; reopening then has to interpret surviving cells as spans.
17
+ */
18
+ declare const reconcileTableGridAfterColumnRemoval: ({ tr, tablePosition, previousTable, removedColumn }: ReconcileTableGridAfterColumnRemovalOptions) => void;
19
+ //#endregion
20
+ export { reconcileTableGridAfterColumnRemoval };
@@ -0,0 +1,30 @@
1
+ import { expectTableAttrs, mergeTableAttrs } from "./attrs/index.js";
2
+ import { TableMap } from "prosemirror-tables";
3
+ //#region src/prosemirror/tableGridMutation.ts
4
+ /**
5
+ * Keep the editable table grid aligned with a physical column removal.
6
+ *
7
+ * `prosemirror-tables` owns cell topology, but Folio separately retains the
8
+ * authored `w:tblGrid` widths and source XML on the table node. A topology
9
+ * edit that leaves those attrs untouched serializes a new row shape against
10
+ * the old grid; reopening then has to interpret surviving cells as spans.
11
+ */
12
+ const reconcileTableGridAfterColumnRemoval = ({ tr, tablePosition, previousTable, removedColumn }) => {
13
+ const previousColumnCount = TableMap.get(previousTable).width;
14
+ const table = tr.doc.nodeAt(tablePosition);
15
+ if (!table || table.type.spec["tableRole"] !== "table") return;
16
+ const columnCount = TableMap.get(table).width;
17
+ if (columnCount >= previousColumnCount) return;
18
+ const removedColumnCount = previousColumnCount - columnCount;
19
+ const previousWidths = expectTableAttrs(previousTable).columnWidths;
20
+ const columnWidths = previousWidths?.length === previousColumnCount ? previousWidths.toSpliced(removedColumn, removedColumnCount) : void 0;
21
+ const formatting = expectTableAttrs(table)._originalFormatting;
22
+ const originalFormatting = formatting ? { ...formatting } : void 0;
23
+ if (originalFormatting) delete originalFormatting.gridSourceXml;
24
+ tr.setNodeMarkup(tablePosition, void 0, mergeTableAttrs(table, {
25
+ columnWidths: columnWidths?.length === columnCount ? columnWidths : void 0,
26
+ _originalFormatting: originalFormatting
27
+ }));
28
+ };
29
+ //#endregion
30
+ export { reconcileTableGridAfterColumnRemoval };
@@ -0,0 +1,31 @@
1
+ import { Node } from "prosemirror-model";
2
+ //#region src/prosemirror/trackedRunInlineAtoms.d.ts
3
+ type TrackedRunInlineAtomDisposition = "carry" | "field-carrier" | "outside-wrapper" | "page-break-carrier" | "text-carrier" | "transparent";
4
+ /**
5
+ * How every inline atom in the editor schema participates in an OOXML
6
+ * run-level tracked-change wrapper.
7
+ *
8
+ * The schema is assembled at runtime, so the fixed-point test binds this map
9
+ * to its complete inline-atom set. The lookup also panics on a missing entry:
10
+ * a new atom cannot silently lose revision ownership before that test runs.
11
+ */
12
+ declare const TRACKED_RUN_INLINE_ATOM_DISPOSITIONS: Readonly<{
13
+ readonly bookmarkBoundary: "carry";
14
+ readonly field: "field-carrier";
15
+ readonly hardBreak: "carry";
16
+ readonly image: "carry";
17
+ readonly math: "outside-wrapper";
18
+ readonly pageBreakRun: "page-break-carrier";
19
+ readonly renderedPageBreak: "transparent";
20
+ readonly shape: "carry";
21
+ readonly structuredField: "carry";
22
+ readonly symbol: "carry";
23
+ readonly tab: "carry";
24
+ readonly text: "text-carrier";
25
+ readonly textBoxAnchor: "carry";
26
+ }>;
27
+ declare const trackedRunInlineAtomDisposition: (node: Node) => TrackedRunInlineAtomDisposition | null;
28
+ /** Whether an editor node can own an insertion/deletion mark as one run carrier. */
29
+ declare const canCarryTrackedRunMark: (node: Node) => boolean;
30
+ //#endregion
31
+ export { TRACKED_RUN_INLINE_ATOM_DISPOSITIONS, TrackedRunInlineAtomDisposition, canCarryTrackedRunMark, trackedRunInlineAtomDisposition };
@@ -0,0 +1,39 @@
1
+ import { panic } from "better-result";
2
+ //#region src/prosemirror/trackedRunInlineAtoms.ts
3
+ /**
4
+ * How every inline atom in the editor schema participates in an OOXML
5
+ * run-level tracked-change wrapper.
6
+ *
7
+ * The schema is assembled at runtime, so the fixed-point test binds this map
8
+ * to its complete inline-atom set. The lookup also panics on a missing entry:
9
+ * a new atom cannot silently lose revision ownership before that test runs.
10
+ */
11
+ const TRACKED_RUN_INLINE_ATOM_DISPOSITIONS = Object.freeze({
12
+ bookmarkBoundary: "carry",
13
+ field: "field-carrier",
14
+ hardBreak: "carry",
15
+ image: "carry",
16
+ math: "outside-wrapper",
17
+ pageBreakRun: "page-break-carrier",
18
+ renderedPageBreak: "transparent",
19
+ shape: "carry",
20
+ structuredField: "carry",
21
+ symbol: "carry",
22
+ tab: "carry",
23
+ text: "text-carrier",
24
+ textBoxAnchor: "carry"
25
+ });
26
+ const dispositionsByName = new Map(Object.entries(TRACKED_RUN_INLINE_ATOM_DISPOSITIONS));
27
+ const trackedRunInlineAtomDisposition = (node) => {
28
+ if (!node.isInline || !node.isAtom) return null;
29
+ const disposition = dispositionsByName.get(node.type.name);
30
+ if (!disposition) panic(`Inline atom ${JSON.stringify(node.type.name)} has no tracked-run disposition`);
31
+ return disposition;
32
+ };
33
+ /** Whether an editor node can own an insertion/deletion mark as one run carrier. */
34
+ const canCarryTrackedRunMark = (node) => {
35
+ const disposition = trackedRunInlineAtomDisposition(node);
36
+ return disposition === "carry" || disposition === "field-carrier" || disposition === "text-carrier" || disposition === "page-break-carrier";
37
+ };
38
+ //#endregion
39
+ export { TRACKED_RUN_INLINE_ATOM_DISPOSITIONS, canCarryTrackedRunMark, trackedRunInlineAtomDisposition };
@@ -1,3 +1,5 @@
1
+ import { findBodyEmptyRuns, findBodyPmSpans } from "../../layout-bridge/dom/findBodyPmSpans.js";
2
+ import { createTextStreamRange, descendantTextNodes, totalTextLength } from "../../layout-bridge/dom/textStreamDom.js";
1
3
  import { findVerticalScrollParent } from "../../utils/findVerticalScrollParent.js";
2
4
  import { TextSelection } from "prosemirror-state";
3
5
  //#region src/prosemirror/utils/visualLineNavigation.ts
@@ -33,28 +35,21 @@ function scrollIntoViewIfNeeded(el) {
33
35
  }
34
36
  /** @internal */
35
37
  function getCaretClientX(container, pmPos) {
36
- const spans = container.querySelectorAll("span[data-pm-start][data-pm-end]");
37
- for (const span of Array.from(spans)) {
38
- const spanEl = span;
38
+ for (const spanEl of findBodyPmSpans(container)) {
39
39
  const pmStart = Number(spanEl.dataset["pmStart"]);
40
40
  const pmEnd = Number(spanEl.dataset["pmEnd"]);
41
41
  if (spanEl.classList.contains("layout-run-tab")) {
42
42
  if (pmPos >= pmStart && pmPos < pmEnd) return spanEl.getBoundingClientRect().left;
43
43
  continue;
44
44
  }
45
- if (pmPos >= pmStart && pmPos <= pmEnd && span.firstChild?.nodeType === Node.TEXT_NODE) {
46
- const textNode = span.firstChild;
47
- const charIndex = Math.min(pmPos - pmStart, textNode.length);
48
- const ownerDoc = spanEl.ownerDocument;
49
- if (!ownerDoc) continue;
50
- const range = ownerDoc.createRange();
51
- range.setStart(textNode, charIndex);
52
- range.setEnd(textNode, charIndex);
53
- return range.getBoundingClientRect().left;
45
+ if (pmPos >= pmStart && pmPos <= pmEnd) {
46
+ const textNodes = descendantTextNodes(spanEl);
47
+ const charIndex = Math.min(pmPos - pmStart, totalTextLength(textNodes));
48
+ const range = createTextStreamRange(spanEl, charIndex, charIndex);
49
+ if (range) return range.getBoundingClientRect().left;
54
50
  }
55
51
  }
56
- const emptyRuns = container.querySelectorAll(".layout-empty-run");
57
- for (const emptyRun of Array.from(emptyRuns)) {
52
+ for (const emptyRun of findBodyEmptyRuns(container)) {
58
53
  const paragraph = emptyRun.closest(".layout-paragraph");
59
54
  if (!paragraph) continue;
60
55
  const pmStart = Number(paragraph.dataset["pmStart"]);
@@ -91,6 +86,13 @@ function findLineElementAtPosition(container, pmPos) {
91
86
  /** @internal */
92
87
  function findPositionOnLineAtClientX(lineEl, clientX) {
93
88
  const spans = lineEl.querySelectorAll("span[data-pm-start][data-pm-end]");
89
+ const emptyRun = lineEl.querySelector(".layout-empty-run");
90
+ if (emptyRun) {
91
+ const emptyRunStart = emptyRun.dataset["pmStart"];
92
+ if (emptyRunStart !== void 0) return Number(emptyRunStart);
93
+ const paragraphStart = emptyRun.closest(".layout-paragraph")?.dataset["pmStart"];
94
+ return paragraphStart === void 0 ? null : Number(paragraphStart) + 1;
95
+ }
94
96
  if (spans.length === 0) {
95
97
  const paragraph = lineEl.closest(".layout-paragraph");
96
98
  if (paragraph?.dataset["pmStart"]) return Number(paragraph.dataset["pmStart"]) + 1;
@@ -106,29 +108,23 @@ function findPositionOnLineAtClientX(lineEl, clientX) {
106
108
  continue;
107
109
  }
108
110
  if (clientX >= rect.left && clientX <= rect.right) {
109
- const textNode = spanEl.firstChild;
110
- if (!textNode || textNode.nodeType !== Node.TEXT_NODE) return pmStart;
111
- const text = textNode;
112
- const ownerDoc = spanEl.ownerDocument;
113
- if (!ownerDoc) return pmStart;
111
+ const textLength = totalTextLength(descendantTextNodes(spanEl));
112
+ if (textLength === 0) return pmStart;
114
113
  let lo = 0;
115
- let hi = text.length;
114
+ let hi = textLength;
116
115
  while (lo < hi) {
117
116
  const mid = Math.floor((lo + hi) / 2);
118
- const r = ownerDoc.createRange();
119
- r.setStart(text, mid);
120
- r.setEnd(text, mid);
117
+ const r = createTextStreamRange(spanEl, mid, mid);
118
+ if (!r) return pmStart;
121
119
  if (clientX < r.getBoundingClientRect().left) hi = mid;
122
120
  else lo = mid + 1;
123
121
  }
124
- if (lo > 0 && lo <= text.length) {
125
- const r = ownerDoc.createRange();
126
- r.setStart(text, lo - 1);
127
- r.setEnd(text, lo - 1);
128
- const leftX = r.getBoundingClientRect().left;
129
- r.setStart(text, Math.min(lo, text.length));
130
- r.setEnd(text, Math.min(lo, text.length));
131
- const rightX = r.getBoundingClientRect().left;
122
+ if (lo > 0 && lo <= textLength) {
123
+ const left = createTextStreamRange(spanEl, lo - 1, lo - 1);
124
+ const right = createTextStreamRange(spanEl, Math.min(lo, textLength), Math.min(lo, textLength));
125
+ if (!left || !right) return pmStart;
126
+ const leftX = left.getBoundingClientRect().left;
127
+ const rightX = right.getBoundingClientRect().left;
132
128
  if (Math.abs(clientX - leftX) < Math.abs(clientX - rightX)) return pmStart + (lo - 1);
133
129
  }
134
130
  return pmStart + Math.min(lo, pmEnd - pmStart);
@@ -1,4 +1,4 @@
1
- import { readBlockSdtAttrs, readCharacterSpacingMarkAttrs, readCharacterStyleMarkAttrs, readCommentMarkAttrs, readEmphasisMarkAttrs, readFieldAttrs, readFontFamilyMarkAttrs, readFontSizeMarkAttrs, readFootnoteRefMarkAttrs, readHardBreakAttrs, readHighlightMarkAttrs, readHyperlinkMarkAttrs, readImageAttrs, readLanguageMarkAttrs, readMathAttrs, readParagraphAttrs, readRunFormattingOverrideMarkAttrs, readRunPropertyChangeMarkAttrs, readRunShadingMarkAttrs, readSdtAttrs, readShapeAttrs, readStrikeMarkAttrs, readSymbolAttrs, readTabAttrs, readTableAttrs, readTableCellAttrs, readTableRowAttrs, readTextBoxAttrs, readTextColorMarkAttrs, readTextEffectMarkAttrs, readTrackedChangeMarkAttrs, readUnderlineMarkAttrs } from "./attrs/index.js";
1
+ import { readBlockSdtAttrs, readCharacterSpacingMarkAttrs, readCharacterStyleMarkAttrs, readCommentMarkAttrs, readEmphasisMarkAttrs, readFieldAttrs, readFontFamilyMarkAttrs, readFontSizeMarkAttrs, readFootnoteRefMarkAttrs, readHardBreakAttrs, readHighlightMarkAttrs, readHyperlinkMarkAttrs, readImageAttrs, readLanguageMarkAttrs, readMathAttrs, readPageBreakRunAttrs, readPageBreakRunOwnerMarkAttrs, readParagraphAttrs, readRunFormattingOverrideMarkAttrs, readRunPropertyChangeMarkAttrs, readRunShadingMarkAttrs, readSdtAttrs, readShapeAttrs, readStrikeMarkAttrs, readSymbolAttrs, readTabAttrs, readTableAttrs, readTableCellAttrs, readTableRowAttrs, readTextBoxAttrs, readTextColorMarkAttrs, readTextEffectMarkAttrs, readTrackedChangeMarkAttrs, readUnderlineMarkAttrs } from "./attrs/index.js";
2
2
  import { readBookmarkBoundaryAttrs } from "./bookmarkBoundaryAttrs.js";
3
3
  import { readTextBoxAnchorAttrs } from "./textBoxAnchorAttrs.js";
4
4
  //#region src/prosemirror/validation.ts
@@ -151,6 +151,9 @@ const validateNodeAttrs = (node, path, issues) => {
151
151
  case "hardBreak":
152
152
  appendAttrIssues(path, readHardBreakAttrs(node), issues);
153
153
  return;
154
+ case "pageBreakRun":
155
+ appendAttrIssues(path, readPageBreakRunAttrs(node), issues);
156
+ return;
154
157
  case "paragraph":
155
158
  appendAttrIssues(path, readParagraphAttrs(node), issues);
156
159
  return;
@@ -180,18 +183,31 @@ const validateNodeAttrs = (node, path, issues) => {
180
183
  appendAttrIssues(path, fieldAttrs, issues);
181
184
  if (fieldAttrs.ok) {
182
185
  const hasStructuredHyperlink = node.content.content.some((child) => child.marks.some((mark) => mark.type.name === "hyperlink"));
183
- if (fieldAttrs.value.fieldKind === "complex") issues.push({
186
+ const hasPageBreakCarrier = node.content.content.some((child) => child.type.name === "pageBreakRun");
187
+ if (fieldAttrs.value.fieldKind === "complex" && !hasPageBreakCarrier) issues.push({
184
188
  path: `${path}.content`,
185
189
  message: "Complex fields cannot contain structured result children."
186
190
  });
187
- else if (!hasStructuredHyperlink) issues.push({
191
+ else if (fieldAttrs.value.fieldKind === "complex" && hasStructuredHyperlink) issues.push({
192
+ path: `${path}.content`,
193
+ message: "Complex field results cannot contain hyperlink content."
194
+ });
195
+ else if (!hasStructuredHyperlink && !hasPageBreakCarrier) issues.push({
188
196
  path: `${path}.content`,
189
197
  message: "Structured simple fields require hyperlink content."
190
198
  });
191
199
  node.forEach((child, _offset, index) => {
192
200
  const childPath = `${path}.content[${index}]`;
193
201
  const hasHyperlink = child.marks.some((mark) => mark.type.name === "hyperlink");
194
- if (child.type.name === "bookmarkBoundary" && !hasHyperlink) issues.push({
202
+ if (fieldAttrs.value.fieldKind === "complex" && child.type.name === "textBoxAnchor") issues.push({
203
+ path: childPath,
204
+ message: "Complex field results cannot contain text-box anchors."
205
+ });
206
+ else if (fieldAttrs.value.fieldKind === "complex" && child.type.name === "bookmarkBoundary") issues.push({
207
+ path: childPath,
208
+ message: "Complex field results cannot contain bookmark boundaries."
209
+ });
210
+ else if (child.type.name === "bookmarkBoundary" && !hasHyperlink) issues.push({
195
211
  path: childPath,
196
212
  message: "Bookmark boundaries inside fields require a hyperlink parent."
197
213
  });
@@ -289,6 +305,9 @@ const validateMarks = (marks, path, issues) => {
289
305
  case "runPropertyChange":
290
306
  appendAttrIssues(markPath, readRunPropertyChangeMarkAttrs(mark), issues);
291
307
  continue;
308
+ case "pageBreakRunOwner":
309
+ appendAttrIssues(markPath, readPageBreakRunOwnerMarkAttrs(mark), issues);
310
+ continue;
292
311
  case "runFormattingOverride":
293
312
  appendAttrIssues(markPath, readRunFormattingOverrideMarkAttrs(mark), issues);
294
313
  continue;
@@ -1,9 +1,9 @@
1
1
  import { findHfCaretSpan, findHfPmSpans } from "../layout-bridge/dom/findHfPmSpans.js";
2
+ import { createTextStreamRange, descendantTextNodes, totalTextLength } from "../layout-bridge/dom/textStreamDom.js";
2
3
  //#region src/render-dom/HeaderFooterSelectionOverlay.ts
3
4
  const CARET_CLASS = "folio-hf-selection-caret";
4
5
  const RANGE_CLASS = "folio-hf-selection-rect";
5
6
  const OWNED_SELECTOR = `.${CARET_CLASS}, .${RANGE_CLASS}`;
6
- const TEXT_NODE_TYPE = 3;
7
7
  const relativeRect = (rect, containerRect, zoom) => ({
8
8
  x: (rect.left - containerRect.left) / zoom,
9
9
  y: (rect.top - containerRect.top) / zoom,
@@ -27,17 +27,17 @@ const resolveHeaderFooterSelectionGeometry = (pagesContainer, selection, zoom) =
27
27
  let height = anchorRect.height || 16;
28
28
  const pmStart = Number.parseInt(hit.element.dataset["pmStart"] ?? "", 10);
29
29
  const pmEnd = Number.parseInt(hit.element.dataset["pmEnd"] ?? "", 10);
30
- const textNode = hit.element.firstChild;
31
- if (textNode?.nodeType === TEXT_NODE_TYPE && Number.isFinite(pmStart) && Number.isFinite(pmEnd)) {
32
- const offset = Math.min(Math.max(0, selection.from - pmStart), textNode.textContent?.length ?? 0);
33
- const range = hit.element.ownerDocument.createRange();
34
- range.setStart(textNode, offset);
35
- range.setEnd(textNode, offset);
36
- const rangeRect = range.getBoundingClientRect();
37
- if (rangeRect.height > 0 || rangeRect.width > 0 || rangeRect.left > 0) {
38
- left = rangeRect.left;
39
- top = rangeRect.top;
40
- height = rangeRect.height || height;
30
+ const textNodes = descendantTextNodes(hit.element);
31
+ if (textNodes.length > 0 && Number.isFinite(pmStart) && Number.isFinite(pmEnd)) {
32
+ const offset = Math.min(Math.max(0, selection.from - pmStart), totalTextLength(textNodes));
33
+ const range = createTextStreamRange(hit.element, offset, offset);
34
+ if (range) {
35
+ const rangeRect = range.getBoundingClientRect();
36
+ if (rangeRect.height > 0 || rangeRect.width > 0 || rangeRect.left > 0) {
37
+ left = rangeRect.left;
38
+ top = rangeRect.top;
39
+ height = rangeRect.height || height;
40
+ }
41
41
  }
42
42
  }
43
43
  return {
@@ -57,17 +57,17 @@ const resolveHeaderFooterSelectionGeometry = (pagesContainer, selection, zoom) =
57
57
  const spanEnd = Number.parseInt(span.dataset["pmEnd"] ?? "", 10);
58
58
  if (!Number.isFinite(spanStart) || !Number.isFinite(spanEnd)) continue;
59
59
  if (spanEnd <= from || spanStart >= to) continue;
60
- const textNode = span.firstChild;
61
- if (textNode?.nodeType === TEXT_NODE_TYPE) {
62
- const length = textNode.textContent?.length ?? 0;
60
+ const textNodes = descendantTextNodes(span);
61
+ if (textNodes.length > 0) {
62
+ const length = totalTextLength(textNodes);
63
63
  const startOffset = Math.max(0, from - spanStart);
64
64
  const endOffset = Math.min(length, to - spanStart);
65
65
  if (startOffset < endOffset) {
66
- const range = span.ownerDocument.createRange();
67
- range.setStart(textNode, startOffset);
68
- range.setEnd(textNode, endOffset);
69
- for (const rect of Array.from(range.getClientRects())) ranges.push(relativeRect(rect, containerRect, zoomDivisor));
70
- continue;
66
+ const range = createTextStreamRange(span, startOffset, endOffset);
67
+ if (range) {
68
+ for (const rect of Array.from(range.getClientRects())) ranges.push(relativeRect(rect, containerRect, zoomDivisor));
69
+ continue;
70
+ }
71
71
  }
72
72
  }
73
73
  ranges.push(relativeRect(span.getBoundingClientRect(), containerRect, zoomDivisor));