@beyondwork/docx-react-component 1.0.37 → 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.
Files changed (74) hide show
  1. package/package.json +1 -1
  2. package/src/api/public-types.ts +319 -1
  3. package/src/core/commands/section-layout-commands.ts +58 -0
  4. package/src/core/commands/table-grid.ts +431 -0
  5. package/src/core/commands/table-structure-commands.ts +815 -55
  6. package/src/io/export/serialize-main-document.ts +2 -11
  7. package/src/io/export/serialize-numbering.ts +1 -2
  8. package/src/io/export/serialize-tables.ts +74 -0
  9. package/src/io/export/table-properties-xml.ts +139 -4
  10. package/src/io/normalize/normalize-text.ts +15 -0
  11. package/src/io/ooxml/parse-footnotes.ts +60 -0
  12. package/src/io/ooxml/parse-headers-footers.ts +60 -0
  13. package/src/io/ooxml/parse-main-document.ts +137 -0
  14. package/src/io/ooxml/parse-tables.ts +249 -0
  15. package/src/model/canonical-document.ts +34 -0
  16. package/src/runtime/document-layout.ts +4 -2
  17. package/src/runtime/document-navigation.ts +1 -1
  18. package/src/runtime/document-runtime.ts +114 -0
  19. package/src/runtime/layout/default-page-format.ts +96 -0
  20. package/src/runtime/layout/index.ts +45 -0
  21. package/src/runtime/layout/inert-layout-facet.ts +14 -0
  22. package/src/runtime/layout/layout-engine-instance.ts +33 -23
  23. package/src/runtime/layout/margin-preset-catalog.ts +178 -0
  24. package/src/runtime/layout/page-format-catalog.ts +233 -0
  25. package/src/runtime/layout/page-graph.ts +19 -0
  26. package/src/runtime/layout/paginated-layout-engine.ts +142 -9
  27. package/src/runtime/layout/project-block-fragments.ts +91 -0
  28. package/src/runtime/layout/public-facet.ts +709 -16
  29. package/src/runtime/layout/table-render-plan.ts +229 -0
  30. package/src/runtime/render/block-fragment-projection.ts +35 -0
  31. package/src/runtime/render/decoration-resolver.ts +189 -0
  32. package/src/runtime/render/index.ts +57 -0
  33. package/src/runtime/render/pending-op-delta-reader.ts +129 -0
  34. package/src/runtime/render/render-frame-types.ts +317 -0
  35. package/src/runtime/render/render-kernel.ts +755 -0
  36. package/src/runtime/view-state.ts +67 -0
  37. package/src/runtime/workflow-markup.ts +1 -5
  38. package/src/runtime/workflow-rail-segments.ts +280 -0
  39. package/src/ui/WordReviewEditor.tsx +84 -15
  40. package/src/ui/editor-shell-view.tsx +6 -0
  41. package/src/ui/headless/chrome-registry.ts +280 -14
  42. package/src/ui/headless/scoped-chrome-policy.ts +20 -1
  43. package/src/ui/headless/selection-tool-types.ts +10 -0
  44. package/src/ui-tailwind/chrome/chrome-preset-model.ts +23 -2
  45. package/src/ui-tailwind/chrome/role-action-sets.ts +74 -0
  46. package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
  47. package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +163 -0
  48. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +57 -92
  49. package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
  50. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
  51. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +274 -138
  52. package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +90 -0
  53. package/src/ui-tailwind/chrome-overlay/index.ts +22 -0
  54. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +86 -0
  55. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
  56. package/src/ui-tailwind/chrome-overlay/tw-workspace-view-switcher.tsx +95 -0
  57. package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +4 -0
  58. package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +11 -0
  59. package/src/ui-tailwind/editor-surface/perf-probe.ts +7 -1
  60. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +12 -1
  61. package/src/ui-tailwind/editor-surface/pm-decorations.ts +22 -12
  62. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +3 -0
  63. package/src/ui-tailwind/index.ts +33 -0
  64. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
  65. package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
  66. package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
  67. package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
  68. package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
  69. package/src/ui-tailwind/theme/editor-theme.css +498 -163
  70. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +559 -0
  71. package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
  72. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
  73. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +69 -0
  74. package/src/ui-tailwind/tw-review-workspace.tsx +136 -1
