@beyondwork/docx-react-component 1.0.36 → 1.0.38
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 +103 -13
- package/package.json +1 -1
- package/src/api/package-version.ts +13 -0
- package/src/api/public-types.ts +402 -1
- package/src/core/commands/index.ts +18 -1
- package/src/core/commands/section-layout-commands.ts +58 -0
- package/src/core/commands/table-grid.ts +431 -0
- package/src/core/commands/table-structure-commands.ts +815 -55
- package/src/core/selection/mapping.ts +6 -0
- package/src/io/docx-session.ts +24 -9
- package/src/io/export/build-app-properties-xml.ts +88 -0
- package/src/io/export/serialize-comments.ts +6 -1
- package/src/io/export/serialize-footnotes.ts +10 -9
- package/src/io/export/serialize-headers-footers.ts +11 -10
- package/src/io/export/serialize-main-document.ts +328 -50
- package/src/io/export/serialize-numbering.ts +114 -24
- package/src/io/export/serialize-tables.ts +87 -11
- package/src/io/export/table-properties-xml.ts +174 -20
- package/src/io/export/twip.ts +66 -0
- package/src/io/normalize/normalize-text.ts +20 -0
- package/src/io/ooxml/parse-footnotes.ts +62 -1
- package/src/io/ooxml/parse-headers-footers.ts +62 -1
- package/src/io/ooxml/parse-main-document.ts +158 -1
- package/src/io/ooxml/parse-tables.ts +249 -0
- package/src/legal/bookmarks.ts +78 -0
- package/src/model/canonical-document.ts +45 -0
- package/src/review/store/scope-tag-diff.ts +130 -0
- package/src/runtime/document-layout.ts +4 -2
- package/src/runtime/document-navigation.ts +2 -306
- package/src/runtime/document-runtime.ts +287 -11
- package/src/runtime/layout/default-page-format.ts +96 -0
- package/src/runtime/layout/docx-font-loader.ts +143 -0
- package/src/runtime/layout/index.ts +233 -0
- package/src/runtime/layout/inert-layout-facet.ts +59 -0
- package/src/runtime/layout/layout-engine-instance.ts +628 -0
- package/src/runtime/layout/layout-invalidation.ts +257 -0
- package/src/runtime/layout/layout-measurement-provider.ts +175 -0
- package/src/runtime/layout/margin-preset-catalog.ts +178 -0
- package/src/runtime/layout/measurement-backend-canvas.ts +307 -0
- package/src/runtime/layout/measurement-backend-empirical.ts +208 -0
- package/src/runtime/layout/page-format-catalog.ts +233 -0
- package/src/runtime/layout/page-fragment-mapper.ts +179 -0
- package/src/runtime/layout/page-graph.ts +452 -0
- package/src/runtime/layout/page-layout-snapshot-adapter.ts +70 -0
- package/src/runtime/layout/page-story-resolver.ts +195 -0
- package/src/runtime/layout/paginated-layout-engine.ts +921 -0
- package/src/runtime/layout/project-block-fragments.ts +91 -0
- package/src/runtime/layout/public-facet.ts +1398 -0
- package/src/runtime/layout/resolved-formatting-document.ts +317 -0
- package/src/runtime/layout/resolved-formatting-state.ts +430 -0
- package/src/runtime/layout/table-render-plan.ts +229 -0
- package/src/runtime/render/block-fragment-projection.ts +35 -0
- package/src/runtime/render/decoration-resolver.ts +189 -0
- package/src/runtime/render/index.ts +57 -0
- package/src/runtime/render/pending-op-delta-reader.ts +129 -0
- package/src/runtime/render/render-frame-types.ts +317 -0
- package/src/runtime/render/render-kernel.ts +755 -0
- package/src/runtime/scope-tag-registry.ts +95 -0
- package/src/runtime/surface-projection.ts +1 -0
- package/src/runtime/text-ack-range.ts +49 -0
- package/src/runtime/view-state.ts +67 -0
- package/src/runtime/workflow-markup.ts +1 -5
- package/src/runtime/workflow-rail-segments.ts +280 -0
- package/src/ui/WordReviewEditor.tsx +99 -15
- package/src/ui/editor-runtime-boundary.ts +10 -1
- package/src/ui/editor-shell-view.tsx +6 -0
- package/src/ui/editor-surface-controller.tsx +3 -0
- package/src/ui/headless/chrome-registry.ts +501 -0
- package/src/ui/headless/scoped-chrome-policy.ts +183 -0
- package/src/ui/headless/selection-tool-context.ts +2 -0
- package/src/ui/headless/selection-tool-resolver.ts +36 -17
- package/src/ui/headless/selection-tool-types.ts +10 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +23 -2
- package/src/ui-tailwind/chrome/role-action-sets.ts +74 -0
- package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +163 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +57 -92
- package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +274 -138
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +90 -0
- package/src/ui-tailwind/chrome-overlay/index.ts +22 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +86 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
- package/src/ui-tailwind/chrome-overlay/tw-workspace-view-switcher.tsx +95 -0
- package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +337 -0
- package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +100 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +27 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +20 -2
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +93 -23
- package/src/ui-tailwind/editor-surface/predicted-position-map.ts +78 -0
- package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +63 -0
- package/src/ui-tailwind/editor-surface/predicted-tx-gate.ts +39 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +176 -6
- package/src/ui-tailwind/index.ts +33 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
- package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
- package/src/ui-tailwind/theme/editor-theme.css +505 -144
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +559 -0
- package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +2 -2
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +304 -166
- package/src/ui-tailwind/tw-review-workspace.tsx +163 -2
|
@@ -69,6 +69,25 @@ export interface ParsedTableLook {
|
|
|
69
69
|
noVBand?: boolean;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
export interface ParsedTableIndent {
|
|
73
|
+
value: number;
|
|
74
|
+
type: "dxa" | "auto" | "pct" | "nil";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ParsedTableFloating {
|
|
78
|
+
horizontalAnchor?: "margin" | "page" | "text";
|
|
79
|
+
verticalAnchor?: "margin" | "page" | "text";
|
|
80
|
+
horizontalAlign?: "left" | "center" | "right" | "inside" | "outside";
|
|
81
|
+
horizontalOffset?: number;
|
|
82
|
+
verticalAlign?: "top" | "center" | "bottom" | "inside" | "outside";
|
|
83
|
+
verticalOffset?: number;
|
|
84
|
+
leftFromText?: number;
|
|
85
|
+
rightFromText?: number;
|
|
86
|
+
topFromText?: number;
|
|
87
|
+
bottomFromText?: number;
|
|
88
|
+
overlap?: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
72
91
|
export interface ParsedTableDocument {
|
|
73
92
|
tables: ParsedTable[];
|
|
74
93
|
}
|
|
@@ -85,6 +104,13 @@ export interface ParsedTable {
|
|
|
85
104
|
borders?: ParsedTableBorders;
|
|
86
105
|
cellMargins?: ParsedCellMargins;
|
|
87
106
|
tblLook?: ParsedTableLook;
|
|
107
|
+
indent?: ParsedTableIndent;
|
|
108
|
+
layoutMode?: "fixed" | "autofit";
|
|
109
|
+
cellSpacing?: ParsedTableWidth;
|
|
110
|
+
caption?: string;
|
|
111
|
+
description?: string;
|
|
112
|
+
bidiVisual?: boolean;
|
|
113
|
+
floating?: ParsedTableFloating;
|
|
88
114
|
}
|
|
89
115
|
|
|
90
116
|
export interface ParsedTableRow {
|
|
@@ -94,6 +120,9 @@ export interface ParsedTableRow {
|
|
|
94
120
|
height?: number;
|
|
95
121
|
heightRule?: "auto" | "atLeast" | "exact";
|
|
96
122
|
isHeader?: boolean;
|
|
123
|
+
cantSplit?: boolean;
|
|
124
|
+
horizontalAlignment?: "left" | "center" | "right";
|
|
125
|
+
cnfStyle?: string;
|
|
97
126
|
}
|
|
98
127
|
|
|
99
128
|
export interface ParsedTableCell {
|
|
@@ -106,6 +135,11 @@ export interface ParsedTableCell {
|
|
|
106
135
|
borders?: ParsedTableCellBorders;
|
|
107
136
|
shading?: ParsedCellShading;
|
|
108
137
|
verticalAlign?: "top" | "center" | "bottom";
|
|
138
|
+
textDirection?: "lrTb" | "tbRl" | "btLr";
|
|
139
|
+
noWrap?: boolean;
|
|
140
|
+
fitText?: boolean;
|
|
141
|
+
margins?: ParsedCellMargins;
|
|
142
|
+
cnfStyle?: string;
|
|
109
143
|
}
|
|
110
144
|
|
|
111
145
|
export function parseTablesFromDocumentXml(xml: string): ParsedTableDocument {
|
|
@@ -133,6 +167,13 @@ function parseTable(node: XmlElementNode, sourceXml: string): ParsedTable {
|
|
|
133
167
|
const borders = propertiesNode ? readTableBorders(propertiesNode) : undefined;
|
|
134
168
|
const cellMargins = propertiesNode ? readTableCellMargins(propertiesNode) : undefined;
|
|
135
169
|
const tblLook = propertiesNode ? readTableLook(propertiesNode) : undefined;
|
|
170
|
+
const indent = propertiesNode ? readTableIndent(propertiesNode) : undefined;
|
|
171
|
+
const layoutMode = propertiesNode ? readTableLayoutMode(propertiesNode) : undefined;
|
|
172
|
+
const cellSpacing = propertiesNode ? readTableCellSpacing(propertiesNode) : undefined;
|
|
173
|
+
const caption = propertiesNode ? readTableCaption(propertiesNode) : undefined;
|
|
174
|
+
const description = propertiesNode ? readTableDescription(propertiesNode) : undefined;
|
|
175
|
+
const bidiVisual = propertiesNode ? readTableBidiVisual(propertiesNode) : undefined;
|
|
176
|
+
const floating = propertiesNode ? readTableFloating(propertiesNode) : undefined;
|
|
136
177
|
|
|
137
178
|
return {
|
|
138
179
|
type: "table",
|
|
@@ -146,6 +187,13 @@ function parseTable(node: XmlElementNode, sourceXml: string): ParsedTable {
|
|
|
146
187
|
...(borders ? { borders } : {}),
|
|
147
188
|
...(cellMargins ? { cellMargins } : {}),
|
|
148
189
|
...(tblLook ? { tblLook } : {}),
|
|
190
|
+
...(indent ? { indent } : {}),
|
|
191
|
+
...(layoutMode ? { layoutMode } : {}),
|
|
192
|
+
...(cellSpacing ? { cellSpacing } : {}),
|
|
193
|
+
...(caption !== undefined ? { caption } : {}),
|
|
194
|
+
...(description !== undefined ? { description } : {}),
|
|
195
|
+
...(bidiVisual !== undefined ? { bidiVisual } : {}),
|
|
196
|
+
...(floating ? { floating } : {}),
|
|
149
197
|
};
|
|
150
198
|
}
|
|
151
199
|
|
|
@@ -154,6 +202,9 @@ function parseRow(node: XmlElementNode, sourceXml: string): ParsedTableRow {
|
|
|
154
202
|
const height = propertiesNode ? readRowHeight(propertiesNode) : undefined;
|
|
155
203
|
const heightRule = propertiesNode ? readRowHeightRule(propertiesNode) : undefined;
|
|
156
204
|
const isHeader = propertiesNode ? readRowIsHeader(propertiesNode) : undefined;
|
|
205
|
+
const cantSplit = propertiesNode ? readRowCantSplit(propertiesNode) : undefined;
|
|
206
|
+
const horizontalAlignment = propertiesNode ? readRowHorizontalAlignment(propertiesNode) : undefined;
|
|
207
|
+
const cnfStyle = propertiesNode ? readRowCnfStyle(propertiesNode) : undefined;
|
|
157
208
|
|
|
158
209
|
return {
|
|
159
210
|
...(propertiesNode ? { propertiesXml: sourceXml.slice(propertiesNode.start, propertiesNode.end) } : {}),
|
|
@@ -164,6 +215,9 @@ function parseRow(node: XmlElementNode, sourceXml: string): ParsedTableRow {
|
|
|
164
215
|
...(height !== undefined ? { height } : {}),
|
|
165
216
|
...(heightRule ? { heightRule } : {}),
|
|
166
217
|
...(isHeader !== undefined ? { isHeader } : {}),
|
|
218
|
+
...(cantSplit !== undefined ? { cantSplit } : {}),
|
|
219
|
+
...(horizontalAlignment ? { horizontalAlignment } : {}),
|
|
220
|
+
...(cnfStyle ? { cnfStyle } : {}),
|
|
167
221
|
};
|
|
168
222
|
}
|
|
169
223
|
|
|
@@ -179,6 +233,11 @@ function parseCell(node: XmlElementNode, sourceXml: string): ParsedTableCell {
|
|
|
179
233
|
const borders = propertiesNode ? readCellBorders(propertiesNode) : undefined;
|
|
180
234
|
const shading = propertiesNode ? readCellShading(propertiesNode) : undefined;
|
|
181
235
|
const verticalAlign = propertiesNode ? readCellVerticalAlign(propertiesNode) : undefined;
|
|
236
|
+
const textDirection = propertiesNode ? readCellTextDirection(propertiesNode) : undefined;
|
|
237
|
+
const noWrap = propertiesNode ? readCellNoWrap(propertiesNode) : undefined;
|
|
238
|
+
const fitText = propertiesNode ? readCellFitText(propertiesNode) : undefined;
|
|
239
|
+
const margins = propertiesNode ? readCellMargins(propertiesNode) : undefined;
|
|
240
|
+
const cnfStyle = propertiesNode ? readCellCnfStyle(propertiesNode) : undefined;
|
|
182
241
|
|
|
183
242
|
return {
|
|
184
243
|
...(propertiesNode ? { propertiesXml: sourceXml.slice(propertiesNode.start, propertiesNode.end) } : {}),
|
|
@@ -190,6 +249,11 @@ function parseCell(node: XmlElementNode, sourceXml: string): ParsedTableCell {
|
|
|
190
249
|
...(borders ? { borders } : {}),
|
|
191
250
|
...(shading ? { shading } : {}),
|
|
192
251
|
...(verticalAlign ? { verticalAlign } : {}),
|
|
252
|
+
...(textDirection ? { textDirection } : {}),
|
|
253
|
+
...(noWrap !== undefined ? { noWrap } : {}),
|
|
254
|
+
...(fitText !== undefined ? { fitText } : {}),
|
|
255
|
+
...(margins ? { margins } : {}),
|
|
256
|
+
...(cnfStyle ? { cnfStyle } : {}),
|
|
193
257
|
};
|
|
194
258
|
}
|
|
195
259
|
|
|
@@ -606,6 +670,191 @@ export function readRowIsHeader(propertiesNode: XmlElementNode): boolean | undef
|
|
|
606
670
|
return val !== "false" && val !== "0";
|
|
607
671
|
}
|
|
608
672
|
|
|
673
|
+
export function readTableIndent(propertiesNode: XmlElementNode): ParsedTableIndent | undefined {
|
|
674
|
+
const indentNode = findFirstChild(propertiesNode, "tblInd");
|
|
675
|
+
if (!indentNode) return undefined;
|
|
676
|
+
const valueRaw = indentNode.attributes["w:w"] ?? indentNode.attributes.w;
|
|
677
|
+
const value = valueRaw !== undefined ? Number.parseInt(valueRaw, 10) : 0;
|
|
678
|
+
if (!Number.isFinite(value)) return undefined;
|
|
679
|
+
const rawType = (indentNode.attributes["w:type"] ?? indentNode.attributes.type ?? "dxa").toLowerCase();
|
|
680
|
+
const type: ParsedTableIndent["type"] =
|
|
681
|
+
rawType === "auto" ? "auto" : rawType === "pct" ? "pct" : rawType === "nil" ? "nil" : "dxa";
|
|
682
|
+
return { value, type };
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
export function readTableLayoutMode(propertiesNode: XmlElementNode): "fixed" | "autofit" | undefined {
|
|
686
|
+
const layoutNode = findFirstChild(propertiesNode, "tblLayout");
|
|
687
|
+
if (!layoutNode) return undefined;
|
|
688
|
+
const raw = (layoutNode.attributes["w:type"] ?? layoutNode.attributes.type ?? "").toLowerCase();
|
|
689
|
+
if (raw === "fixed") return "fixed";
|
|
690
|
+
if (raw === "autofit") return "autofit";
|
|
691
|
+
return undefined;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export function readTableCellSpacing(propertiesNode: XmlElementNode): ParsedTableWidth | undefined {
|
|
695
|
+
const spacingNode = findFirstChild(propertiesNode, "tblCellSpacing");
|
|
696
|
+
if (!spacingNode) return undefined;
|
|
697
|
+
const valueRaw = spacingNode.attributes["w:w"] ?? spacingNode.attributes.w;
|
|
698
|
+
const value = valueRaw !== undefined ? Number.parseInt(valueRaw, 10) : 0;
|
|
699
|
+
if (!Number.isFinite(value)) return undefined;
|
|
700
|
+
const rawType = (spacingNode.attributes["w:type"] ?? spacingNode.attributes.type ?? "dxa").toLowerCase();
|
|
701
|
+
const type: ParsedTableWidth["type"] =
|
|
702
|
+
rawType === "auto" ? "auto" : rawType === "pct" ? "pct" : rawType === "nil" ? "nil" : "dxa";
|
|
703
|
+
return { value, type };
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
export function readTableCaption(propertiesNode: XmlElementNode): string | undefined {
|
|
707
|
+
const captionNode = findFirstChild(propertiesNode, "tblCaption");
|
|
708
|
+
if (!captionNode) return undefined;
|
|
709
|
+
return captionNode.attributes["w:val"] ?? captionNode.attributes.val;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
export function readTableDescription(propertiesNode: XmlElementNode): string | undefined {
|
|
713
|
+
const descriptionNode = findFirstChild(propertiesNode, "tblDescription");
|
|
714
|
+
if (!descriptionNode) return undefined;
|
|
715
|
+
return descriptionNode.attributes["w:val"] ?? descriptionNode.attributes.val;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export function readTableBidiVisual(propertiesNode: XmlElementNode): boolean | undefined {
|
|
719
|
+
const bidiNode = findFirstChild(propertiesNode, "bidiVisual");
|
|
720
|
+
if (!bidiNode) return undefined;
|
|
721
|
+
const val = bidiNode.attributes["w:val"] ?? bidiNode.attributes.val;
|
|
722
|
+
return val !== "false" && val !== "0" && val !== "off";
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
export function readTableFloating(propertiesNode: XmlElementNode): ParsedTableFloating | undefined {
|
|
726
|
+
const tblpPrNode = findFirstChild(propertiesNode, "tblpPr");
|
|
727
|
+
const overlapNode = findFirstChild(propertiesNode, "tblOverlap");
|
|
728
|
+
if (!tblpPrNode && !overlapNode) return undefined;
|
|
729
|
+
|
|
730
|
+
const floating: ParsedTableFloating = {};
|
|
731
|
+
|
|
732
|
+
if (tblpPrNode) {
|
|
733
|
+
const attr = (name: string): string | undefined =>
|
|
734
|
+
tblpPrNode.attributes[`w:${name}`] ?? tblpPrNode.attributes[name];
|
|
735
|
+
const num = (raw: string | undefined): number | undefined => {
|
|
736
|
+
if (raw === undefined) return undefined;
|
|
737
|
+
const parsed = Number.parseInt(raw, 10);
|
|
738
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
739
|
+
};
|
|
740
|
+
const anchor = (raw: string | undefined): ParsedTableFloating["horizontalAnchor"] | undefined => {
|
|
741
|
+
if (raw === "margin" || raw === "page" || raw === "text") return raw;
|
|
742
|
+
return undefined;
|
|
743
|
+
};
|
|
744
|
+
const hAlign = (raw: string | undefined): ParsedTableFloating["horizontalAlign"] | undefined => {
|
|
745
|
+
if (raw === "left" || raw === "center" || raw === "right" || raw === "inside" || raw === "outside") return raw;
|
|
746
|
+
return undefined;
|
|
747
|
+
};
|
|
748
|
+
const vAlign = (raw: string | undefined): ParsedTableFloating["verticalAlign"] | undefined => {
|
|
749
|
+
if (raw === "top" || raw === "center" || raw === "bottom" || raw === "inside" || raw === "outside") return raw;
|
|
750
|
+
return undefined;
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const hAnc = anchor(attr("horzAnchor"));
|
|
754
|
+
if (hAnc) floating.horizontalAnchor = hAnc;
|
|
755
|
+
const vAnc = anchor(attr("vertAnchor"));
|
|
756
|
+
if (vAnc) floating.verticalAnchor = vAnc;
|
|
757
|
+
const hA = hAlign(attr("tblpXSpec"));
|
|
758
|
+
if (hA) floating.horizontalAlign = hA;
|
|
759
|
+
const hOff = num(attr("tblpX"));
|
|
760
|
+
if (hOff !== undefined) floating.horizontalOffset = hOff;
|
|
761
|
+
const vA = vAlign(attr("tblpYSpec"));
|
|
762
|
+
if (vA) floating.verticalAlign = vA;
|
|
763
|
+
const vOff = num(attr("tblpY"));
|
|
764
|
+
if (vOff !== undefined) floating.verticalOffset = vOff;
|
|
765
|
+
const left = num(attr("leftFromText"));
|
|
766
|
+
if (left !== undefined) floating.leftFromText = left;
|
|
767
|
+
const right = num(attr("rightFromText"));
|
|
768
|
+
if (right !== undefined) floating.rightFromText = right;
|
|
769
|
+
const top = num(attr("topFromText"));
|
|
770
|
+
if (top !== undefined) floating.topFromText = top;
|
|
771
|
+
const bottom = num(attr("bottomFromText"));
|
|
772
|
+
if (bottom !== undefined) floating.bottomFromText = bottom;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (overlapNode) {
|
|
776
|
+
const val = (overlapNode.attributes["w:val"] ?? overlapNode.attributes.val ?? "overlap").toLowerCase();
|
|
777
|
+
floating.overlap = val === "overlap";
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return Object.keys(floating).length > 0 ? floating : undefined;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
export function readRowCantSplit(propertiesNode: XmlElementNode): boolean | undefined {
|
|
784
|
+
const cantSplitNode = findFirstChild(propertiesNode, "cantSplit");
|
|
785
|
+
if (!cantSplitNode) return undefined;
|
|
786
|
+
const val = cantSplitNode.attributes["w:val"] ?? cantSplitNode.attributes.val;
|
|
787
|
+
return val !== "false" && val !== "0" && val !== "off";
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
export function readRowHorizontalAlignment(propertiesNode: XmlElementNode): "left" | "center" | "right" | undefined {
|
|
791
|
+
const jcNode = findFirstChild(propertiesNode, "jc");
|
|
792
|
+
if (!jcNode) return undefined;
|
|
793
|
+
const val = jcNode.attributes["w:val"] ?? jcNode.attributes.val;
|
|
794
|
+
if (val === "left" || val === "center" || val === "right") return val;
|
|
795
|
+
return undefined;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
export function readRowCnfStyle(propertiesNode: XmlElementNode): string | undefined {
|
|
799
|
+
const cnfNode = findFirstChild(propertiesNode, "cnfStyle");
|
|
800
|
+
if (!cnfNode) return undefined;
|
|
801
|
+
return cnfNode.attributes["w:val"] ?? cnfNode.attributes.val;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
export function readCellTextDirection(propertiesNode: XmlElementNode): "lrTb" | "tbRl" | "btLr" | undefined {
|
|
805
|
+
const dirNode = findFirstChild(propertiesNode, "textDirection");
|
|
806
|
+
if (!dirNode) return undefined;
|
|
807
|
+
const val = dirNode.attributes["w:val"] ?? dirNode.attributes.val;
|
|
808
|
+
if (val === "lrTb" || val === "tbRl" || val === "btLr") return val;
|
|
809
|
+
return undefined;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
export function readCellNoWrap(propertiesNode: XmlElementNode): boolean | undefined {
|
|
813
|
+
const noWrapNode = findFirstChild(propertiesNode, "noWrap");
|
|
814
|
+
if (!noWrapNode) return undefined;
|
|
815
|
+
const val = noWrapNode.attributes["w:val"] ?? noWrapNode.attributes.val;
|
|
816
|
+
return val !== "false" && val !== "0" && val !== "off";
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
export function readCellFitText(propertiesNode: XmlElementNode): boolean | undefined {
|
|
820
|
+
const fitNode = findFirstChild(propertiesNode, "tcFitText");
|
|
821
|
+
if (!fitNode) return undefined;
|
|
822
|
+
const val = fitNode.attributes["w:val"] ?? fitNode.attributes.val;
|
|
823
|
+
return val !== "false" && val !== "0" && val !== "off";
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
export function readCellMargins(propertiesNode: XmlElementNode): ParsedCellMargins | undefined {
|
|
827
|
+
const marginsNode = findFirstChild(propertiesNode, "tcMar");
|
|
828
|
+
if (!marginsNode) return undefined;
|
|
829
|
+
const readSide = (name: string): number | undefined => {
|
|
830
|
+
const sideNode = findFirstChild(marginsNode, name);
|
|
831
|
+
if (!sideNode) return undefined;
|
|
832
|
+
const raw = sideNode.attributes["w:w"] ?? sideNode.attributes.w;
|
|
833
|
+
if (raw === undefined) return undefined;
|
|
834
|
+
const parsed = Number.parseInt(raw, 10);
|
|
835
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
836
|
+
};
|
|
837
|
+
const top = readSide("top");
|
|
838
|
+
const bottom = readSide("bottom");
|
|
839
|
+
const left = readSide("start") ?? readSide("left");
|
|
840
|
+
const right = readSide("end") ?? readSide("right");
|
|
841
|
+
if (top === undefined && bottom === undefined && left === undefined && right === undefined) {
|
|
842
|
+
return undefined;
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
...(top !== undefined ? { top } : {}),
|
|
846
|
+
...(bottom !== undefined ? { bottom } : {}),
|
|
847
|
+
...(left !== undefined ? { left } : {}),
|
|
848
|
+
...(right !== undefined ? { right } : {}),
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
export function readCellCnfStyle(propertiesNode: XmlElementNode): string | undefined {
|
|
853
|
+
const cnfNode = findFirstChild(propertiesNode, "cnfStyle");
|
|
854
|
+
if (!cnfNode) return undefined;
|
|
855
|
+
return cnfNode.attributes["w:val"] ?? cnfNode.attributes.val;
|
|
856
|
+
}
|
|
857
|
+
|
|
609
858
|
function parseBorderSpec(child: XmlElementNode): ParsedBorderSpec | undefined {
|
|
610
859
|
const value = child.attributes["w:val"] ?? child.attributes.val;
|
|
611
860
|
const sizeRaw = child.attributes["w:sz"] ?? child.attributes.sz;
|
package/src/legal/bookmarks.ts
CHANGED
|
@@ -254,6 +254,84 @@ export function resolveBookmarkFieldDependencies(
|
|
|
254
254
|
return deps;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
/**
|
|
258
|
+
* A.4 — detect duplicate bookmark ids in the canonical document.
|
|
259
|
+
*
|
|
260
|
+
* Walks the document in order and records every bookmarkStart. The first
|
|
261
|
+
* occurrence of each id keeps its original value; later occurrences are
|
|
262
|
+
* scheduled for re-keying to a fresh monotonically-increasing id above
|
|
263
|
+
* `max(existing numeric ids)` so newly-allocated ids never collide with
|
|
264
|
+
* any surviving original.
|
|
265
|
+
*
|
|
266
|
+
* Returns a plan the caller can use to mutate both bookmark nodes and
|
|
267
|
+
* every reference to them (PAGEREF / REF field instructions, hyperlink
|
|
268
|
+
* w:anchor, TOC entries). Each assignment is keyed by the start node's
|
|
269
|
+
* ordinal position so callers can walk the document a second time and
|
|
270
|
+
* apply the new id deterministically.
|
|
271
|
+
*/
|
|
272
|
+
export interface BookmarkRekeyAssignment {
|
|
273
|
+
/** Ordinal position of the bookmark_start within the pre-order walk. */
|
|
274
|
+
ordinal: number;
|
|
275
|
+
/** Original bookmark id as emitted by the source. */
|
|
276
|
+
oldId: string;
|
|
277
|
+
/** New id when a re-key was scheduled; undefined when the id is kept. */
|
|
278
|
+
newId?: string;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface BookmarkRekeyPlan {
|
|
282
|
+
assignments: readonly BookmarkRekeyAssignment[];
|
|
283
|
+
/** All ids that had ≥2 occurrences in the source. */
|
|
284
|
+
duplicatedIds: readonly string[];
|
|
285
|
+
/** New max id allocated by the plan (advisory for allocators). */
|
|
286
|
+
nextId: number;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function detectDuplicateBookmarkIds(
|
|
290
|
+
document: Pick<CanonicalDocument, "content"> | DocumentNode,
|
|
291
|
+
): BookmarkRekeyPlan {
|
|
292
|
+
const root = "content" in document ? document.content : document;
|
|
293
|
+
const seenStarts = new Set<string>();
|
|
294
|
+
const duplicatedIds = new Set<string>();
|
|
295
|
+
let maxNumericId = 0;
|
|
296
|
+
|
|
297
|
+
const bookmarkStarts: Array<BookmarkStartNode> = [];
|
|
298
|
+
|
|
299
|
+
walkDocument(root, (node) => {
|
|
300
|
+
if (node.type === "bookmark_start") {
|
|
301
|
+
bookmarkStarts.push(node);
|
|
302
|
+
const parsed = Number.parseInt(node.bookmarkId, 10);
|
|
303
|
+
if (Number.isFinite(parsed)) {
|
|
304
|
+
maxNumericId = Math.max(maxNumericId, parsed);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const assignments: BookmarkRekeyAssignment[] = [];
|
|
310
|
+
let nextId = maxNumericId + 1;
|
|
311
|
+
for (let ordinal = 0; ordinal < bookmarkStarts.length; ordinal += 1) {
|
|
312
|
+
const start = bookmarkStarts[ordinal]!;
|
|
313
|
+
if (!seenStarts.has(start.bookmarkId)) {
|
|
314
|
+
seenStarts.add(start.bookmarkId);
|
|
315
|
+
assignments.push({ ordinal, oldId: start.bookmarkId });
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
duplicatedIds.add(start.bookmarkId);
|
|
319
|
+
const fresh = String(nextId);
|
|
320
|
+
nextId += 1;
|
|
321
|
+
assignments.push({
|
|
322
|
+
ordinal,
|
|
323
|
+
oldId: start.bookmarkId,
|
|
324
|
+
newId: fresh,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
assignments,
|
|
330
|
+
duplicatedIds: [...duplicatedIds].sort(),
|
|
331
|
+
nextId,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
257
335
|
function walkDocument(node: DocumentNode, visit: (node: DocumentNode) => void): void {
|
|
258
336
|
visit(node);
|
|
259
337
|
|
|
@@ -372,6 +372,17 @@ export interface ParagraphNode {
|
|
|
372
372
|
bidi?: boolean;
|
|
373
373
|
suppressLineNumbers?: boolean;
|
|
374
374
|
cnfStyle?: string;
|
|
375
|
+
/**
|
|
376
|
+
* Preserved w14 extension identifiers for this paragraph.
|
|
377
|
+
* Round-trip (§2 A.7) requires these to survive import → export so the
|
|
378
|
+
* `w14:paraId` / `w14:textId` attributes that Word places on paragraph
|
|
379
|
+
* and run boundaries are re-emitted with the same values. Both ids are
|
|
380
|
+
* 8-hex uppercase strings per ECMA-376 Part 1 Appendix A.
|
|
381
|
+
*/
|
|
382
|
+
wordExtensionIds?: {
|
|
383
|
+
paraId?: string;
|
|
384
|
+
textId?: string;
|
|
385
|
+
};
|
|
375
386
|
children: InlineNode[];
|
|
376
387
|
}
|
|
377
388
|
|
|
@@ -405,6 +416,25 @@ export interface TableWidth {
|
|
|
405
416
|
type: "dxa" | "auto" | "pct" | "nil";
|
|
406
417
|
}
|
|
407
418
|
|
|
419
|
+
export interface TableIndent {
|
|
420
|
+
value: number;
|
|
421
|
+
type: "dxa" | "auto" | "pct" | "nil";
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export interface TableFloatingProperties {
|
|
425
|
+
horizontalAnchor?: "margin" | "page" | "text";
|
|
426
|
+
verticalAnchor?: "margin" | "page" | "text";
|
|
427
|
+
horizontalAlign?: "left" | "center" | "right" | "inside" | "outside";
|
|
428
|
+
horizontalOffset?: number;
|
|
429
|
+
verticalAlign?: "top" | "center" | "bottom" | "inside" | "outside";
|
|
430
|
+
verticalOffset?: number;
|
|
431
|
+
leftFromText?: number;
|
|
432
|
+
rightFromText?: number;
|
|
433
|
+
topFromText?: number;
|
|
434
|
+
bottomFromText?: number;
|
|
435
|
+
overlap?: boolean;
|
|
436
|
+
}
|
|
437
|
+
|
|
408
438
|
export interface CellShading {
|
|
409
439
|
fill?: string;
|
|
410
440
|
color?: string;
|
|
@@ -470,6 +500,13 @@ export interface TableNode {
|
|
|
470
500
|
borders?: TableBorders;
|
|
471
501
|
cellMargins?: TableCellMargins;
|
|
472
502
|
tblLook?: TableLook;
|
|
503
|
+
indent?: TableIndent;
|
|
504
|
+
layoutMode?: "fixed" | "autofit";
|
|
505
|
+
cellSpacing?: TableWidth;
|
|
506
|
+
caption?: string;
|
|
507
|
+
description?: string;
|
|
508
|
+
bidiVisual?: boolean;
|
|
509
|
+
floating?: TableFloatingProperties;
|
|
473
510
|
}
|
|
474
511
|
|
|
475
512
|
export interface TableRowNode {
|
|
@@ -483,6 +520,9 @@ export interface TableRowNode {
|
|
|
483
520
|
height?: number;
|
|
484
521
|
heightRule?: "auto" | "atLeast" | "exact";
|
|
485
522
|
isHeader?: boolean;
|
|
523
|
+
cantSplit?: boolean;
|
|
524
|
+
horizontalAlignment?: "left" | "center" | "right";
|
|
525
|
+
cnfStyle?: string;
|
|
486
526
|
}
|
|
487
527
|
|
|
488
528
|
export interface TableCellNode {
|
|
@@ -495,6 +535,11 @@ export interface TableCellNode {
|
|
|
495
535
|
borders?: TableCellBorders;
|
|
496
536
|
shading?: CellShading;
|
|
497
537
|
verticalAlign?: "top" | "center" | "bottom";
|
|
538
|
+
textDirection?: "lrTb" | "tbRl" | "btLr";
|
|
539
|
+
noWrap?: boolean;
|
|
540
|
+
fitText?: boolean;
|
|
541
|
+
margins?: TableCellMargins;
|
|
542
|
+
cnfStyle?: string;
|
|
498
543
|
}
|
|
499
544
|
|
|
500
545
|
export interface SdtCheckboxState {
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { ScopeTagTouch } from "../../api/public-types.ts";
|
|
2
|
+
import type {
|
|
3
|
+
CanonicalAnchor,
|
|
4
|
+
CommentThread,
|
|
5
|
+
RevisionRecord,
|
|
6
|
+
} from "../../model/canonical-document.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Diff prior vs next review state and emit one `ScopeTagTouch` per changed
|
|
10
|
+
* comment or revision anchor. The predicted-text lane consumes these touches
|
|
11
|
+
* to classify a text command's ack as `equivalent` (no touches), `adjusted`
|
|
12
|
+
* (anchors shifted), or to feed decoration redraws without a PM rebuild.
|
|
13
|
+
*
|
|
14
|
+
* Notes
|
|
15
|
+
* - Newly created annotations show up as "extended" (revisions) or "split"
|
|
16
|
+
* (comments) so the lane knows to redraw them.
|
|
17
|
+
* - Detached annotations show up as "detached".
|
|
18
|
+
* - Anchors whose range is byte-identical across the diff are omitted.
|
|
19
|
+
*/
|
|
20
|
+
export function collectScopeTagTouches(
|
|
21
|
+
priorComments: Readonly<Record<string, CommentThread>>,
|
|
22
|
+
nextComments: Readonly<Record<string, CommentThread>>,
|
|
23
|
+
priorRevisions: Readonly<Record<string, RevisionRecord>>,
|
|
24
|
+
nextRevisions: Readonly<Record<string, RevisionRecord>>,
|
|
25
|
+
): ScopeTagTouch[] {
|
|
26
|
+
const touches: ScopeTagTouch[] = [];
|
|
27
|
+
|
|
28
|
+
for (const [id, prior] of Object.entries(priorComments)) {
|
|
29
|
+
const next = nextComments[id];
|
|
30
|
+
if (!next) {
|
|
31
|
+
touches.push({
|
|
32
|
+
tagType: "comment",
|
|
33
|
+
tagId: id,
|
|
34
|
+
behavior: "detached",
|
|
35
|
+
range: anchorRange(prior.anchor),
|
|
36
|
+
});
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const behavior = classifyAnchorChange(prior.anchor, next.anchor);
|
|
40
|
+
if (behavior !== "unchanged") {
|
|
41
|
+
touches.push({
|
|
42
|
+
tagType: "comment",
|
|
43
|
+
tagId: id,
|
|
44
|
+
behavior,
|
|
45
|
+
range: anchorRange(next.anchor),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const [id, next] of Object.entries(nextComments)) {
|
|
51
|
+
if (!priorComments[id]) {
|
|
52
|
+
touches.push({
|
|
53
|
+
tagType: "comment",
|
|
54
|
+
tagId: id,
|
|
55
|
+
behavior: "split",
|
|
56
|
+
range: anchorRange(next.anchor),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const [id, prior] of Object.entries(priorRevisions)) {
|
|
62
|
+
const next = nextRevisions[id];
|
|
63
|
+
if (!next) {
|
|
64
|
+
touches.push({
|
|
65
|
+
tagType: "revision",
|
|
66
|
+
tagId: id,
|
|
67
|
+
behavior: "detached",
|
|
68
|
+
range: anchorRange(prior.anchor),
|
|
69
|
+
});
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const behavior = classifyAnchorChange(prior.anchor, next.anchor);
|
|
73
|
+
if (behavior !== "unchanged") {
|
|
74
|
+
touches.push({
|
|
75
|
+
tagType: "revision",
|
|
76
|
+
tagId: id,
|
|
77
|
+
behavior,
|
|
78
|
+
range: anchorRange(next.anchor),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const [id, next] of Object.entries(nextRevisions)) {
|
|
84
|
+
if (!priorRevisions[id]) {
|
|
85
|
+
touches.push({
|
|
86
|
+
tagType: "revision",
|
|
87
|
+
tagId: id,
|
|
88
|
+
behavior: "extended",
|
|
89
|
+
range: anchorRange(next.anchor),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return touches;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function anchorRange(anchor: CanonicalAnchor): { from: number; to: number } {
|
|
98
|
+
if (anchor.kind === "range") {
|
|
99
|
+
return { from: anchor.range.from, to: anchor.range.to };
|
|
100
|
+
}
|
|
101
|
+
if (anchor.kind === "node") {
|
|
102
|
+
return { from: anchor.at, to: anchor.at };
|
|
103
|
+
}
|
|
104
|
+
return { from: anchor.lastKnownRange.from, to: anchor.lastKnownRange.to };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function classifyAnchorChange(
|
|
108
|
+
prior: CanonicalAnchor,
|
|
109
|
+
next: CanonicalAnchor,
|
|
110
|
+
): "unchanged" | "extended" | "trimmed" | "split" | "detached" {
|
|
111
|
+
if (prior.kind === "detached" && next.kind === "detached") {
|
|
112
|
+
const pr = prior.lastKnownRange;
|
|
113
|
+
const nr = next.lastKnownRange;
|
|
114
|
+
return pr.from === nr.from && pr.to === nr.to ? "unchanged" : "detached";
|
|
115
|
+
}
|
|
116
|
+
if (prior.kind !== "detached" && next.kind === "detached") {
|
|
117
|
+
return "detached";
|
|
118
|
+
}
|
|
119
|
+
if (prior.kind === "detached" && next.kind !== "detached") {
|
|
120
|
+
return "extended";
|
|
121
|
+
}
|
|
122
|
+
const priorR = anchorRange(prior);
|
|
123
|
+
const nextR = anchorRange(next);
|
|
124
|
+
if (priorR.from === nextR.from && priorR.to === nextR.to) return "unchanged";
|
|
125
|
+
const priorLen = priorR.to - priorR.from;
|
|
126
|
+
const nextLen = nextR.to - nextR.from;
|
|
127
|
+
if (nextLen > priorLen) return "extended";
|
|
128
|
+
if (nextLen < priorLen) return "trimmed";
|
|
129
|
+
return "split";
|
|
130
|
+
}
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
SubPartsCatalog,
|
|
16
16
|
} from "../model/canonical-document.ts";
|
|
17
17
|
import { createEditorSurfaceSnapshot } from "./surface-projection.ts";
|
|
18
|
+
import { resolveDefaultPageSizeTwips } from "./layout/default-page-format.ts";
|
|
18
19
|
import {
|
|
19
20
|
resolveSectionVariants,
|
|
20
21
|
sectionSupportsStoryTarget,
|
|
@@ -88,9 +89,10 @@ export function buildPageLayoutSnapshot(
|
|
|
88
89
|
properties: SectionProperties | undefined,
|
|
89
90
|
subParts: SubPartsCatalog | undefined,
|
|
90
91
|
): PageLayoutSnapshot {
|
|
92
|
+
const defaultSize = resolveDefaultPageSizeTwips();
|
|
91
93
|
const pageSize = properties?.pageSize ?? {
|
|
92
|
-
width:
|
|
93
|
-
height:
|
|
94
|
+
width: defaultSize.widthTwips,
|
|
95
|
+
height: defaultSize.heightTwips,
|
|
94
96
|
orientation: "portrait" as const,
|
|
95
97
|
};
|
|
96
98
|
const margins = properties?.pageMargins ?? {
|