@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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beyondwork/docx-react-component",
3
3
  "publisher": "beyondwork",
4
- "version": "1.0.37",
4
+ "version": "1.0.38",
5
5
  "description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
6
6
  "type": "module",
7
7
  "sideEffects": [
@@ -20,8 +20,42 @@ export type {
20
20
  PublicResolvedParagraphFormatting,
21
21
  PublicResolvedRunFormatting,
22
22
  PublicSectionNode,
23
+ // R0.5: named page-format + margin-preset catalogs
24
+ PageFormatDefinition,
25
+ ActivePageFormat,
26
+ MarginPresetDefinition,
27
+ ActiveMarginPreset,
28
+ RenderZoomSummary,
23
29
  } from "../runtime/layout/public-facet.ts";
24
30
 
31
+ // R1: render-kernel shapes (frame, decoration index, anchor index) — these
32
+ // are exposed additively so consumers can read the future `getRenderFrame`
33
+ // output without depending on the internal render kernel module.
34
+ export type {
35
+ RenderFrame,
36
+ RenderPage,
37
+ RenderPageRegions,
38
+ RenderStoryRegion,
39
+ RenderBlock,
40
+ RenderLine,
41
+ RenderLineAnchor,
42
+ RenderBlockDecoration,
43
+ RenderFrameRect,
44
+ RenderZoom,
45
+ RenderAnchorIndex,
46
+ RenderAnchorQuery,
47
+ RenderFrameQueryOptions,
48
+ RenderHitResult,
49
+ RenderKernelEvent,
50
+ RenderPoint,
51
+ DecorationIndex,
52
+ PageChromeReservations,
53
+ } from "../runtime/render/index.ts";
54
+
55
+ // R0: scope-rail posture vocabulary and chrome pin state — exposed so
56
+ // host apps can drive the editor role and render custom posture menus.
57
+ export type { ScopeRailPosture } from "../runtime/workflow-rail-segments.ts";
58
+
25
59
  export type FieldFamily = import("../model/canonical-document.ts").FieldFamily;
26
60
  export type FieldRefreshStatus = import("../model/canonical-document.ts").FieldRefreshStatus;
27
61
  export type SupportedFieldFamily = import("../model/canonical-document.ts").SupportedFieldFamily;
@@ -1036,9 +1070,231 @@ export interface TableStructureContextSnapshot {
1036
1070
  mergeCells: TableOperationCapabilitySnapshot;
1037
1071
  splitCell: TableOperationCapabilitySnapshot;
1038
1072
  deleteTable: TableOperationCapabilitySnapshot;
1073
+ // P2g — table-level authoring ops
1074
+ setTableWidth: TableOperationCapabilitySnapshot;
1075
+ setTableAlignment: TableOperationCapabilitySnapshot;
1076
+ setTableIndent: TableOperationCapabilitySnapshot;
1077
+ setTableLayoutMode: TableOperationCapabilitySnapshot;
1078
+ setTableCellMargins: TableOperationCapabilitySnapshot;
1079
+ setTableBorders: TableOperationCapabilitySnapshot;
1080
+ setTableCaption: TableOperationCapabilitySnapshot;
1081
+ setTableDescription: TableOperationCapabilitySnapshot;
1082
+ // P2h — column / row sizing + row props
1083
+ setColumnWidth: TableOperationCapabilitySnapshot;
1084
+ distributeColumnsEvenly: TableOperationCapabilitySnapshot;
1085
+ setRowHeight: TableOperationCapabilitySnapshot;
1086
+ setRowCantSplit: TableOperationCapabilitySnapshot;
1087
+ setRowIsHeader: TableOperationCapabilitySnapshot;
1088
+ setRowAlignment: TableOperationCapabilitySnapshot;
1089
+ insertRows: TableOperationCapabilitySnapshot;
1090
+ insertColumns: TableOperationCapabilitySnapshot;
1091
+ // P2i — cell-level ops (over a locator)
1092
+ setCellBorders: TableOperationCapabilitySnapshot;
1093
+ setCellShading: TableOperationCapabilitySnapshot;
1094
+ clearCellShading: TableOperationCapabilitySnapshot;
1095
+ setCellMargins: TableOperationCapabilitySnapshot;
1096
+ setCellVerticalAlign: TableOperationCapabilitySnapshot;
1097
+ setCellTextDirection: TableOperationCapabilitySnapshot;
1098
+ setCellNoWrap: TableOperationCapabilitySnapshot;
1099
+ setCellFitText: TableOperationCapabilitySnapshot;
1039
1100
  };
1040
1101
  }