@@ -10,6 +10,8 @@ import type {
10
10
  TableBorders,
11
11
  TableCellBorders,
12
12
  TableCellMargins,
13
+ TableFloatingProperties,
14
+ TableIndent,
13
15
  TableLook,
14
16
  TableWidth,
15
17
  SectionProperties,
@@ -35,16 +37,31 @@ import { classifyFieldInstruction } from "./parse-fields.ts";
35
37
  import { resolveHighlightColor } from "./highlight-colors.ts";
36
38
  import {
37
39
  readCellBorders as readSharedCellBorders,
40
+ readCellCnfStyle as readSharedCellCnfStyle,
41
+ readCellFitText as readSharedCellFitText,
42
+ readCellMargins as readSharedCellMargins,
43
+ readCellNoWrap as readSharedCellNoWrap,
38
44
  readCellShading as readSharedCellShading,
45
+ readCellTextDirection as readSharedCellTextDirection,
39
46
  readCellVerticalAlign as readSharedCellVerticalAlign,
40
47
  readCellWidth as readSharedCellWidth,
41
48
  readGridColumns as readSharedGridColumns,
49
+ readRowCantSplit as readSharedRowCantSplit,
50
+ readRowCnfStyle as readSharedRowCnfStyle,
42
51
  readRowHeight as readSharedRowHeight,
43
52
  readRowHeightRule as readSharedRowHeightRule,
53
+ readRowHorizontalAlignment as readSharedRowHorizontalAlignment,
44
54
  readRowIsHeader as readSharedRowIsHeader,
45
55
  readTableAlignment as readSharedTableAlignment,
56
+ readTableBidiVisual as readSharedTableBidiVisual,
46
57
  readTableBorders as readSharedTableBorders,
58
+ readTableCaption as readSharedTableCaption,
47
59
  readTableCellMargins as readSharedTableCellMargins,
60
+ readTableCellSpacing as readSharedTableCellSpacing,
61
+ readTableDescription as readSharedTableDescription,
62
+ readTableFloating as readSharedTableFloating,
63
+ readTableIndent as readSharedTableIndent,
64
+ readTableLayoutMode as readSharedTableLayoutMode,
48
65
  readTableLook as readSharedTableLook,
49
66
  readTableStyleId as readSharedTableStyleId,
50
67
  readTableWidth as readSharedTableWidth,
@@ -332,6 +349,13 @@ export interface ParsedTableBlockNode {
332
349
  alignment?: "left" | "center" | "right";
333
350
  borders?: TableBorders;
334
351
  cellMargins?: TableCellMargins;
352
+ indent?: TableIndent;
353
+ layoutMode?: "fixed" | "autofit";
354
+ cellSpacing?: TableWidth;
355
+ caption?: string;
356
+ description?: string;
357
+ bidiVisual?: boolean;
358
+ floating?: TableFloatingProperties;
335
359
  rawXml: string;
336
360
  }
337
361
 
@@ -346,6 +370,9 @@ export interface ParsedTableRowNode {
346
370
  height?: number;
347
371
  heightRule?: "auto" | "atLeast" | "exact";
348
372
  isHeader?: boolean;
373
+ cantSplit?: boolean;
374
+ horizontalAlignment?: "left" | "center" | "right";
375
+ cnfStyle?: string;
349
376
  rawXml: string;
350
377
  }
351
378
 
@@ -359,6 +386,11 @@ export interface ParsedTableCellNode {
359
386
  borders?: TableCellBorders;
360
387
  shading?: CellShading;
361
388
  verticalAlign?: "top" | "center" | "bottom";
389
+ textDirection?: "lrTb" | "tbRl" | "btLr";
390
+ noWrap?: boolean;
391
+ fitText?: boolean;
392
+ margins?: TableCellMargins;
393
+ cnfStyle?: string;
362
394
  rawXml: string;
363
395
  }
364
396
 
@@ -857,6 +889,13 @@ function parseTableElement(
857
889
  let alignment: ParsedTableBlockNode["alignment"];
858
890
  let borders: TableBorders | undefined;
859
891
  let cellMargins: TableCellMargins | undefined;
892
+ let indent: TableIndent | undefined;
893
+ let layoutMode: ParsedTableBlockNode["layoutMode"];
894
+ let cellSpacing: TableWidth | undefined;
895
+ let caption: string | undefined;
896
+ let description: string | undefined;
897
+ let bidiVisual: boolean | undefined;
898
+ let floating: TableFloatingProperties | undefined;
860
899
  const rows: ParsedTableRowNode[] = [];
861
900
 
862
901
  for (const child of node.children) {
@@ -871,6 +910,13 @@ function parseTableElement(
871
910
  alignment = readTableAlignment(child);
872
911
  borders = readTableBorders(child);
873
912
  cellMargins = readTableCellMargins(child);
913
+ indent = readTableIndent(child);
914
+ layoutMode = readTableLayoutMode(child);
915
+ cellSpacing = readTableCellSpacing(child);
916
+ caption = readTableCaption(child);
917
+ description = readTableDescription(child);
918
+ bidiVisual = readTableBidiVisual(child);
919
+ floating = readTableFloating(child);
874
920
  break;
875
921
  }
876
922
  case "tblGrid": {
@@ -895,6 +941,13 @@ function parseTableElement(
895
941
  ...(alignment ? { alignment } : {}),
896
942
  ...(borders ? { borders } : {}),
897
943
  ...(cellMargins ? { cellMargins } : {}),
944
+ ...(indent ? { indent } : {}),
945
+ ...(layoutMode ? { layoutMode } : {}),
946
+ ...(cellSpacing ? { cellSpacing } : {}),
947
+ ...(caption !== undefined ? { caption } : {}),
948
+ ...(description !== undefined ? { description } : {}),
949
+ ...(bidiVisual !== undefined ? { bidiVisual } : {}),
950
+ ...(floating ? { floating } : {}),
898
951
  rawXml: sourceXml.slice(node.start, node.end),
899
952
  };
900
953
  }
@@ -915,6 +968,9 @@ function parseTableRowElement(
915
968
  let height: number | undefined;
916
969
  let heightRule: ParsedTableRowNode["heightRule"];
917
970
  let isHeader: boolean | undefined;
971
+ let cantSplit: boolean | undefined;
972
+ let horizontalAlignment: ParsedTableRowNode["horizontalAlignment"];
973
+ let cnfStyle: string | undefined;
918
974
  const cells: ParsedTableCellNode[] = [];
919
975
 
920
976
  for (const child of node.children) {
@@ -930,6 +986,9 @@ function parseTableRowElement(
930
986
  height = readRowHeight(child);
931
987
  heightRule = readRowHeightRule(child);
932
988
  isHeader = readRowIsHeader(child);
989
+ cantSplit = readRowCantSplit(child);
990
+ horizontalAlignment = readRowHorizontalAlignment(child);
991
+ cnfStyle = readRowCnfStyle(child);
933
992
  break;
934
993
  case "tc":
935
994
  cells.push(parseTableCellElement(child, sourceXml, relationshipMap, relationships, mediaParts, sourcePartPath));
@@ -947,6 +1006,9 @@ function parseTableRowElement(
947
1006
  ...(height !== undefined ? { height } : {}),
948
1007
  ...(heightRule ? { heightRule } : {}),
949
1008
  ...(isHeader !== undefined ? { isHeader } : {}),
1009
+ ...(cantSplit !== undefined ? { cantSplit } : {}),
1010
+ ...(horizontalAlignment ? { horizontalAlignment } : {}),
1011
+ ...(cnfStyle ? { cnfStyle } : {}),
950
1012
  cells,
951
1013
  rawXml: sourceXml.slice(node.start, node.end),
952
1014
  };
@@ -967,6 +1029,11 @@ function parseTableCellElement(
967
1029
  let borders: TableCellBorders | undefined;
968
1030
  let shading: CellShading | undefined;
969
1031
  let verticalAlign: ParsedTableCellNode["verticalAlign"];
1032
+ let textDirection: ParsedTableCellNode["textDirection"];
1033
+ let noWrap: boolean | undefined;
1034
+ let fitText: boolean | undefined;
1035
+ let margins: TableCellMargins | undefined;
1036
+ let cnfStyle: string | undefined;
970
1037
  const children: ParsedBlockNode[] = [];
971
1038
 
972
1039
  for (const child of node.children) {
@@ -981,6 +1048,11 @@ function parseTableCellElement(
981
1048
  borders = readCellBorders(child);
982
1049
  shading = readCellShading(child);
983
1050
  verticalAlign = readCellVerticalAlign(child);
1051
+ textDirection = readCellTextDirection(child);
1052
+ noWrap = readCellNoWrap(child);
1053
+ fitText = readCellFitText(child);
1054
+ margins = readCellMargins(child);
1055
+ cnfStyle = readCellCnfStyle(child);
984
1056
  break;
985
1057
  }
986
1058
  default: {
@@ -1001,6 +1073,11 @@ function parseTableCellElement(
1001
1073
  ...(borders ? { borders } : {}),
1002
1074
  ...(shading ? { shading } : {}),
1003
1075
  ...(verticalAlign ? { verticalAlign } : {}),
1076
+ ...(textDirection ? { textDirection } : {}),
1077
+ ...(noWrap !== undefined ? { noWrap } : {}),
1078
+ ...(fitText !== undefined ? { fitText } : {}),
1079
+ ...(margins ? { margins } : {}),
1080
+ ...(cnfStyle ? { cnfStyle } : {}),
1004
1081
  rawXml: sourceXml.slice(node.start, node.end),
1005
1082
  };
1006
1083
  }
@@ -1274,6 +1351,66 @@ function readCellVerticalAlign(node: XmlElementNode): ParsedTableCellNode["verti
1274
1351
  return readSharedCellVerticalAlign(node);
1275
1352
  }
1276
1353
 
1354
+ function readTableIndent(node: XmlElementNode): TableIndent | undefined {
1355
+ return readSharedTableIndent(node);
1356
+ }
1357
+
1358
+ function readTableLayoutMode(node: XmlElementNode): ParsedTableBlockNode["layoutMode"] {
1359
+ return readSharedTableLayoutMode(node);
1360
+ }
1361
+
1362
+ function readTableCellSpacing(node: XmlElementNode): TableWidth | undefined {
1363
+ return readSharedTableCellSpacing(node);
1364
+ }
1365
+
1366
+ function readTableCaption(node: XmlElementNode): string | undefined {
1367
+ return readSharedTableCaption(node);
1368
+ }
1369
+
1370
+ function readTableDescription(node: XmlElementNode): string | undefined {
1371
+ return readSharedTableDescription(node);
1372
+ }
1373
+
1374
+ function readTableBidiVisual(node: XmlElementNode): boolean | undefined {
1375
+ return readSharedTableBidiVisual(node);
1376
+ }
1377
+
1378
+ function readTableFloating(node: XmlElementNode): TableFloatingProperties | undefined {
1379
+ return readSharedTableFloating(node);
1380
+ }
1381
+
1382
+ function readRowCantSplit(node: XmlElementNode): boolean | undefined {
1383
+ return readSharedRowCantSplit(node);
1384
+ }
1385
+
1386
+ function readRowHorizontalAlignment(node: XmlElementNode): ParsedTableRowNode["horizontalAlignment"] {
1387
+ return readSharedRowHorizontalAlignment(node);
1388
+ }
1389
+
1390
+ function readRowCnfStyle(node: XmlElementNode): string | undefined {
1391
+ return readSharedRowCnfStyle(node);
1392
+ }
1393
+
1394
+ function readCellTextDirection(node: XmlElementNode): ParsedTableCellNode["textDirection"] {
1395
+ return readSharedCellTextDirection(node);
1396
+ }
1397
+
1398
+ function readCellNoWrap(node: XmlElementNode): boolean | undefined {
1399
+ return readSharedCellNoWrap(node);
1400
+ }
1401
+
1402
+ function readCellFitText(node: XmlElementNode): boolean | undefined {
1403
+ return readSharedCellFitText(node);
1404
+ }
1405
+
1406
+ function readCellMargins(node: XmlElementNode): TableCellMargins | undefined {
1407
+ return readSharedCellMargins(node);
1408
+ }
1409
+
1410
+ function readCellCnfStyle(node: XmlElementNode): string | undefined {
1411
+ return readSharedCellCnfStyle(node);
1412
+ }
1413
+
1277
1414
  /**
1278
1415
  * Check if a table's raw XML contains content that cannot safely round-trip
1279
1416
  * through the parsed table path yet. This includes:
@@ -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;
@@ -416,6 +416,25 @@ export interface TableWidth {
416
416
  type: "dxa" | "auto" | "pct" | "nil";
417
417
  }
418
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
+
419
438
  export interface CellShading {
420
439
  fill?: string;
421
440
  color?: string;
@@ -481,6 +500,13 @@ export interface TableNode {
481
500
  borders?: TableBorders;
482
501
  cellMargins?: TableCellMargins;
483
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;
484
510
  }
485
511
 
486
512
  export interface TableRowNode {
@@ -494,6 +520,9 @@ export interface TableRowNode {
494
520
  height?: number;
495
521
  heightRule?: "auto" | "atLeast" | "exact";
496
522
  isHeader?: boolean;
523
+ cantSplit?: boolean;
524
+ horizontalAlignment?: "left" | "center" | "right";
525
+ cnfStyle?: string;
497
526
  }
498
527
 
499
528
  export interface TableCellNode {
@@ -506,6 +535,11 @@ export interface TableCellNode {
506
535
  borders?: TableCellBorders;
507
536
  shading?: CellShading;
508
537
  verticalAlign?: "top" | "center" | "bottom";
538
+ textDirection?: "lrTb" | "tbRl" | "btLr";
539
+ noWrap?: boolean;
540
+ fitText?: boolean;
541
+ margins?: TableCellMargins;
542
+ cnfStyle?: string;
509
543
  }
510
544
 
511
545
  export interface SdtCheckboxState {
@@ -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: 12240,
93
- height: 15840,
94
+ width: defaultSize.widthTwips,
95
+ height: defaultSize.heightTwips,
94
96
  orientation: "portrait" as const,
95
97
  };
96
98
  const margins = properties?.pageMargins ?? {
@@ -206,7 +206,7 @@ function headingLevelFromStyleId(styleId?: string): number | null {
206
206
  return null;
207
207
  }
208
208
 
209
- function buildHeadingOutline(
209
+ export function buildHeadingOutline(
210
210
  document: CanonicalDocumentEnvelope,
211
211
  mainSurface: EditorSurfaceSnapshot,
212
212
  sections: ResolvedDocumentSection[],