@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.
- package/README.md +59 -1
- package/dist/ai-edits/apply.js +545 -296
- package/dist/ai-edits/block-identity.d.ts +4 -1
- package/dist/ai-edits/block-identity.js +4 -1
- package/dist/ai-edits/blockRange.js +14 -14
- package/dist/ai-edits/clean-text.d.ts +40 -1
- package/dist/ai-edits/clean-text.js +52 -2
- package/dist/ai-edits/headless.d.ts +3 -1
- package/dist/ai-edits/headless.js +133 -71
- package/dist/ai-edits/index.d.ts +3 -3
- package/dist/ai-edits/index.js +2 -2
- package/dist/ai-edits/read.js +38 -20
- package/dist/ai-edits/snapshot.d.ts +14 -6
- package/dist/ai-edits/snapshot.js +99 -19
- package/dist/ai-edits/table-cell-mutations.js +1 -1
- package/dist/ai-edits/table-row-column-mutations.js +7 -0
- package/dist/ai-edits/table-template.d.ts +11 -1
- package/dist/ai-edits/table-template.js +49 -17
- package/dist/ai-edits/types.d.ts +45 -57
- package/dist/ai-suggestions/conflict.js +1 -4
- package/dist/ai-suggestions/text-positions.d.ts +8 -0
- package/dist/ai-suggestions/text-positions.js +27 -5
- package/dist/compare/__fixtures__/body-sequence.js +3 -4
- package/dist/compare/column-alignment.d.ts +13 -13
- package/dist/compare/column-alignment.js +78 -39
- package/dist/compare/compare.d.ts +4 -3
- package/dist/compare/compare.js +41 -5
- package/dist/compare/content-alignment.d.ts +95 -0
- package/dist/compare/content-alignment.js +1450 -0
- package/dist/compare/content-types.d.ts +100 -0
- package/dist/compare/content-types.js +0 -0
- package/dist/compare/content.d.ts +209 -0
- package/dist/compare/content.js +885 -0
- package/dist/compare/formatting.d.ts +5 -5
- package/dist/compare/formatting.js +6 -4
- package/dist/compare/plan.d.ts +6 -1
- package/dist/compare/plan.js +169 -567
- package/dist/compare/scenario.d.ts +2 -2
- package/dist/compare/types.d.ts +3 -3
- package/dist/compare/verification.d.ts +2 -2
- package/dist/compare/verification.js +25 -8
- package/dist/compat/eigenpal.d.ts +5 -3
- package/dist/compat/eigenpal.js +3 -2
- package/dist/controller/headerFooterEditorManager.js +14 -6
- package/dist/controller/hiddenEditorManager.d.ts +7 -2
- package/dist/controller/hiddenEditorManager.js +15 -6
- package/dist/controller/layoutPipeline.js +5 -3
- package/dist/controller/noteEditorManager.js +12 -7
- package/dist/display-list/build/headerFooterPrimitives.js +1 -0
- package/dist/display-list/build/storyPrimitives.d.ts +5 -2
- package/dist/display-list/build/storyPrimitives.js +60 -3
- package/dist/display-list/build/tablePrimitives.js +30 -0
- package/dist/display-list/primitives.d.ts +1 -1
- package/dist/document-operations.d.ts +6 -4
- package/dist/document-operations.js +77 -12
- package/dist/docx/blockContentParser.js +6 -27
- package/dist/docx/ensureParaIds.js +9 -4
- package/dist/docx/numberingParser.d.ts +3 -1
- package/dist/docx/numberingParser.js +4 -1
- package/dist/docx/paragraphParser.js +7 -6
- package/dist/docx/paragraphTraversal.js +30 -34
- package/dist/docx/rezip.d.ts +0 -8
- package/dist/docx/rezip.js +41 -16
- package/dist/docx/runParser.js +4 -2
- package/dist/docx/serializer/paragraphSerializer.js +2 -0
- package/dist/docx/serializer/runSerializer.js +5 -3
- package/dist/docx/serializer/trackedChangeAttributes.d.ts +3 -1
- package/dist/docx/serializer/trackedChangeAttributes.js +11 -1
- package/dist/docx/server/build.d.ts +1 -1
- package/dist/headless-layout.js +5 -4
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/internal/headlessRevisionResolution.d.ts +23 -0
- package/dist/internal/headlessRevisionResolution.js +155 -0
- package/dist/internal/headlessRevisionResolutionGuard.d.ts +12 -0
- package/dist/internal/headlessRevisionResolutionGuard.js +28 -0
- package/dist/internal/pageBreakRunDescendantIndex.d.ts +9 -0
- package/dist/internal/pageBreakRunDescendantIndex.js +27 -0
- package/dist/internal/pageBreakRunPartition.d.ts +26 -0
- package/dist/internal/pageBreakRunPartition.js +35 -0
- package/dist/internal/pageBreakRunSourceDescendantIndex.d.ts +15 -0
- package/dist/internal/pageBreakRunSourceDescendantIndex.js +126 -0
- package/dist/internal/sectionEndpointResolution.d.ts +32 -0
- package/dist/internal/sectionEndpointResolution.js +38 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +16 -4
- package/dist/layout-bridge/convert/headerFooterLayout.js +48 -5
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +379 -75
- package/dist/layout-bridge/dom/clickToPositionDom.d.ts +8 -1
- package/dist/layout-bridge/dom/clickToPositionDom.js +114 -40
- package/dist/layout-bridge/dom/textStreamDom.d.ts +24 -0
- package/dist/layout-bridge/dom/textStreamDom.js +75 -0
- package/dist/layout-bridge/headerFooterLayout.js +16 -30
- package/dist/layout-engine/index.js +12 -3
- package/dist/layout-engine/measure/tableFragmentBorderGeometry.d.ts +17 -0
- package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +24 -0
- package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
- package/dist/layout-engine/types.d.ts +7 -0
- package/dist/layout-painter/imageLayout.d.ts +1 -1
- package/dist/layout-painter/renderTable.js +20 -0
- package/dist/model.d.ts +1 -1
- package/dist/paged-layout/rangeProjection.js +6 -9
- package/dist/paged-layout/transactionDirtyRange.d.ts +3 -1
- package/dist/paged-layout/transactionDirtyRange.js +23 -1
- package/dist/prosemirror/attrs/index.d.ts +7 -3
- package/dist/prosemirror/attrs/index.js +113 -7
- package/dist/prosemirror/bookmarkBoundaryIntegrity.d.ts +9 -0
- package/dist/prosemirror/bookmarkBoundaryIntegrity.js +22 -0
- package/dist/prosemirror/commands/comments.d.ts +10 -1
- package/dist/prosemirror/commands/comments.js +190 -59
- package/dist/prosemirror/commands/formatPainter.d.ts +45 -10
- package/dist/prosemirror/commands/formatPainter.js +319 -63
- package/dist/prosemirror/commands/index.d.ts +2 -2
- package/dist/prosemirror/commands/pageBreak.d.ts +5 -2
- package/dist/prosemirror/commands/pageBreak.js +18 -36
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +7 -5
- package/dist/prosemirror/commands/propertyChangeScope.js +27 -18
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +7 -2
- package/dist/prosemirror/conversion/fromProseDoc.js +809 -209
- package/dist/prosemirror/conversion/runShadingMark.js +2 -2
- package/dist/prosemirror/conversion/toProseDoc.d.ts +26 -1
- package/dist/prosemirror/conversion/toProseDoc.js +416 -283
- package/dist/prosemirror/extensions/StarterKit.js +4 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +12 -8
- package/dist/prosemirror/extensions/features/ListExtension.js +95 -123
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +1 -1
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +27 -1
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +134 -30
- package/dist/prosemirror/extensions/features/pasteCleanup.js +9 -15
- package/dist/prosemirror/extensions/marks/BoldExtension.js +4 -3
- package/dist/prosemirror/extensions/marks/CharacterStyleExtension.d.ts +4 -7
- package/dist/prosemirror/extensions/marks/CharacterStyleExtension.js +5 -11
- package/dist/prosemirror/extensions/marks/FontSizeExtension.js +3 -3
- package/dist/prosemirror/extensions/marks/ItalicExtension.js +4 -3
- package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.d.ts +13 -0
- package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.js +39 -0
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts +7 -1
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +67 -21
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -1
- package/dist/prosemirror/extensions/marks/markUtils.js +88 -2
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +44 -3
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
- package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +15 -4
- package/dist/prosemirror/extensions/nodes/PageBreakExtension.d.ts +6 -0
- package/dist/prosemirror/extensions/nodes/PageBreakExtension.js +6 -0
- package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.d.ts +6 -0
- package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.js +36 -0
- package/dist/prosemirror/findReplaceSelection.js +8 -3
- package/dist/prosemirror/index.d.ts +3 -3
- package/dist/prosemirror/listMarker.d.ts +26 -1
- package/dist/prosemirror/listMarker.js +108 -14
- package/dist/prosemirror/pageBreakRunProjection.d.ts +28 -0
- package/dist/prosemirror/pageBreakRunProjection.js +47 -0
- package/dist/prosemirror/paraText.js +6 -0
- package/dist/prosemirror/paragraphSpacing.d.ts +29 -0
- package/dist/prosemirror/paragraphSpacing.js +125 -0
- package/dist/prosemirror/plugins/documentNumbering.d.ts +8 -0
- package/dist/prosemirror/plugins/documentNumbering.js +18 -0
- package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -2
- package/dist/prosemirror/plugins/pmTextScan.js +14 -4
- package/dist/prosemirror/plugins/suggestionMode.js +8 -10
- package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +16 -0
- package/dist/prosemirror/rebaseParagraphRunFormatting.js +61 -0
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +64 -0
- package/dist/prosemirror/runFormattingInlineCarriers.js +121 -0
- package/dist/prosemirror/runFormattingProvenance.d.ts +12 -0
- package/dist/prosemirror/runFormattingProvenance.js +48 -0
- package/dist/prosemirror/runFormattingReconciliation.d.ts +25 -0
- package/dist/prosemirror/runFormattingReconciliation.js +54 -0
- package/dist/prosemirror/runPropertyChangeResolution.d.ts +18 -0
- package/dist/prosemirror/runPropertyChangeResolution.js +18 -0
- package/dist/prosemirror/runStyleFormatting.d.ts +50 -0
- package/dist/prosemirror/runStyleFormatting.js +105 -0
- package/dist/prosemirror/schema/index.d.ts +3 -3
- package/dist/prosemirror/schema/marks.d.ts +82 -9
- package/dist/prosemirror/schema/marks.js +73 -1
- package/dist/prosemirror/schema/nodes.d.ts +18 -5
- package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +14 -2
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +44 -23
- package/dist/prosemirror/styles/styleResolver.d.ts +10 -13
- package/dist/prosemirror/styles/styleResolver.js +3 -3
- package/dist/prosemirror/styles/styleToggleCascade.d.ts +8 -1
- package/dist/prosemirror/styles/styleToggleCascade.js +24 -1
- package/dist/prosemirror/tableGridMutation.d.ts +20 -0
- package/dist/prosemirror/tableGridMutation.js +30 -0
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +31 -0
- package/dist/prosemirror/trackedRunInlineAtoms.js +39 -0
- package/dist/prosemirror/utils/visualLineNavigation.js +27 -31
- package/dist/prosemirror/validation.js +23 -4
- package/dist/render-dom/HeaderFooterSelectionOverlay.js +20 -20
- package/dist/render-dom/RenderedDomContext.js +17 -34
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/style-engine/styleEngine.d.ts +1 -1
- package/dist/utils/clipboard.js +1 -1
- package/dist/utils/findReplace.js +38 -21
- package/dist/version-comparison.d.ts +38 -44
- package/dist/version-comparison.js +270 -401
- package/package.json +3 -2
|
@@ -86,6 +86,13 @@ type CollapsedLineEdgeCaretTarget = {
|
|
|
86
86
|
* cannot decide which span owns a boundary.
|
|
87
87
|
*/
|
|
88
88
|
declare function findCollapsedLineEdgeCaretTarget(spans: readonly HTMLElement[], pmPos: number): CollapsedLineEdgeCaretTarget | null;
|
|
89
|
+
/**
|
|
90
|
+
* Map an unpainted inline position inside a paragraph to the nearest painted
|
|
91
|
+
* span boundary. Inline metadata and folded fields occupy ProseMirror
|
|
92
|
+
* positions without emitting glyphs; their carets belong at the adjacent text
|
|
93
|
+
* slot, not at the paragraph box's physical edge.
|
|
94
|
+
*/
|
|
95
|
+
declare function findParagraphGapCaretTarget(container: ParentNode, pmPos: number): CollapsedLineEdgeCaretTarget | null;
|
|
89
96
|
declare function getCaretPositionFromDom(container: HTMLElement, pmPos: number, overlayRect: DOMRect): DomCaretPosition | null;
|
|
90
97
|
//#endregion
|
|
91
|
-
export { CollapsedLineEdgeCaretGeometry, CollapsedLineEdgeCaretTarget, DomCaretPosition, DomSelectionRect, clickToPositionDom, findCollapsedLineEdgeCaretTarget, findPositionInSpan, getCaretPositionFromDom, getCollapsedLineEdgeCaretGeometry, getSelectionRectsFromDom };
|
|
98
|
+
export { CollapsedLineEdgeCaretGeometry, CollapsedLineEdgeCaretTarget, DomCaretPosition, DomSelectionRect, clickToPositionDom, findCollapsedLineEdgeCaretTarget, findParagraphGapCaretTarget, findPositionInSpan, getCaretPositionFromDom, getCollapsedLineEdgeCaretGeometry, getSelectionRectsFromDom };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { closestHtmlElement, findHtmlElement, htmlQueryAll, queryHtmlElement } from "../../utils/domGuards.js";
|
|
2
|
+
import { descendantTextNodes, logicalTextOffset, measurePaintedSpanRange, textBoundaryAt, totalTextLength } from "./textStreamDom.js";
|
|
2
3
|
//#region src/layout-bridge/dom/clickToPositionDom.ts
|
|
3
4
|
/**
|
|
4
5
|
* DOM-based Click-to-Position Mapping
|
|
@@ -57,28 +58,30 @@ function findPositionInSpan(spanEl, clientX, clientY) {
|
|
|
57
58
|
const rect = spanEl.getBoundingClientRect();
|
|
58
59
|
return clientX < (rect.left + rect.right) / 2 ? pmStart : pmEnd;
|
|
59
60
|
}
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
const textLength = text.length;
|
|
64
|
-
if (textLength === 0) return pmStart;
|
|
61
|
+
const textNodes = descendantTextNodes(spanEl);
|
|
62
|
+
if (textNodes.length === 0) return pmStart;
|
|
63
|
+
if (totalTextLength(textNodes) === 0) return pmStart;
|
|
65
64
|
const ownerDoc = spanEl.ownerDocument;
|
|
66
|
-
const native = caretOffsetFromPoint(ownerDoc,
|
|
65
|
+
const native = caretOffsetFromPoint(ownerDoc, textNodes, clientX, clientY);
|
|
67
66
|
if (native !== null) return pmStart + Math.min(native, pmEnd - pmStart);
|
|
68
67
|
const range = ownerDoc.createRange();
|
|
69
68
|
let bestOffset = 0;
|
|
70
69
|
let bestDistance = Number.POSITIVE_INFINITY;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
let charsProcessed = 0;
|
|
71
|
+
for (const text of textNodes) {
|
|
72
|
+
for (let i = 0; i < text.length; i++) {
|
|
73
|
+
range.setStart(text, i);
|
|
74
|
+
range.setEnd(text, i + 1);
|
|
75
|
+
const rect = range.getBoundingClientRect();
|
|
76
|
+
if (rect.width === 0 && rect.height === 0) continue;
|
|
77
|
+
const midpoint = rect.left + rect.width / 2;
|
|
78
|
+
const distance = Math.abs(clientX - midpoint);
|
|
79
|
+
if (distance < bestDistance) {
|
|
80
|
+
bestDistance = distance;
|
|
81
|
+
bestOffset = charsProcessed + (clientX < midpoint ? i : i + 1);
|
|
82
|
+
}
|
|
81
83
|
}
|
|
84
|
+
charsProcessed += text.length;
|
|
82
85
|
}
|
|
83
86
|
return pmStart + Math.min(bestOffset, pmEnd - pmStart);
|
|
84
87
|
}
|
|
@@ -89,15 +92,21 @@ function findPositionInSpan(spanEl, clientX, clientY) {
|
|
|
89
92
|
* a different node — callers fall back to a glyph-by-glyph
|
|
90
93
|
* scan in that case.
|
|
91
94
|
*/
|
|
92
|
-
function caretOffsetFromPoint(ownerDoc,
|
|
95
|
+
function caretOffsetFromPoint(ownerDoc, textNodes, clientX, clientY) {
|
|
93
96
|
const doc = ownerDoc;
|
|
94
97
|
if (typeof doc.caretPositionFromPoint === "function") {
|
|
95
98
|
const pos = doc.caretPositionFromPoint(clientX, clientY);
|
|
96
|
-
if (pos
|
|
99
|
+
if (pos) {
|
|
100
|
+
const offset = logicalTextOffset(textNodes, pos.offsetNode, pos.offset);
|
|
101
|
+
if (offset !== null) return offset;
|
|
102
|
+
}
|
|
97
103
|
}
|
|
98
104
|
if (typeof doc.caretRangeFromPoint === "function") {
|
|
99
105
|
const range = doc.caretRangeFromPoint(clientX, clientY);
|
|
100
|
-
if (range
|
|
106
|
+
if (range) {
|
|
107
|
+
const offset = logicalTextOffset(textNodes, range.startContainer, range.startOffset);
|
|
108
|
+
if (offset !== null) return offset;
|
|
109
|
+
}
|
|
101
110
|
}
|
|
102
111
|
return null;
|
|
103
112
|
}
|
|
@@ -200,22 +209,12 @@ function getSelectionRectsFromDom(container, from, to, overlayRect) {
|
|
|
200
209
|
const rects = [];
|
|
201
210
|
const spans = htmlQueryAll(container, ".layout-page-content span[data-pm-start][data-pm-end]");
|
|
202
211
|
for (const spanEl of spans) {
|
|
203
|
-
const
|
|
204
|
-
if (
|
|
205
|
-
const textNode = spanEl.firstChild;
|
|
206
|
-
if (!textNode || textNode.nodeType !== Node.TEXT_NODE) continue;
|
|
207
|
-
const text = textNode;
|
|
208
|
-
const ownerDoc = spanEl.ownerDocument;
|
|
209
|
-
const startChar = Math.max(0, from - pmStart);
|
|
210
|
-
const endChar = Math.min(text.length, to - pmStart);
|
|
211
|
-
if (startChar >= endChar) continue;
|
|
212
|
-
const range = ownerDoc.createRange();
|
|
213
|
-
range.setStart(text, startChar);
|
|
214
|
-
range.setEnd(text, endChar);
|
|
215
|
-
const clientRects = range.getClientRects();
|
|
212
|
+
const measurement = measurePaintedSpanRange(spanEl, from, to);
|
|
213
|
+
if (measurement.type === "outside") continue;
|
|
216
214
|
const pageEl = closestHtmlElement(spanEl, ".layout-page");
|
|
217
215
|
const pageIndex = pageEl ? Number(pageEl.dataset["pageNumber"] || 1) - 1 : 0;
|
|
218
|
-
|
|
216
|
+
const clientRects = measurement.type === "element" ? [measurement.rect] : measurement.rects;
|
|
217
|
+
for (const clientRect of clientRects) rects.push({
|
|
219
218
|
x: clientRect.left - overlayRect.left,
|
|
220
219
|
y: clientRect.top - overlayRect.top,
|
|
221
220
|
width: clientRect.width,
|
|
@@ -273,6 +272,70 @@ function findCollapsedLineEdgeCaretTarget(spans, pmPos) {
|
|
|
273
272
|
}
|
|
274
273
|
return null;
|
|
275
274
|
}
|
|
275
|
+
const getPaintedSpanCaretGeometry = (span, pmPos) => {
|
|
276
|
+
const collapsed = getCollapsedLineEdgeCaretGeometry(span, pmPos);
|
|
277
|
+
if (collapsed) return collapsed;
|
|
278
|
+
const spanRect = span.getBoundingClientRect();
|
|
279
|
+
const lineHeight = closestHtmlElement(span, ".layout-line")?.getBoundingClientRect().height || spanRect.height || 16;
|
|
280
|
+
if (span.classList.contains("layout-run-tab")) return {
|
|
281
|
+
left: pmPos >= Number(span.dataset["pmEnd"]) ? spanRect.right : spanRect.left,
|
|
282
|
+
top: spanRect.top,
|
|
283
|
+
height: lineHeight
|
|
284
|
+
};
|
|
285
|
+
const textNodes = descendantTextNodes(span);
|
|
286
|
+
if (textNodes.length === 0) return {
|
|
287
|
+
left: spanRect.left,
|
|
288
|
+
top: spanRect.top,
|
|
289
|
+
height: lineHeight
|
|
290
|
+
};
|
|
291
|
+
const pmStart = Number(span.dataset["pmStart"]);
|
|
292
|
+
const boundary = textBoundaryAt(textNodes, Math.min(Math.max(0, pmPos - pmStart), totalTextLength(textNodes)), "start");
|
|
293
|
+
if (!boundary) return {
|
|
294
|
+
left: spanRect.left,
|
|
295
|
+
top: spanRect.top,
|
|
296
|
+
height: lineHeight
|
|
297
|
+
};
|
|
298
|
+
const range = span.ownerDocument.createRange();
|
|
299
|
+
range.setStart(boundary.node, boundary.offset);
|
|
300
|
+
range.setEnd(boundary.node, boundary.offset);
|
|
301
|
+
const rangeRect = range.getBoundingClientRect();
|
|
302
|
+
return {
|
|
303
|
+
left: rangeRect.left,
|
|
304
|
+
top: rangeRect.top,
|
|
305
|
+
height: rangeRect.height || lineHeight
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
/**
|
|
309
|
+
* Map an unpainted inline position inside a paragraph to the nearest painted
|
|
310
|
+
* span boundary. Inline metadata and folded fields occupy ProseMirror
|
|
311
|
+
* positions without emitting glyphs; their carets belong at the adjacent text
|
|
312
|
+
* slot, not at the paragraph box's physical edge.
|
|
313
|
+
*/
|
|
314
|
+
function findParagraphGapCaretTarget(container, pmPos) {
|
|
315
|
+
let best;
|
|
316
|
+
for (const paragraph of htmlQueryAll(container, ".layout-page-content .layout-paragraph")) {
|
|
317
|
+
const paragraphStart = Number(paragraph.dataset["pmStart"]);
|
|
318
|
+
const paragraphEnd = Number(paragraph.dataset["pmEnd"]);
|
|
319
|
+
if (!(pmPos > paragraphStart && pmPos < paragraphEnd)) continue;
|
|
320
|
+
for (const span of htmlQueryAll(paragraph, "span[data-pm-start][data-pm-end]")) {
|
|
321
|
+
const spanStart = Number(span.dataset["pmStart"]);
|
|
322
|
+
const spanEnd = Number(span.dataset["pmEnd"]);
|
|
323
|
+
if (!Number.isFinite(spanStart) || !Number.isFinite(spanEnd)) continue;
|
|
324
|
+
const boundaryPos = pmPos < spanStart ? spanStart : spanEnd;
|
|
325
|
+
const distance = Math.abs(boundaryPos - pmPos);
|
|
326
|
+
if (!best || distance < best.distance) best = {
|
|
327
|
+
span,
|
|
328
|
+
boundaryPos,
|
|
329
|
+
distance
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (!best) return null;
|
|
334
|
+
return {
|
|
335
|
+
span: best.span,
|
|
336
|
+
geometry: getPaintedSpanCaretGeometry(best.span, best.boundaryPos)
|
|
337
|
+
};
|
|
338
|
+
}
|
|
276
339
|
function getCaretPositionFromDom(container, pmPos, overlayRect) {
|
|
277
340
|
const spans = htmlQueryAll(container, ".layout-page-content span[data-pm-start][data-pm-end]");
|
|
278
341
|
const collapsedTarget = findCollapsedLineEdgeCaretTarget(spans, pmPos);
|
|
@@ -306,8 +369,8 @@ function getCaretPositionFromDom(container, pmPos, overlayRect) {
|
|
|
306
369
|
continue;
|
|
307
370
|
}
|
|
308
371
|
if (pmPos >= pmStart && pmPos <= pmEnd) {
|
|
309
|
-
const
|
|
310
|
-
if (
|
|
372
|
+
const textNodes = descendantTextNodes(spanEl);
|
|
373
|
+
if (textNodes.length === 0) {
|
|
311
374
|
const spanRect = spanEl.getBoundingClientRect();
|
|
312
375
|
const pageEl = closestHtmlElement(spanEl, ".layout-page");
|
|
313
376
|
const pageIndex = pageEl ? Number(pageEl.dataset["pageNumber"] || 1) - 1 : 0;
|
|
@@ -320,11 +383,11 @@ function getCaretPositionFromDom(container, pmPos, overlayRect) {
|
|
|
320
383
|
pageIndex
|
|
321
384
|
};
|
|
322
385
|
}
|
|
323
|
-
const
|
|
324
|
-
|
|
386
|
+
const boundary = textBoundaryAt(textNodes, Math.min(pmPos - pmStart, totalTextLength(textNodes)), "start");
|
|
387
|
+
if (!boundary) continue;
|
|
325
388
|
const range = spanEl.ownerDocument.createRange();
|
|
326
|
-
range.setStart(
|
|
327
|
-
range.setEnd(
|
|
389
|
+
range.setStart(boundary.node, boundary.offset);
|
|
390
|
+
range.setEnd(boundary.node, boundary.offset);
|
|
328
391
|
const rangeRect = range.getBoundingClientRect();
|
|
329
392
|
const pageEl = closestHtmlElement(spanEl, ".layout-page");
|
|
330
393
|
const pageIndex = pageEl ? Number(pageEl.dataset["pageNumber"] || 1) - 1 : 0;
|
|
@@ -338,6 +401,17 @@ function getCaretPositionFromDom(container, pmPos, overlayRect) {
|
|
|
338
401
|
};
|
|
339
402
|
}
|
|
340
403
|
}
|
|
404
|
+
const gapTarget = findParagraphGapCaretTarget(container, pmPos);
|
|
405
|
+
if (gapTarget) {
|
|
406
|
+
const pageEl = closestHtmlElement(gapTarget.span, ".layout-page");
|
|
407
|
+
const pageIndex = pageEl ? Number(pageEl.dataset["pageNumber"] || 1) - 1 : 0;
|
|
408
|
+
return {
|
|
409
|
+
x: gapTarget.geometry.left - overlayRect.left,
|
|
410
|
+
y: gapTarget.geometry.top - overlayRect.top,
|
|
411
|
+
height: gapTarget.geometry.height,
|
|
412
|
+
pageIndex
|
|
413
|
+
};
|
|
414
|
+
}
|
|
341
415
|
const paragraphs = htmlQueryAll(container, ".layout-paragraph");
|
|
342
416
|
for (const pEl of paragraphs) {
|
|
343
417
|
const pStart = Number(pEl.dataset["pmStart"]);
|
|
@@ -360,4 +434,4 @@ function getCaretPositionFromDom(container, pmPos, overlayRect) {
|
|
|
360
434
|
return null;
|
|
361
435
|
}
|
|
362
436
|
//#endregion
|
|
363
|
-
export { clickToPositionDom, findCollapsedLineEdgeCaretTarget, findPositionInSpan, getCaretPositionFromDom, getCollapsedLineEdgeCaretGeometry, getSelectionRectsFromDom };
|
|
437
|
+
export { clickToPositionDom, findCollapsedLineEdgeCaretTarget, findParagraphGapCaretTarget, findPositionInSpan, getCaretPositionFromDom, getCollapsedLineEdgeCaretGeometry, getSelectionRectsFromDom };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/layout-bridge/dom/textStreamDom.d.ts
|
|
2
|
+
type TextBoundary = {
|
|
3
|
+
node: Text;
|
|
4
|
+
offset: number;
|
|
5
|
+
};
|
|
6
|
+
type PaintedSpanRangeMeasurement = {
|
|
7
|
+
type: "outside";
|
|
8
|
+
} | {
|
|
9
|
+
type: "element";
|
|
10
|
+
rect: DOMRect;
|
|
11
|
+
} | {
|
|
12
|
+
type: "text";
|
|
13
|
+
rects: DOMRect[];
|
|
14
|
+
};
|
|
15
|
+
/** Treat every descendant text node as one logical character stream. */
|
|
16
|
+
declare const descendantTextNodes: (root: Node) => Text[];
|
|
17
|
+
declare const totalTextLength: (nodes: readonly Text[]) => number;
|
|
18
|
+
declare const textBoundaryAt: (nodes: readonly Text[], offset: number, edge: "start" | "end") => TextBoundary | null;
|
|
19
|
+
declare const logicalTextOffset: (nodes: readonly Text[], target: Node, offset: number) => number | null;
|
|
20
|
+
declare const createTextStreamRange: (element: HTMLElement, startOffset: number, endOffset: number) => Range | null;
|
|
21
|
+
/** Measure the intersecting portion of one painted position span. */
|
|
22
|
+
declare const measurePaintedSpanRange: (element: HTMLElement, from: number, to: number) => PaintedSpanRangeMeasurement;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { PaintedSpanRangeMeasurement, TextBoundary, createTextStreamRange, descendantTextNodes, logicalTextOffset, measurePaintedSpanRange, textBoundaryAt, totalTextLength };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//#region src/layout-bridge/dom/textStreamDom.ts
|
|
2
|
+
const TEXT_NODE_TYPE = 3;
|
|
3
|
+
const isTextNode = (node) => node.nodeType === TEXT_NODE_TYPE;
|
|
4
|
+
/** Treat every descendant text node as one logical character stream. */
|
|
5
|
+
const descendantTextNodes = (root) => {
|
|
6
|
+
const nodes = [];
|
|
7
|
+
const visit = (node) => {
|
|
8
|
+
if (isTextNode(node)) {
|
|
9
|
+
nodes.push(node);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
for (const child of node.childNodes) visit(child);
|
|
13
|
+
};
|
|
14
|
+
visit(root);
|
|
15
|
+
return nodes;
|
|
16
|
+
};
|
|
17
|
+
const totalTextLength = (nodes) => {
|
|
18
|
+
let length = 0;
|
|
19
|
+
for (const node of nodes) length += node.length;
|
|
20
|
+
return length;
|
|
21
|
+
};
|
|
22
|
+
const textBoundaryAt = (nodes, offset, edge) => {
|
|
23
|
+
let consumed = 0;
|
|
24
|
+
for (const [index, node] of nodes.entries()) {
|
|
25
|
+
const next = consumed + node.length;
|
|
26
|
+
if (offset < next || edge === "end" && offset === next || index === nodes.length - 1) return {
|
|
27
|
+
node,
|
|
28
|
+
offset: Math.max(0, Math.min(node.length, offset - consumed))
|
|
29
|
+
};
|
|
30
|
+
consumed = next;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
const logicalTextOffset = (nodes, target, offset) => {
|
|
35
|
+
let consumed = 0;
|
|
36
|
+
for (const text of nodes) {
|
|
37
|
+
if (text === target) return consumed + Math.max(0, Math.min(text.length, offset));
|
|
38
|
+
consumed += text.length;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
};
|
|
42
|
+
const createTextStreamRange = (element, startOffset, endOffset) => {
|
|
43
|
+
const nodes = descendantTextNodes(element);
|
|
44
|
+
const length = totalTextLength(nodes);
|
|
45
|
+
const boundedStart = Math.max(0, Math.min(length, startOffset));
|
|
46
|
+
const boundedEnd = Math.max(0, Math.min(length, endOffset));
|
|
47
|
+
const start = textBoundaryAt(nodes, boundedStart, "start");
|
|
48
|
+
const end = textBoundaryAt(nodes, boundedEnd, boundedStart === boundedEnd ? "start" : "end");
|
|
49
|
+
if (!start || !end) return null;
|
|
50
|
+
const range = element.ownerDocument.createRange();
|
|
51
|
+
range.setStart(start.node, start.offset);
|
|
52
|
+
range.setEnd(end.node, end.offset);
|
|
53
|
+
return range;
|
|
54
|
+
};
|
|
55
|
+
/** Measure the intersecting portion of one painted position span. */
|
|
56
|
+
const measurePaintedSpanRange = (element, from, to) => {
|
|
57
|
+
const pmStart = Number(element.dataset["pmStart"]);
|
|
58
|
+
const pmEnd = Number(element.dataset["pmEnd"]);
|
|
59
|
+
if (!Number.isFinite(pmStart) || !Number.isFinite(pmEnd) || pmEnd <= from || pmStart >= to) return { type: "outside" };
|
|
60
|
+
if (element.classList.contains("layout-run-tab")) return {
|
|
61
|
+
type: "element",
|
|
62
|
+
rect: element.getBoundingClientRect()
|
|
63
|
+
};
|
|
64
|
+
const textNodes = descendantTextNodes(element);
|
|
65
|
+
const startOffset = Math.max(0, from - pmStart);
|
|
66
|
+
const endOffset = Math.min(totalTextLength(textNodes), to - pmStart);
|
|
67
|
+
if (startOffset >= endOffset) return { type: "outside" };
|
|
68
|
+
const range = createTextStreamRange(element, startOffset, endOffset);
|
|
69
|
+
return range ? {
|
|
70
|
+
type: "text",
|
|
71
|
+
rects: Array.from(range.getClientRects())
|
|
72
|
+
} : { type: "outside" };
|
|
73
|
+
};
|
|
74
|
+
//#endregion
|
|
75
|
+
export { createTextStreamRange, descendantTextNodes, logicalTextOffset, measurePaintedSpanRange, textBoundaryAt, totalTextLength };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getCollapsedLineEdgeCaretGeometry } from "./dom/clickToPositionDom.js";
|
|
2
|
+
import { createTextStreamRange, descendantTextNodes, totalTextLength } from "./dom/textStreamDom.js";
|
|
2
3
|
//#region src/layout-bridge/headerFooterLayout.ts
|
|
3
4
|
const hfDomCache = {
|
|
4
5
|
header: null,
|
|
@@ -75,27 +76,15 @@ function computeHfCaretRectFromView(view, section, doc = globalThis.document) {
|
|
|
75
76
|
left: collapsedGeometry.left,
|
|
76
77
|
height: collapsedGeometry.height
|
|
77
78
|
};
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
range.setEnd(textNode, remaining);
|
|
88
|
-
const rect = range.getClientRects()[0] ?? range.getBoundingClientRect();
|
|
89
|
-
if (rect && rect.height > 0) return {
|
|
90
|
-
top: rect.top,
|
|
91
|
-
left: rect.left,
|
|
92
|
-
height: rect.height
|
|
93
|
-
};
|
|
94
|
-
} catch {}
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
remaining -= len;
|
|
98
|
-
textNode = walker.nextNode();
|
|
79
|
+
const offset = Math.min(pmPos - start, totalTextLength(descendantTextNodes(span)));
|
|
80
|
+
const range = createTextStreamRange(span, offset, offset);
|
|
81
|
+
if (range) {
|
|
82
|
+
const rect = range.getClientRects()[0] ?? range.getBoundingClientRect();
|
|
83
|
+
if (rect && rect.height > 0) return {
|
|
84
|
+
top: rect.top,
|
|
85
|
+
left: rect.left,
|
|
86
|
+
height: rect.height
|
|
87
|
+
};
|
|
99
88
|
}
|
|
100
89
|
const spanRect = span.getBoundingClientRect();
|
|
101
90
|
const ratio = (pmPos - start) / Math.max(1, end - start);
|
|
@@ -184,7 +173,7 @@ function computeHfSelectionRectsFromView(view, section, doc = globalThis.documen
|
|
|
184
173
|
const out = [];
|
|
185
174
|
const snapshot = getHfDomSnapshot(section, doc);
|
|
186
175
|
if (!snapshot) return out;
|
|
187
|
-
const {
|
|
176
|
+
const { spans } = snapshot;
|
|
188
177
|
for (const spanEl of spans) {
|
|
189
178
|
const pmStart = Number(spanEl.dataset["pmStart"]);
|
|
190
179
|
const pmEnd = Number(spanEl.dataset["pmEnd"]);
|
|
@@ -200,16 +189,13 @@ function computeHfSelectionRectsFromView(view, section, doc = globalThis.documen
|
|
|
200
189
|
});
|
|
201
190
|
continue;
|
|
202
191
|
}
|
|
203
|
-
|
|
204
|
-
if (
|
|
205
|
-
else if (spanEl.firstChild?.nodeType === Node.ELEMENT_NODE && spanEl.firstChild.tagName === "A" && spanEl.firstChild.firstChild?.nodeType === Node.TEXT_NODE) textNode = spanEl.firstChild.firstChild;
|
|
206
|
-
if (!textNode) continue;
|
|
192
|
+
const textNodes = descendantTextNodes(spanEl);
|
|
193
|
+
if (textNodes.length === 0) continue;
|
|
207
194
|
const startChar = Math.max(0, from - pmStart);
|
|
208
|
-
const endChar = Math.min(
|
|
195
|
+
const endChar = Math.min(totalTextLength(textNodes), to - pmStart);
|
|
209
196
|
if (startChar >= endChar) continue;
|
|
210
|
-
const range =
|
|
211
|
-
range
|
|
212
|
-
range.setEnd(textNode, endChar);
|
|
197
|
+
const range = createTextStreamRange(spanEl, startChar, endChar);
|
|
198
|
+
if (!range) continue;
|
|
213
199
|
for (const rect of Array.from(range.getClientRects())) out.push({
|
|
214
200
|
top: rect.top,
|
|
215
201
|
left: rect.left,
|
|
@@ -894,7 +894,7 @@ function layoutTextBox(block, measure, { paginator, sectionMarginTop, sectionPag
|
|
|
894
894
|
kind: "textBox",
|
|
895
895
|
blockId: block.id,
|
|
896
896
|
x,
|
|
897
|
-
y: state.topMargin + bandTop,
|
|
897
|
+
y: block.position?.vertical?.relativeTo === "page" ? sectionMarginTop + bandTop : state.topMargin + bandTop,
|
|
898
898
|
width: measure.width,
|
|
899
899
|
height: measure.height,
|
|
900
900
|
isPositioned: true,
|
|
@@ -916,12 +916,21 @@ function layoutTextBox(block, measure, { paginator, sectionMarginTop, sectionPag
|
|
|
916
916
|
const vertical = block.position.vertical;
|
|
917
917
|
const anchorBlockId = readTextBoxAnchorBlockId(block);
|
|
918
918
|
const anchorParagraph = vertical?.relativeTo === "paragraph" && typeof anchorBlockId === "string" ? state.page.fragments.find((fragment) => fragment.kind === "paragraph" && fragment.blockId === anchorBlockId) : void 0;
|
|
919
|
-
const
|
|
919
|
+
const bandGeometry = {
|
|
920
920
|
pageHeight: sectionPageHeight,
|
|
921
921
|
marginTop: sectionMarginTop,
|
|
922
922
|
marginBottom: sectionMarginBottom,
|
|
923
923
|
boxHeight: measure.height
|
|
924
|
-
}
|
|
924
|
+
};
|
|
925
|
+
let y;
|
|
926
|
+
if (vertical?.relativeTo === "page") y = sectionMarginTop + bandTopContentY(vertical, bandGeometry);
|
|
927
|
+
else if (isPageFrameRelativeAnchor(vertical?.relativeTo)) y = state.topMargin + bandTopContentY(vertical, {
|
|
928
|
+
pageHeight: sectionPageHeight,
|
|
929
|
+
marginTop: sectionMarginTop,
|
|
930
|
+
marginBottom: sectionMarginBottom,
|
|
931
|
+
boxHeight: measure.height
|
|
932
|
+
});
|
|
933
|
+
else y = (anchorParagraph?.y ?? state.cursorY) + emuToPixels(vertical?.posOffset ?? 0);
|
|
925
934
|
const fragment = {
|
|
926
935
|
kind: "textBox",
|
|
927
936
|
blockId: block.id,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CellBorderSpec, TableBlock, TableFragment, TableMeasure } from "../types.js";
|
|
2
|
+
import { TableCellPlacements } from "./tableCellGrid.js";
|
|
3
|
+
//#region src/layout-engine/measure/tableFragmentBorderGeometry.d.ts
|
|
4
|
+
type TableFragmentBottomBorder = {
|
|
5
|
+
readonly left: number;
|
|
6
|
+
readonly width: number;
|
|
7
|
+
readonly border: CellBorderSpec;
|
|
8
|
+
};
|
|
9
|
+
type TableFragmentBottomBordersOptions = {
|
|
10
|
+
readonly fragment: TableFragment;
|
|
11
|
+
readonly block: TableBlock;
|
|
12
|
+
readonly measure: TableMeasure;
|
|
13
|
+
readonly placements?: TableCellPlacements;
|
|
14
|
+
};
|
|
15
|
+
declare const tableFragmentBottomBorders: ({ fragment, block, measure, placements: suppliedPlacements }: TableFragmentBottomBordersOptions) => readonly TableFragmentBottomBorder[];
|
|
16
|
+
//#endregion
|
|
17
|
+
export { tableFragmentBottomBorders };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { buildTableCellGrid, buildTableCellPlacements } from "./tableCellGrid.js";
|
|
2
|
+
//#region src/layout-engine/measure/tableFragmentBorderGeometry.ts
|
|
3
|
+
const isVisible = (border) => border !== void 0 && border.style !== "none" && border.style !== "nil";
|
|
4
|
+
const tableFragmentBottomBorders = ({ fragment, block, measure, placements: suppliedPlacements }) => {
|
|
5
|
+
if (fragment.continuesOnNext !== true || fragment.bottomClip === void 0) return [];
|
|
6
|
+
const splitRow = block.rows.at(fragment.toRow - 1);
|
|
7
|
+
if (!splitRow) return [];
|
|
8
|
+
const placements = suppliedPlacements ?? buildTableCellPlacements({
|
|
9
|
+
grid: buildTableCellGrid(block.rows, measure.columnWidths.length),
|
|
10
|
+
columnWidths: measure.columnWidths,
|
|
11
|
+
bidi: block.bidi === true
|
|
12
|
+
});
|
|
13
|
+
return splitRow.cells.flatMap((cell) => {
|
|
14
|
+
const placement = placements.get(cell);
|
|
15
|
+
const border = cell.borders?.bottom;
|
|
16
|
+
return placement && isVisible(border) ? [{
|
|
17
|
+
left: placement.left,
|
|
18
|
+
width: placement.width,
|
|
19
|
+
border
|
|
20
|
+
}] : [];
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { tableFragmentBottomBorders };
|
|
@@ -34,7 +34,7 @@ const reconcileBreakBeforeBlock = ({ state, block, previousBlock, page, blocksBy
|
|
|
34
34
|
state
|
|
35
35
|
};
|
|
36
36
|
const markerAlreadySatisfied = pageStartsWithPreviousParagraphContinuation(page, previousBlock) || state.type === "pageAdvance" && state.reason === "authoredBoundary" || state.type === "pageAdvance" && state.reason === "reflowBoundary" || state.type === "pageAdvance" && isPaginationEmptyParagraph(block) || state.type === "pageAdvance" && (continuesNumberedSequence(previousBlock, block) || continuesTabbedParagraphSequence(previousBlock, block));
|
|
37
|
-
const forcePageBreak = (renderedBreakNeedsSnap ||
|
|
37
|
+
const forcePageBreak = (renderedBreakNeedsSnap || (block.attrs?.spacing?.before ?? 0) > 0 && block.attrs?.keepNext !== true && previousBlock?.kind !== "sectionBreak" || previousBlock?.kind === "table") && !markerAlreadySatisfied && pageHasVisibleBodyContent(page, blocksById);
|
|
38
38
|
const followsAuthoredPageBreak = previousBlock?.kind === "pageBreak";
|
|
39
39
|
const followsSectionBreak = previousBlock?.kind === "sectionBreak";
|
|
40
40
|
const followsPageBreakingSection = followsSectionBreak && previousBlock.type !== "continuous";
|
|
@@ -748,6 +748,13 @@ type PageHeaderFooterRefs = {
|
|
|
748
748
|
type PageBreakBlock = {
|
|
749
749
|
kind: "pageBreak";
|
|
750
750
|
id: BlockId;
|
|
751
|
+
/** Revision metadata when the structural boundary is newly authored. */
|
|
752
|
+
isInsertion?: boolean;
|
|
753
|
+
changeAuthor?: string;
|
|
754
|
+
changeDate?: string;
|
|
755
|
+
changeRevisionId?: number;
|
|
756
|
+
isSuggestion?: boolean;
|
|
757
|
+
suggestionId?: string;
|
|
751
758
|
pmStart?: number;
|
|
752
759
|
pmEnd?: number;
|
|
753
760
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FlowBlock, Measure } from "../layout-engine/types.js";
|
|
2
1
|
import { ImageAttrs } from "../prosemirror/schema/nodes.js";
|
|
2
|
+
import { FlowBlock, Measure } from "../layout-engine/types.js";
|
|
3
3
|
import { BlockLookup } from "./index.js";
|
|
4
4
|
import { WrapType } from "../types/wrap.js";
|
|
5
5
|
//#region src/layout-painter/imageLayout.d.ts
|
|
@@ -2,6 +2,7 @@ import { measureParagraph } from "../layout-engine/measure/measureParagraph.js";
|
|
|
2
2
|
import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "../layout-engine/measure/tableCellFloating.js";
|
|
3
3
|
import { createTableCellFlowState, finishTableCellFlow, placeTableCellBlock } from "../layout-engine/measure/tableCellFlow.js";
|
|
4
4
|
import { buildTableCellGrid, buildTableCellPlacements, getSourceCellAt } from "../layout-engine/measure/tableCellGrid.js";
|
|
5
|
+
import { tableFragmentBottomBorders } from "../layout-engine/measure/tableFragmentBorderGeometry.js";
|
|
5
6
|
import { resolveTableInlineOffset, resolveTableInlinePlacement } from "../layout-engine/measure/tableInlinePlacement.js";
|
|
6
7
|
import { isFloatingImageRun, isFloatingTextBoxBlock, resolveTableCellPadding, tableColumnsArePinned } from "../layout-engine/types.js";
|
|
7
8
|
import { applySanitizedImageSrc } from "../utils/sanitizeImageSrc.js";
|
|
@@ -727,6 +728,25 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
|
|
|
727
728
|
contentParent.append(rowEl);
|
|
728
729
|
y += rowMeasure.height;
|
|
729
730
|
}
|
|
731
|
+
for (const { left, width, border } of tableFragmentBottomBorders({
|
|
732
|
+
fragment,
|
|
733
|
+
block,
|
|
734
|
+
measure,
|
|
735
|
+
placements: cellPlacements
|
|
736
|
+
})) {
|
|
737
|
+
const borderEl = doc.createElement("div");
|
|
738
|
+
borderEl.className = CELL_BOTTOM_BORDER_CLASS;
|
|
739
|
+
borderEl.style.position = "absolute";
|
|
740
|
+
borderEl.style.left = `${left}px`;
|
|
741
|
+
borderEl.style.top = "0";
|
|
742
|
+
borderEl.style.width = `${width}px`;
|
|
743
|
+
borderEl.style.height = `${fragment.height}px`;
|
|
744
|
+
borderEl.style.boxSizing = "border-box";
|
|
745
|
+
borderEl.style.pointerEvents = "none";
|
|
746
|
+
borderEl.style.zIndex = "1";
|
|
747
|
+
applyBorder(borderEl, "bottom", border);
|
|
748
|
+
tableEl.append(borderEl);
|
|
749
|
+
}
|
|
730
750
|
let handleY = 0;
|
|
731
751
|
for (let rowIdx = fragment.fromRow; rowIdx < fragment.toRow; rowIdx++) {
|
|
732
752
|
if (block.rows[rowIdx]?.hidden) continue;
|
package/dist/model.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { document_d_exports } from "./types/document.js";
|
|
2
|
-
import { DeriveBlockIdInput, FolioBlockId, currentFolioBlockId, deriveBlankBlockId, deriveBlockId, getFolioParaIdFromBlockId, getSequentialFolioBlockIdIndex, isBlankFolioBlockId, isFolioBlockId, isSequentialFolioBlockId } from "./types/block-id.js";
|
|
3
2
|
import { BORDER_STYLE_VALUES, CONDITIONAL_STYLE_TYPE_VALUES, EMPHASIS_MARK_VALUES, FIELD_TYPE_VALUES, FLOATING_TABLE_X_SPEC_VALUES, FLOATING_TABLE_Y_SPEC_VALUES, FONT_HINT_VALUES, FONT_THEME_VALUES, FRAME_WRAP_VALUES, FRAME_X_ALIGN_VALUES, FRAME_Y_ALIGN_VALUES, HIGHLIGHT_COLOR_VALUES, IMAGE_HORIZONTAL_ALIGNMENT_VALUES, IMAGE_HORIZONTAL_RELATIVE_TO_VALUES, IMAGE_VERTICAL_ALIGNMENT_VALUES, IMAGE_VERTICAL_RELATIVE_TO_VALUES, IMAGE_WRAP_TEXT_VALUES, IMAGE_WRAP_TYPE_VALUES, LEVEL_SUFFIX_VALUES, LINE_SPACING_RULE_VALUES, NUMBER_FORMAT_VALUES, OUTLINE_STYLE_ATTR_VALUES, OUTLINE_STYLE_CSS_ALIASES, OUTLINE_STYLE_CSS_ALIAS_VALUES, OutlineStyleAttr, OutlineStyleCssAlias, PARAGRAPH_ALIGNMENT_VALUES, POSITIONAL_TAB_ALIGNMENT_VALUES, POSITIONAL_TAB_LEADER_VALUES, POSITIONAL_TAB_RELATIVE_TO_VALUES, SDT_LOCK_VALUES, SDT_TYPE_VALUES, SHADING_PATTERN_VALUES, SHAPE_OUTLINE_STYLE_VALUES, SHAPE_TEXT_ANCHOR_VALUES, SHAPE_TYPE_VALUES, STYLE_TYPE_VALUES, TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_CELL_VERTICAL_ALIGNMENT_VALUES, TABLE_JUSTIFICATION_VALUES, TABLE_ROW_HEIGHT_RULE_VALUES, TABLE_WIDTH_TYPE_VALUES, TAB_LEADER_VALUES, TAB_STOP_ALIGNMENT_VALUES, TEXT_EFFECT_VALUES, THEME_COLOR_SLOT_VALUES, UNDERLINE_STYLE_VALUES, normalizeShapeTextAnchor } from "./types/documentEnumValues.js";
|
|
3
|
+
import { DeriveBlockIdInput, FolioBlockId, currentFolioBlockId, deriveBlankBlockId, deriveBlockId, getFolioParaIdFromBlockId, getSequentialFolioBlockIdIndex, isBlankFolioBlockId, isFolioBlockId, isSequentialFolioBlockId } from "./types/block-id.js";
|
|
4
4
|
import { types_d_exports } from "./layout-engine/types.js";
|
|
5
5
|
import { measureTypes_d_exports } from "./layout-engine/measure/measureTypes.js";
|
|
6
6
|
export * from "@stll/docx-core/model";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { findBodyPmSpans } from "../layout-bridge/dom/findBodyPmSpans.js";
|
|
2
|
+
import { createTextStreamRange, descendantTextNodes, totalTextLength } from "../layout-bridge/dom/textStreamDom.js";
|
|
2
3
|
//#region src/paged-layout/rangeProjection.ts
|
|
3
4
|
/**
|
|
4
5
|
* Shared range → paged-canvas rectangle projection.
|
|
@@ -12,7 +13,6 @@ import { findBodyPmSpans } from "../layout-bridge/dom/findBodyPmSpans.js";
|
|
|
12
13
|
* projection only for ranges whose DOM spans aren't mounted yet
|
|
13
14
|
* (initial paint, off-screen pages).
|
|
14
15
|
*/
|
|
15
|
-
const isTextNode = (node) => node?.nodeType === Node.TEXT_NODE;
|
|
16
16
|
const pageIndexOf = (el) => {
|
|
17
17
|
const page = el.closest(".layout-page");
|
|
18
18
|
const raw = page instanceof HTMLElement ? page.dataset["pageNumber"] : void 0;
|
|
@@ -35,16 +35,13 @@ const domRectsForRange = (pmSpans, overlayRect, zoom, from, to) => {
|
|
|
35
35
|
});
|
|
36
36
|
continue;
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
else if (spanEl.firstChild instanceof HTMLElement && spanEl.firstChild.tagName === "A" && isTextNode(spanEl.firstChild.firstChild)) textNode = spanEl.firstChild.firstChild;
|
|
41
|
-
if (!textNode) continue;
|
|
38
|
+
const textNodes = descendantTextNodes(spanEl);
|
|
39
|
+
if (textNodes.length === 0) continue;
|
|
42
40
|
const startChar = Math.max(0, from - pmStart);
|
|
43
|
-
const endChar = Math.min(
|
|
41
|
+
const endChar = Math.min(totalTextLength(textNodes), to - pmStart);
|
|
44
42
|
if (startChar >= endChar) continue;
|
|
45
|
-
const range = spanEl
|
|
46
|
-
range
|
|
47
|
-
range.setEnd(textNode, endChar);
|
|
43
|
+
const range = createTextStreamRange(spanEl, startChar, endChar);
|
|
44
|
+
if (!range) continue;
|
|
48
45
|
const pageIndex = pageIndexOf(spanEl);
|
|
49
46
|
for (const rect of Array.from(range.getClientRects())) rects.push({
|
|
50
47
|
x: (rect.left - overlayRect.left) / zoom,
|
|
@@ -2,5 +2,7 @@ import { DirtyRange } from "./incrementalMeasure.js";
|
|
|
2
2
|
import { Transaction } from "prosemirror-state";
|
|
3
3
|
//#region src/paged-layout/transactionDirtyRange.d.ts
|
|
4
4
|
declare function getTransactionDirtyRange(transaction: Transaction): DirtyRange | null;
|
|
5
|
+
/** Collect every transaction in an applied batch in the final document's coordinates. */
|
|
6
|
+
declare function getTransactionsDirtyRange(transactions: readonly Transaction[]): DirtyRange | null;
|
|
5
7
|
//#endregion
|
|
6
|
-
export { getTransactionDirtyRange };
|
|
8
|
+
export { getTransactionDirtyRange, getTransactionsDirtyRange };
|