1041
1102
 
1103
+ // ─── Table typed-op surface (P2.5) ───────────────────────────────────────────
1104
+
1105
+ /**
1106
+ * Width/height unit used by OOXML table properties.
1107
+ */
1108
+ export interface PublicTableWidth {
1109
+ value: number;
1110
+ type: "dxa" | "auto" | "pct" | "nil";
1111
+ }
1112
+
1113
+ /**
1114
+ * Reusable border-spec shape for table-level and cell-level borders.
1115
+ * Matches OOXML w:*Borders child attributes.
1116
+ */
1117
+ export interface PublicBorderSpec {
1118
+ value?: string;
1119
+ size?: number;
1120
+ space?: number;
1121
+ color?: string;
1122
+ }
1123
+
1124
+ export interface PublicTableBorders {
1125
+ top?: PublicBorderSpec;
1126
+ left?: PublicBorderSpec;
1127
+ bottom?: PublicBorderSpec;
1128
+ right?: PublicBorderSpec;
1129
+ insideH?: PublicBorderSpec;
1130
+ insideV?: PublicBorderSpec;
1131
+ }
1132
+
1133
+ export interface PublicTableCellBorders {
1134
+ top?: PublicBorderSpec;
1135
+ left?: PublicBorderSpec;
1136
+ bottom?: PublicBorderSpec;
1137
+ right?: PublicBorderSpec;
1138
+ insideH?: PublicBorderSpec;
1139
+ insideV?: PublicBorderSpec;
1140
+ }
1141
+
1142
+ export interface PublicTableCellMargins {
1143
+ top?: number;
1144
+ left?: number;
1145
+ bottom?: number;
1146
+ right?: number;
1147
+ }
1148
+
1149
+ export interface PublicCellShading {
1150
+ fill?: string;
1151
+ color?: string;
1152
+ val?: string;
1153
+ }
1154
+
1155
+ /**
1156
+ * Addresses a cell (or set of cells) for locator-driven cell-level ops.
1157
+ *
1158
+ * - `anchor`: the currently anchored cell from the active selection
1159
+ * - `index`: explicit (rowIndex, columnIndex)
1160
+ * - `rect`: rectangular range in logical grid coordinates
1161
+ * - `row` / `column`: all cells in a single row or column
1162
+ * - `selection`: whatever the active selection covers
1163
+ */
1164
+ export type PublicCellLocator =
1165
+ | { kind: "anchor" }
1166
+ | { kind: "index"; rowIndex: number; columnIndex: number }
1167
+ | {
1168
+ kind: "rect";
1169
+ rect: { top: number; left: number; bottom: number; right: number };
1170
+ }
1171
+ | { kind: "row"; rowIndex: number }
1172
+ | { kind: "column"; columnIndex: number }
1173
+ | { kind: "selection" };
1174
+
1175
+ /**
1176
+ * Discriminated union of every table operation the public API exposes.
1177
+ * Mirrors the internal `TableStructureOperation` variants on a stable
1178
+ * kebab-case `kind` discriminator.
1179
+ *
1180
+ * Stability: additive. New ops land as new `kind` values; existing
1181
+ * variants keep the same shape across minor versions.
1182
+ */
1183
+ export type TableOp =
1184
+ | { kind: "insert"; rows: number; columns: number }
1185
+ | { kind: "delete-table" }
1186
+ | { kind: "add-row-before" }
1187
+ | { kind: "add-row-after" }
1188
+ | { kind: "add-column-before" }
1189
+ | { kind: "add-column-after" }
1190
+ | { kind: "delete-row" }
1191
+ | { kind: "delete-column" }
1192
+ | { kind: "merge-cells" }
1193
+ | { kind: "split-cell" }
1194
+ | { kind: "set-cell-background"; color: string }
1195
+ // Table-level (P2g)
1196
+ | { kind: "set-table-width"; width: PublicTableWidth }
1197
+ | { kind: "set-table-alignment"; alignment: "left" | "center" | "right" }
1198
+ | { kind: "set-table-indent"; indent: PublicTableWidth }
1199
+ | { kind: "set-table-layout-mode"; mode: "fixed" | "autofit" }
1200
+ | { kind: "set-table-cell-margins"; margins: Partial<PublicTableCellMargins> }
1201
+ | { kind: "set-table-borders"; borders: Partial<PublicTableBorders> }
1202
+ | { kind: "set-table-style"; styleId: string | null }
1203
+ | { kind: "set-table-caption"; caption: string | null }
1204
+ | { kind: "set-table-description"; description: string | null }
1205
+ // Column / row sizing + row props (P2h)
1206
+ | { kind: "set-column-width"; columnIndex: number; twips: number }
1207
+ | {
1208
+ kind: "distribute-columns-evenly";
1209
+ columnRange?: { from: number; to: number };
1210
+ }
1211
+ | {
1212
+ kind: "set-row-height";
1213
+ rowIndex: number;
1214
+ twips: number;
1215
+ rule: "auto" | "atLeast" | "exact";
1216
+ }
1217
+ | { kind: "set-row-cant-split"; rowIndex: number; value: boolean }
1218
+ | { kind: "set-row-is-header"; rowIndex: number; value: boolean }
1219
+ | {
1220
+ kind: "set-row-alignment";
1221
+ rowIndex: number;
1222
+ alignment: "left" | "center" | "right";
1223
+ }
1224
+ | {
1225
+ kind: "insert-rows";
1226
+ rowIndex: number;
1227
+ at: "before" | "after";
1228
+ count: number;
1229
+ }
1230
+ | {
1231
+ kind: "insert-columns";
1232
+ columnIndex: number;
1233
+ at: "before" | "after";
1234
+ count: number;
1235
+ widths?: readonly number[];
1236
+ }
1237
+ // Cell-level (P2i)
1238
+ | {
1239
+ kind: "set-cell-borders";
1240
+ locator: PublicCellLocator;
1241
+ borders: Partial<PublicTableCellBorders>;
1242
+ }
1243
+ | {
1244
+ kind: "set-cell-shading";
1245
+ locator: PublicCellLocator;
1246
+ shading: Partial<PublicCellShading> | null;
1247
+ }
1248
+ | { kind: "clear-cell-shading"; locator: PublicCellLocator }
1249
+ | {
1250
+ kind: "set-cell-margins";
1251
+ locator: PublicCellLocator;
1252
+ margins: Partial<PublicTableCellMargins>;
1253
+ }
1254
+ | {
1255
+ kind: "set-cell-vertical-align";
1256
+ locator: PublicCellLocator;
1257
+ align: "top" | "center" | "bottom";
1258
+ }
1259
+ | {
1260
+ kind: "set-cell-text-direction";
1261
+ locator: PublicCellLocator;
1262
+ direction: "lrTb" | "tbRl" | "btLr";
1263
+ }
1264
+ | { kind: "set-cell-no-wrap"; locator: PublicCellLocator; value: boolean }
1265
+ | { kind: "set-cell-fit-text"; locator: PublicCellLocator; value: boolean };
1266
+
1267
+ /**
1268
+ * Outcome of `ref.tables.apply(op)`.
1269
+ */
1270
+ export interface TableOpResult {
1271
+ /** Whether the op mutated canonical document state. */
1272
+ changed: boolean;
1273
+ /**
1274
+ * When the op was refused (e.g. no table selection, merge not clean),
1275
+ * a short human-readable reason. `null` when the op committed.
1276
+ */
1277
+ coercedReason: string | null;
1278
+ /**
1279
+ * Fresh capability snapshot post-op. `null` when there is no table in
1280
+ * the current selection.
1281
+ */
1282
+ capabilities: TableStructureContextSnapshot | null;
1283
+ }
1284
+
1285
+ /**
1286
+ * The `tables` property on `WordReviewEditorRef`. Minimal surface in this
1287
+ * phase: a typed dispatch boundary for every table op plus a capability
1288
+ * read. Read-only geometry queries (columns, rows, render plan, ...)
1289
+ * land with the layout engine + render kernel integration.
1290
+ */
1291
+ export interface WordReviewEditorTablesFacet {
1292
+ /** Dispatch a typed table op through the runtime. */
1293
+ apply(op: TableOp): TableOpResult;
1294
+ /** Current capability snapshot for the active table selection. */
1295
+ getCapabilities(): TableStructureContextSnapshot | null;
1296
+ }
1297
+
1042
1298
  export interface PageRegionHitTest {
1043
1299
  region: "body" | "header" | "footer" | "margin" | "gutter";
1044
1300
  sectionIndex: number;
@@ -1067,8 +1323,50 @@ export interface EditorViewStateSnapshot {
1067
1323
  activeObjectFrame: LayoutMeasurement["objectFrame"] | null;
1068
1324
  measurement: LayoutMeasurement;
1069
1325
  isFocused: boolean;
1326
+ /**
1327
+ * Role-scoped chrome dimension (spec §6.4). Drives which action set the
1328
+ * top toolbar renders: `"editor"` exposes authoring (insert, format,
1329
+ * Mark-section menu, tracked-changes toggle), `"review"` exposes review
1330
+ * queue nav + accept/reject, `"workflow"` exposes work-item traversal.
1331
+ * Independent of `viewMode` / `documentMode`.
1332
+ */
1333
+ editorRole: EditorRole;
1334
+ /**
1335
+ * Pin state for detachable chrome surfaces — topnav and the selection
1336
+ * tier tool. Absent key means "docked default"; `detached: true` means
1337
+ * the surface renders as a floating, user-positioned panel. State is
1338
+ * runtime-owned, per-session, and survives snapshot rebuilds.
1339
+ */
1340
+ chromePins: ChromePinsState;
1341
+ }
1342
+
1343
+ /**
1344
+ * Role-scoped chrome dimension (spec §6.4 of runtime-rendering-and-chrome-phase.md).
1345
+ *
1346
+ * - `"editor"` — authoring posture. Toolbar surfaces format, insert menu,
1347
+ * Mark-section posture menu, tracked-changes display toggle, comment.
1348
+ * - `"review"` — reviewing posture. Toolbar surfaces prev/next, accept,
1349
+ * reject, accept-all / reject-all in scope, markup mode, comment.
1350
+ * - `"workflow"` — workflow-actor posture. Toolbar surfaces prev/next
1351
+ * work item, claim, skip, mark complete, mark blocked, jump to scope.
1352
+ */
1353
+ export type EditorRole = "editor" | "review" | "workflow";
1354
+
1355
+ /**
1356
+ * Chrome surfaces that can be detached into a floating, draggable panel.
1357
+ * Extend this union as new surfaces adopt the `DraggableFloat` primitive.
1358
+ */
1359
+ export type ChromePinSurface = "topnav" | "selectionTier";
1360
+
1361
+ export interface PinState {
1362
+ /** True when the surface is detached from its default dock. */
1363
+ detached: boolean;
1364
+ /** User-chosen offset in CSS px from the surface's dock origin. */
1365
+ offset: { x: number; y: number };
1070
1366
  }
1071
1367
 
1368
+ export type ChromePinsState = Partial<Record<ChromePinSurface, PinState>>;
1369
+
1072
1370
  export interface CommandStateSnapshot {
1073
1371
  canUndo: boolean;
1074
1372
  canRedo: boolean;
@@ -1952,13 +2250,33 @@ export interface WordReviewEditorRef {
1952
2250
  * event has fired, the facet is always available.
1953
2251
  */
1954
2252
  readonly layout: WordReviewEditorLayoutFacet;
2253
+ /**
2254
+ * Typed dispatch boundary for every table operation the runtime
2255
+ * supports. Existing flat verbs (`addRowBefore`, `setCellBackground`,
2256
+ * …) remain as compatibility wrappers; new ops land through
2257
+ * `ref.tables.apply(op)` with a typed discriminated union.
2258
+ */
2259
+ readonly tables: WordReviewEditorTablesFacet;
1955
2260
  }
1956
2261
 
2262
+ /**
2263
+ * Density + role-scoped chrome preset.
2264
+ *
2265
+ * Historically a pure density knob (`selection` → `simple` → `advanced` →
2266
+ * `review` progressively exposed more chrome). `"workflow"` was added in
2267
+ * chrome-phase R1 to surface workflow-actor primary actions (prev/next
2268
+ * work item, claim, skip, mark complete, jump to scope) inline in the
2269
+ * top toolbar. Host apps that already use `chromePreset` keep working
2270
+ * unchanged; consumers that want the new workflow view set the preset
2271
+ * to `"workflow"`. For finer-grained role control independent of
2272
+ * density, set `editorRole` on the view state directly.
2273
+ */
1957
2274
  export type WordReviewEditorChromePreset =
1958
2275
  | "selection"
1959
2276
  | "simple"
1960
2277
  | "advanced"
1961
- | "review";
2278
+ | "review"
2279
+ | "workflow";
1962
2280
 
1963
2281
  export interface WordReviewEditorChromeOptions {
1964
2282
  showReviewQueueBar: boolean;
@@ -21,6 +21,10 @@ import type {
21
21
  SectionBreakNode,
22
22
  SectionProperties,
23
23
  } from "../../model/canonical-document.ts";
24
+ import type {
25
+ MarginPresetDefinition,
26
+ PageFormatDefinition,
27
+ } from "../../runtime/layout/index.ts";
24
28
 
25
29
  // ---- Public types ----
26
30
 
@@ -57,6 +61,60 @@ export interface SectionLinkPatch {
57
61
  relationshipId?: string | null;
58
62
  }
59
63
 
64
+ // ---- Page-format / margin-preset patch builders (R0.5) ----
65
+
66
+ /**
67
+ * Build a `SectionLayoutPatch` that swaps the page size to a named format
68
+ * at the requested orientation. Width/height are swapped when orientation
69
+ * is `"landscape"` so the caller does not have to think about which twip
70
+ * goes on which axis.
71
+ *
72
+ * `custom` formats carry `portraitWidthTwips === 0`; callers should pass
73
+ * their desired twips through `SectionLayoutPatch.pageSize` directly in
74
+ * that case rather than going through this helper.
75
+ */
76
+ export function buildPageFormatPatch(
77
+ format: PageFormatDefinition,
78
+ orientation: "portrait" | "landscape",
79
+ ): SectionLayoutPatch {
80
+ if (format.portraitWidthTwips <= 0 || format.portraitHeightTwips <= 0) {
81
+ return {};
82
+ }
83
+ const [width, height] =
84
+ orientation === "landscape"
85
+ ? [format.portraitHeightTwips, format.portraitWidthTwips]
86
+ : [format.portraitWidthTwips, format.portraitHeightTwips];
87
+ return {
88
+ pageSize: {
89
+ width,
90
+ height,
91
+ orientation,
92
+ },
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Build a `SectionLayoutPatch` that applies a named margin preset to the
98
+ * section. `custom` presets carry zero values and are a no-op here; the
99
+ * caller should pass explicit twips through `SectionLayoutPatch.pageMargins`.
100
+ */
101
+ export function buildMarginPresetPatch(
102
+ preset: MarginPresetDefinition,
103
+ ): SectionLayoutPatch {
104
+ if (preset.id === "custom") {
105
+ return {};
106
+ }
107
+ return {
108
+ pageMargins: {
109
+ top: preset.topTwips,
110
+ bottom: preset.bottomTwips,
111
+ left: preset.leftTwips,
112
+ right: preset.rightTwips,
113
+ gutter: preset.gutterTwips,
114
+ },
115
+ };
116
+ }
117
+
60
118
  // ---- Insert section break ----
61
119
 
62
120
  /**