@beyondwork/docx-react-component 1.0.56 → 1.0.58

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 (113) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/api/public-types.ts +330 -0
  4. package/src/compare/diff-engine.ts +3 -0
  5. package/src/core/commands/formatting-commands.ts +1 -0
  6. package/src/core/commands/index.ts +17 -11
  7. package/src/core/selection/mapping.ts +18 -1
  8. package/src/core/selection/review-anchors.ts +29 -18
  9. package/src/io/chart-preview-resolver.ts +175 -41
  10. package/src/io/docx-session.ts +57 -2
  11. package/src/io/export/serialize-main-document.ts +82 -0
  12. package/src/io/export/serialize-styles.ts +61 -3
  13. package/src/io/export/table-properties-xml.ts +19 -4
  14. package/src/io/normalize/normalize-text.ts +33 -0
  15. package/src/io/ooxml/parse-anchor.ts +182 -0
  16. package/src/io/ooxml/parse-drawing.ts +319 -0
  17. package/src/io/ooxml/parse-fields.ts +115 -2
  18. package/src/io/ooxml/parse-fill.ts +215 -0
  19. package/src/io/ooxml/parse-font-table.ts +190 -0
  20. package/src/io/ooxml/parse-footnotes.ts +52 -1
  21. package/src/io/ooxml/parse-main-document.ts +241 -1
  22. package/src/io/ooxml/parse-numbering.ts +96 -0
  23. package/src/io/ooxml/parse-picture.ts +158 -0
  24. package/src/io/ooxml/parse-settings.ts +34 -0
  25. package/src/io/ooxml/parse-shapes.ts +87 -0
  26. package/src/io/ooxml/parse-solid-fill.ts +11 -0
  27. package/src/io/ooxml/parse-styles.ts +74 -1
  28. package/src/io/ooxml/parse-theme.ts +60 -0
  29. package/src/io/paste/html-clipboard.ts +449 -0
  30. package/src/io/paste/word-clipboard.ts +5 -1
  31. package/src/legal/_document-root.ts +26 -0
  32. package/src/legal/bookmarks.ts +4 -3
  33. package/src/legal/cross-references.ts +3 -2
  34. package/src/legal/defined-terms.ts +2 -1
  35. package/src/legal/signature-blocks.ts +2 -1
  36. package/src/model/canonical-document.ts +421 -3
  37. package/src/runtime/chart/chart-model-store.ts +73 -10
  38. package/src/runtime/document-runtime.ts +760 -41
  39. package/src/runtime/document-search.ts +61 -0
  40. package/src/runtime/edit-ops/index.ts +129 -0
  41. package/src/runtime/event-refresh-hints.ts +7 -0
  42. package/src/runtime/field-resolver.ts +341 -0
  43. package/src/runtime/footnote-resolver.ts +55 -0
  44. package/src/runtime/hyperlink-color-resolver.ts +13 -10
  45. package/src/runtime/object-grab/index.ts +51 -0
  46. package/src/runtime/paragraph-style-resolver.ts +105 -0
  47. package/src/runtime/query-scopes.ts +186 -0
  48. package/src/runtime/resolved-numbering-geometry.ts +12 -0
  49. package/src/runtime/scope-resolver.ts +60 -0
  50. package/src/runtime/selection/cursor-ops.ts +186 -15
  51. package/src/runtime/selection/index.ts +17 -1
  52. package/src/runtime/structure-ops/index.ts +77 -0
  53. package/src/runtime/styles-cascade.ts +33 -0
  54. package/src/runtime/surface-projection.ts +192 -12
  55. package/src/runtime/theme-color-resolver.ts +189 -44
  56. package/src/runtime/units.ts +46 -0
  57. package/src/runtime/view-state.ts +13 -2
  58. package/src/ui/WordReviewEditor.tsx +239 -11
  59. package/src/ui/editor-runtime-boundary.ts +97 -1
  60. package/src/ui/editor-shell-view.tsx +1 -1
  61. package/src/ui/runtime-shortcut-dispatch.ts +17 -3
  62. package/src/ui-tailwind/chart/ChartSurface.tsx +36 -10
  63. package/src/ui-tailwind/chart/layout/plot-area.ts +120 -45
  64. package/src/ui-tailwind/chart/render/area.tsx +22 -4
  65. package/src/ui-tailwind/chart/render/bar-column.tsx +37 -11
  66. package/src/ui-tailwind/chart/render/bubble.tsx +6 -2
  67. package/src/ui-tailwind/chart/render/combo.tsx +37 -4
  68. package/src/ui-tailwind/chart/render/line.tsx +28 -5
  69. package/src/ui-tailwind/chart/render/pie.tsx +36 -16
  70. package/src/ui-tailwind/chart/render/progressive-render.ts +8 -1
  71. package/src/ui-tailwind/chart/render/scatter.tsx +9 -4
  72. package/src/ui-tailwind/chrome/avatar-initials.ts +15 -0
  73. package/src/ui-tailwind/chrome/tw-comment-preview.tsx +3 -1
  74. package/src/ui-tailwind/chrome/tw-context-menu.tsx +14 -0
  75. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +3 -2
  76. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +30 -11
  77. package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +15 -2
  78. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +1 -1
  79. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +24 -7
  80. package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +31 -12
  81. package/src/ui-tailwind/chrome-overlay/page-border-resolver.ts +211 -0
  82. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +24 -0
  83. package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +74 -0
  84. package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +65 -0
  85. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +157 -0
  86. package/src/ui-tailwind/chrome-overlay/tw-page-border-overlay.tsx +233 -0
  87. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +135 -13
  88. package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +51 -0
  89. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +12 -4
  90. package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +32 -12
  91. package/src/ui-tailwind/chrome-overlay/tw-toc-outline-sidebar.tsx +133 -0
  92. package/src/ui-tailwind/editor-surface/chart-node-view.tsx +49 -10
  93. package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +119 -0
  94. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +236 -9
  95. package/src/ui-tailwind/editor-surface/pm-schema.ts +214 -11
  96. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +32 -2
  97. package/src/ui-tailwind/editor-surface/shape-renderer.ts +206 -0
  98. package/src/ui-tailwind/editor-surface/surface-layer.ts +66 -0
  99. package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +29 -0
  100. package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +7 -1
  101. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +22 -6
  102. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +10 -16
  103. package/src/ui-tailwind/review/tw-health-panel.tsx +0 -25
  104. package/src/ui-tailwind/review/tw-rail-card.tsx +38 -17
  105. package/src/ui-tailwind/review/tw-review-rail.tsx +2 -2
  106. package/src/ui-tailwind/review/tw-revision-sidebar.tsx +5 -12
  107. package/src/ui-tailwind/review/tw-workflow-tab.tsx +2 -2
  108. package/src/ui-tailwind/theme/editor-theme.css +1 -0
  109. package/src/ui-tailwind/theme/tokens.css +6 -0
  110. package/src/ui-tailwind/theme/tokens.ts +10 -0
  111. package/src/ui-tailwind/tw-review-workspace.tsx +23 -0
  112. package/src/validation/compatibility-engine.ts +2 -0
  113. package/src/validation/docx-comment-proof.ts +12 -3
@@ -36,7 +36,7 @@ const CANONICAL_DOCUMENT_TOP_LEVEL_KEYS = [
36
36
  "diagnostics",
37
37
  ] as const;
38
38
 
39
- const CANONICAL_DOCUMENT_OPTIONAL_KEYS = ["subParts", "fieldRegistry"] as const;
39
+ const CANONICAL_DOCUMENT_OPTIONAL_KEYS = ["subParts", "fieldRegistry", "fontTable"] as const;
40
40
 
41
41
  const ID_PATTERNS = {
42
42
  styleId: /^[A-Za-z_][A-Za-z0-9._-]{0,127}$/,
@@ -72,8 +72,11 @@ export interface CanonicalDocument {
72
72
  subParts?: SubPartsCatalog;
73
73
  /** Package-backed field registry for supported field families. */
74
74
  fieldRegistry?: FieldRegistry;
75
+ /** Parsed fontTable.xml. Undefined when the package has no fontTable part. */
76
+ fontTable?: CanonicalFontTable;
75
77
  }
76
78
 
79
+
77
80
  export interface DocumentMetadata {
78
81
  title?: string;
79
82
  subject?: string;
@@ -89,11 +92,34 @@ export interface StylesCatalog {
89
92
  paragraphs: Record<string, ParagraphStyleDefinition>;
90
93
  characters: Record<string, CharacterStyleDefinition>;
91
94
  tables: Record<string, TableStyleDefinition>;
95
+ /**
96
+ * `w:type="numbering"` styles — preserve-only. Word emits these for
97
+ * style-linked list families (List Bullet, List Number). Canonical
98
+ * consumers do not apply numbering via these styles; they survive
99
+ * only for round-trip fidelity.
100
+ */
101
+ numberingStyles?: Record<string, NumberingStyleDefinition>;
92
102
  latentStyles?: Record<string, LatentStyleDefinition>;
93
103
  fromPackage?: boolean;
94
104
  docDefaults?: DocumentDefaults;
95
105
  }
96
106
 
107
+ /** A `<w:style w:type="numbering">` entry. Preserve-only. */
108
+ export interface NumberingStyleDefinition {
109
+ styleId: string;
110
+ displayName: string;
111
+ kind: "numbering";
112
+ isDefault: boolean;
113
+ basedOn?: string;
114
+ aliases?: string[];
115
+ /**
116
+ * The `<w:numId w:val="..."/>` inside the `<w:pPr><w:numPr>` subtree,
117
+ * when present. Consumers that later resolve numbering-type styles to
118
+ * numbering instances (Lane 3a) will use this.
119
+ */
120
+ numberingInstanceId?: string;
121
+ }
122
+
97
123
  export interface ParagraphStyleDefinition {
98
124
  styleId: string;
99
125
  basedOn?: string;
@@ -101,6 +127,17 @@ export interface ParagraphStyleDefinition {
101
127
  outlineLevel?: number;
102
128
  numbering?: ParagraphStyleNumberingReference;
103
129
  displayName: string;
130
+ /**
131
+ * Alternate display names parsed from `<w:aliases w:val="A,B"/>`
132
+ * (ECMA-376 §17.7.4.2). Comma-separated in source; stored as array.
133
+ */
134
+ aliases?: string[];
135
+ /**
136
+ * `<w:autoRedefine/>` — when true, Word re-records paragraph direct
137
+ * formatting into the style definition as the user types (ECMA-376
138
+ * §17.7.4.3). Round-trip preservation only; no render effect.
139
+ */
140
+ autoRedefine?: boolean;
104
141
  kind: "paragraph";
105
142
  isDefault: boolean;
106
143
  paragraphProperties?: CanonicalParagraphFormatting;
@@ -124,6 +161,8 @@ export interface CharacterStyleDefinition {
124
161
  styleId: string;
125
162
  basedOn?: string;
126
163
  displayName: string;
164
+ /** See `ParagraphStyleDefinition.aliases`. */
165
+ aliases?: string[];
127
166
  kind: "character";
128
167
  isDefault: boolean;
129
168
  runProperties?: CanonicalRunFormatting;
@@ -139,6 +178,8 @@ export interface TableStyleDefinition {
139
178
  styleId: string;
140
179
  basedOn?: string;
141
180
  displayName: string;
181
+ /** See `ParagraphStyleDefinition.aliases`. */
182
+ aliases?: string[];
142
183
  kind: "table";
143
184
  isDefault: boolean;
144
185
  formatting?: TableStyleFormatting;
@@ -157,6 +198,42 @@ export interface LatentStyleDefinition {
157
198
  export interface NumberingCatalog {
158
199
  abstractDefinitions: Record<string, AbstractNumberingDefinition>;
159
200
  instances: Record<string, NumberingInstance>;
201
+ /**
202
+ * `<w:numPicBullet>` media-catalog entries keyed by `w:numPicBulletId`.
203
+ * Each entry preserves the raw drawing/pic XML plus a resolved media
204
+ * pointer when the bullet references a relationship-backed image.
205
+ * Consumed by numbering-prefix when `NumberingLevelDefinition.picBulletId`
206
+ * is set (ECMA-376 §17.9.19). Rendering is Lane 6 territory.
207
+ */
208
+ numPicBullets?: Record<string, NumPicBullet>;
209
+ }
210
+
211
+ /**
212
+ * A single `<w:numPicBullet>` entry — picture-bullet image catalog.
213
+ *
214
+ * OOXML allows either a drawingML `<w:drawing>` (modern) or a VML `<w:pict>`
215
+ * (legacy) inside the element. We preserve the raw XML for a byte-identical
216
+ * round-trip and, when possible, resolve the `r:embed` / `r:id` to a media
217
+ * catalog entry so the renderer can look up the bitmap without re-parsing.
218
+ */
219
+ export interface NumPicBullet {
220
+ /** `w:numPicBulletId` attribute value — the lookup key. */
221
+ numPicBulletId: string;
222
+ /** Verbatim XML for the `<w:numPicBullet>` element. */
223
+ rawXml: string;
224
+ /**
225
+ * Resolved media-catalog media id when the bullet's inner image references
226
+ * a relationship we could resolve at parse time. Undefined when the
227
+ * bullet uses an unresolved blipRef or a VML path.
228
+ */
229
+ mediaId?: string;
230
+ /**
231
+ * Bullet cell extent in EMU (English Metric Units) when parsed from the
232
+ * drawing's `wp:extent` — lets the renderer size the bullet image without
233
+ * round-tripping through a new measurement pass. Undefined for VML paths.
234
+ */
235
+ widthEmu?: number;
236
+ heightEmu?: number;
160
237
  }
161
238
 
162
239
  export interface AbstractNumberingDefinition {
@@ -188,6 +265,12 @@ export interface NumberingLevelDefinition {
188
265
  paragraphGeometry?: NumberingLevelParagraphGeometry;
189
266
  runProperties?: CanonicalRunFormatting;
190
267
  restartAfterLevel?: number;
268
+ /**
269
+ * `<w:lvlPicBulletId w:val="N"/>` reference — picks the matching entry
270
+ * from `NumberingCatalog.numPicBullets`. When set, the marker renders as
271
+ * an inline image instead of a text glyph. ECMA-376 §17.9.12.
272
+ */
273
+ picBulletId?: string;
191
274
  }
192
275
 
193
276
  export interface NumberingLevelOverrideDefinition {
@@ -257,9 +340,28 @@ export interface FootnoteDefinition {
257
340
  blocks: BlockNode[];
258
341
  }
259
342
 
343
+ /**
344
+ * Separator and continuation-separator content parsed from the special
345
+ * footnote/endnote entries with `w:type="separator"` and
346
+ * `w:type="continuationSeparator"`.
347
+ *
348
+ * Content is stored as raw inner XML (the run children of the <w:p>) so
349
+ * Lane 3a page-chrome can render the horizontal rule without re-parsing.
350
+ */
351
+ export interface FootnoteSeparators {
352
+ /** Raw XML of the <w:r> children inside the separator paragraph. */
353
+ separatorContent?: string;
354
+ /** Raw XML of the <w:r> children inside the continuationSeparator paragraph. */
355
+ continuationSeparatorContent?: string;
356
+ }
357
+
260
358
  export interface FootnoteCollection {
261
359
  footnotes: Record<string, FootnoteDefinition>;
262
360
  endnotes: Record<string, FootnoteDefinition>;
361
+ /** Separator content parsed from footnotes.xml special entries. */
362
+ footnoteSeparators?: FootnoteSeparators;
363
+ /** Separator content parsed from endnotes.xml special entries. */
364
+ endnoteSeparators?: FootnoteSeparators;
263
365
  }
264
366
 
265
367
  export interface ThemeColorScheme {
@@ -336,6 +438,12 @@ export interface DocumentSettings {
336
438
  * appears in the re-emitted output.
337
439
  */
338
440
  unmodelledSettingsChildren?: string[];
441
+ /**
442
+ * Parsed from `<w:clrSchemeMapping>` in settings.xml. Maps document-level
443
+ * color style slot names to physical clrScheme slots. Absent when the
444
+ * element was not present in the document (identity mapping applies).
445
+ */
446
+ clrSchemeMapping?: ClrSchemeMapping;
339
447
  }
340
448
 
341
449
  export interface SubPartsCatalog {
@@ -346,6 +454,12 @@ export interface SubPartsCatalog {
346
454
  finalSectionProperties?: SectionProperties;
347
455
  resolvedTheme?: ResolvedTheme;
348
456
  settings?: DocumentSettings;
457
+ /**
458
+ * Fully materialized theme for runtime resolution — combines theme1.xml
459
+ * color/font scheme with the clrSchemeMapping from settings.xml.
460
+ * Available after CO1 load wiring lands in docx-session.ts.
461
+ */
462
+ canonicalTheme?: CanonicalTheme;
349
463
  }
350
464
 
351
465
  export interface ResolvedTheme {
@@ -354,6 +468,55 @@ export interface ResolvedTheme {
354
468
  minorFont?: string;
355
469
  }
356
470
 
471
+ /**
472
+ * Document-level color style slot name from `<w:clrSchemeMapping>`.
473
+ * These are the attribute names on the XML element (e.g. `w:bg1`).
474
+ */
475
+ export type ClrSchemeMappingSlot =
476
+ | "bg1" | "bg2" | "t1" | "t2"
477
+ | "accent1" | "accent2" | "accent3" | "accent4" | "accent5" | "accent6"
478
+ | "hlink" | "followedHyperlink";
479
+
480
+ /**
481
+ * Physical slot name in the `<a:clrScheme>` color scheme (theme1.xml).
482
+ */
483
+ export type ThemeColorSlot =
484
+ | "dk1" | "lt1" | "dk2" | "lt2"
485
+ | "accent1" | "accent2" | "accent3" | "accent4" | "accent5" | "accent6"
486
+ | "hlink" | "folHlink";
487
+
488
+ /**
489
+ * Re-maps document-level color style slot names to physical `clrScheme` slot
490
+ * names. Parsed from `<w:clrSchemeMapping>` in settings.xml.
491
+ *
492
+ * Keys are the attribute local names (e.g. "bg1", "t1", "accent1").
493
+ * Values are the target clrScheme slot (e.g. "lt1", "dk1").
494
+ *
495
+ * An absent key means identity mapping — the slot resolves to its own name.
496
+ */
497
+ export type ClrSchemeMapping = Partial<Record<ClrSchemeMappingSlot, ThemeColorSlot>>;
498
+
499
+ /**
500
+ * Fully materialized theme for runtime resolution. Combines the color scheme,
501
+ * optional font scheme, and the clrSchemeMapping from settings.xml.
502
+ *
503
+ * `themeHash` and `clrMapHash` are stable content-derived strings used as
504
+ * cache keys per CLAUDE.md §3 (cache keys must be structural, not revisionToken).
505
+ *
506
+ * Populated during document load when theme1.xml is present. Absent in
507
+ * documents with no theme part.
508
+ */
509
+ export interface CanonicalTheme {
510
+ clrScheme: ThemeColorScheme;
511
+ fontScheme?: ThemeFontScheme;
512
+ /** Resolved clrSchemeMapping: styleSlot → clrScheme slot name. Empty = identity. */
513
+ clrMap: ClrSchemeMapping;
514
+ /** Stable content hash of clrScheme.colors (sorted key:value pairs). */
515
+ themeHash: string;
516
+ /** Stable content hash of clrMap (sorted key:value pairs). */
517
+ clrMapHash: string;
518
+ }
519
+
357
520
  // ---- Inline footnote reference node ----
358
521
 
359
522
  export interface FootnoteRefNode {
@@ -391,7 +554,8 @@ export type DocumentNode =
391
554
  | SmartArtPreviewNode
392
555
  | ShapeNode
393
556
  | WordArtNode
394
- | VmlShapeNode;
557
+ | VmlShapeNode
558
+ | DrawingFrameNode;
395
559
 
396
560
  export interface DocumentRootNode {
397
561
  type: "doc";
@@ -554,6 +718,32 @@ export interface DocumentDefaults {
554
718
  run?: CanonicalRunFormatting;
555
719
  }
556
720
 
721
+ /** Materialized `fontTable.xml` — one entry per `<w:font>` element. */
722
+ export interface CanonicalFontTable {
723
+ fonts: Record<string, CanonicalFontEntry>;
724
+ }
725
+
726
+ /** Metadata for a single `<w:font>` element from fontTable.xml. */
727
+ export interface CanonicalFontEntry {
728
+ /** `w:name` attribute — the key used in rFonts references. */
729
+ name: string;
730
+ /**
731
+ * `w:family` value. Qualitative category per ECMA-376 §17.8.3.10.
732
+ * "roman" = proportional serif, "swiss" = proportional sans-serif,
733
+ * "modern" = fixed-pitch, "script" = handwriting, "decorative" = display.
734
+ */
735
+ family?: "roman" | "swiss" | "modern" | "script" | "decorative";
736
+ /** `w:pitch` value — "fixed" maps to monospace metrics. */
737
+ pitch?: "fixed" | "variable" | "default";
738
+ /**
739
+ * Windows charset code from `w:charset w:val`.
740
+ * 2 = Symbol font (charset override required for correct glyph mapping).
741
+ */
742
+ charset?: number;
743
+ /** `w:altName` — substitution name when the primary font is unavailable. */
744
+ altName?: string;
745
+ }
746
+
557
747
  export interface ParagraphNode {
558
748
  type: "paragraph";
559
749
  styleId?: string;
@@ -691,12 +881,25 @@ export interface TableStyleFormatting {
691
881
  shading?: CellShading;
692
882
  verticalAlign?: "top" | "center" | "bottom";
693
883
  };
884
+ /**
885
+ * `<w:pPr>` directly inside the table style body — applies to every
886
+ * cell paragraph by default. Conditional-region pPr (inside
887
+ * `<w:tblStylePr>`) is not captured here; see Lane 3a follow-up.
888
+ */
889
+ paragraphProperties?: CanonicalParagraphFormatting;
890
+ /**
891
+ * `<w:rPr>` directly inside the table style body — applies to every
892
+ * run in every cell paragraph by default.
893
+ */
894
+ runProperties?: CanonicalRunFormatting;
694
895
  }
695
896
 
696
897
  export interface TableNode {
697
898
  type: "table";
698
899
  styleId?: string;
900
+ /** @deprecated Use `unknownPropertyChildren`. Kept for snapshot back-compat. */
699
901
  propertiesXml?: string;
902
+ unknownPropertyChildren?: UnknownPropertyChild[];
700
903
  gridColumns: number[];
701
904
  rows: TableRowNode[];
702
905
  width?: TableWidth;
@@ -715,7 +918,9 @@ export interface TableNode {
715
918
 
716
919
  export interface TableRowNode {
717
920
  type: "table_row";
921
+ /** @deprecated Use `unknownPropertyChildren`. Kept for snapshot back-compat. */
718
922
  propertiesXml?: string;
923
+ unknownPropertyChildren?: UnknownPropertyChild[];
719
924
  cells: TableCellNode[];
720
925
  gridBefore?: number;
721
926
  widthBefore?: TableWidth;
@@ -731,7 +936,9 @@ export interface TableRowNode {
731
936
 
732
937
  export interface TableCellNode {
733
938
  type: "table_cell";
939
+ /** @deprecated Use `unknownPropertyChildren`. Kept for snapshot back-compat. */
734
940
  propertiesXml?: string;
941
+ unknownPropertyChildren?: UnknownPropertyChild[];
735
942
  gridSpan?: number;
736
943
  verticalMerge?: "restart" | "continue";
737
944
  children: BlockNode[];
@@ -893,6 +1100,8 @@ export interface FieldRegistryEntry {
893
1100
  paragraphIndex: number;
894
1101
  /** Runtime refresh status. */
895
1102
  refreshStatus: FieldRefreshStatus;
1103
+ /** Parsed field switches carried from FieldNode.switches. Present only for REF/PAGEREF/NOTEREF/TOC entries with recognized switches. */
1104
+ switches?: FieldNode["switches"];
896
1105
  }
897
1106
 
898
1107
  /**
@@ -957,6 +1166,10 @@ export interface SectionProperties {
957
1166
  footerReferences?: HeaderFooterReference[];
958
1167
  sectionType?: "continuous" | "nextPage" | "evenPage" | "oddPage" | "nextColumn";
959
1168
  titlePage?: boolean;
1169
+ /** Per-section footnote configuration (ECMA-376 §17.11.12). Overrides file-level defaults. */
1170
+ footnotePr?: FootnoteProperties;
1171
+ /** Per-section endnote configuration (ECMA-376 §17.11.4). Overrides file-level defaults. */
1172
+ endnotePr?: EndnoteProperties;
960
1173
  /**
961
1174
  * Unmodelled direct children of `<w:sectPr>` captured verbatim for
962
1175
  * round-trip. Mirrors `CanonicalParagraphFormatting.unknownPropertyChildren`
@@ -968,6 +1181,35 @@ export interface SectionProperties {
968
1181
  unknownPropertyChildren?: UnknownPropertyChild[];
969
1182
  }
970
1183
 
1184
+ /**
1185
+ * Per-section footnote numbering + placement configuration.
1186
+ *
1187
+ * `pos` — where footnotes render: `pageBottom` (default), `beneathText`
1188
+ * (immediately after content flow), `sectEnd` (at end of this section),
1189
+ * `docEnd` (at end of document — converts to endnote-like behavior).
1190
+ * `numFmt` — numbering glyph format for the footnote marker.
1191
+ * `numStart` — starting number for the first footnote in the restart scope.
1192
+ * `numRestart` — when to reset the footnote counter: `continuous` (never),
1193
+ * `eachSect` (at each section break), `eachPage` (at every page boundary).
1194
+ *
1195
+ * Defined per ECMA-376 §17.11.11–.18. The same child-element shape serves
1196
+ * both `<w:footnotePr>` (inside sectPr or settings.xml) and its endnote
1197
+ * counterpart; `EndnoteProperties` aliases this shape.
1198
+ */
1199
+ export interface FootnoteProperties {
1200
+ pos?: "pageBottom" | "beneathText" | "sectEnd" | "docEnd";
1201
+ numFmt?: "decimal" | "upperRoman" | "lowerRoman" | "upperLetter" | "lowerLetter" | "chicago" | "none";
1202
+ numStart?: number;
1203
+ numRestart?: "continuous" | "eachSect" | "eachPage";
1204
+ }
1205
+
1206
+ /**
1207
+ * Per-section endnote configuration. Structurally identical to
1208
+ * `FootnoteProperties`; kept as a distinct type so future divergence
1209
+ * (e.g. endnote-only switches) has a home without breaking callers.
1210
+ */
1211
+ export type EndnoteProperties = FootnoteProperties;
1212
+
971
1213
  export interface PageSize {
972
1214
  width: number;
973
1215
  height: number;
@@ -1046,7 +1288,8 @@ export type InlineNode =
1046
1288
  | SmartArtPreviewNode
1047
1289
  | ShapeNode
1048
1290
  | WordArtNode
1049
- | VmlShapeNode;
1291
+ | VmlShapeNode
1292
+ | DrawingFrameNode;
1050
1293
 
1051
1294
  export interface TextNode {
1052
1295
  type: "text";
@@ -1207,6 +1450,103 @@ export interface VmlShapeNode {
1207
1450
  rawXml: string;
1208
1451
  }
1209
1452
 
1453
+ export interface AnchorGeometry {
1454
+ display: "inline" | "floating";
1455
+ extent: { widthEmu: number; heightEmu: number };
1456
+ wrapMode: "none" | "square" | "tight" | "through" | "topAndBottom";
1457
+ positionH?: { relativeFrom: string; align?: string; offset?: number };
1458
+ positionV?: { relativeFrom: string; align?: string; offset?: number };
1459
+ distMargins?: { top?: number; bottom?: number; left?: number; right?: number };
1460
+ relativeHeight?: number;
1461
+ behindDoc?: boolean;
1462
+ layoutInCell?: boolean;
1463
+ allowOverlap?: boolean;
1464
+ simplePos?: boolean;
1465
+ docPr?: { id: string; name?: string; descr?: string };
1466
+ }
1467
+
1468
+ export interface PictureContent {
1469
+ type: "picture";
1470
+ blipRef: string;
1471
+ /** Resolved media catalog ID (e.g. "media:word/media/image1.png"), populated by parse-drawing. */
1472
+ mediaId?: string;
1473
+ /** Absolute package path for media catalog lookup. */
1474
+ packagePartName?: string;
1475
+ srcRect?: { top: number; bottom: number; left: number; right: number };
1476
+ stretch?: boolean;
1477
+ rotation?: number;
1478
+ flipH?: boolean;
1479
+ flipV?: boolean;
1480
+ presetGeom?: string;
1481
+ /** N11.b — DrawingML a:softEdge feather radius in EMU. */
1482
+ softEdgeRadius?: number;
1483
+ /** N11.b — DrawingML a:outerShdw attributes. `dir` is in 60000ths of a degree; `blurRad`/`dist` in EMU. */
1484
+ outerShadow?: { blurRad: number; dist: number; dir: number; color: string; colorType: "srgbClr" | "schemeClr" };
1485
+ /** N11.b — DrawingML a:glow radius in EMU + color. */
1486
+ glow?: { radius: number; color: string; colorType: "srgbClr" | "schemeClr" };
1487
+ /** Original w:drawing XML slice, preserved for lossless round-trip serialization. */
1488
+ rawXml?: string;
1489
+ }
1490
+
1491
+ export interface ShapeContent {
1492
+ type: "shape";
1493
+ geometry?: string;
1494
+ /**
1495
+ * Shape fill — solid, gradient, pattern, or none. srgbClr values on solid +
1496
+ * gradient stops + pattern fg/bg are normalized to uppercase hex. schemeClr
1497
+ * tokens are preserved as-is for CO1 theme-color cascade resolution.
1498
+ *
1499
+ * Gradient direction is linear (angle in 60000ths of degree) or path-based
1500
+ * (circle|rect|shape). Pattern uses the raw OOXML `prst` preset token.
1501
+ */
1502
+ fill?:
1503
+ | { kind: "solid"; color: string; colorType: "srgbClr" | "schemeClr" }
1504
+ | { kind: "none" }
1505
+ | {
1506
+ kind: "gradient";
1507
+ stops: Array<{ pos: number; color: string; colorType: "srgbClr" | "schemeClr" }>;
1508
+ direction:
1509
+ | { kind: "linear"; angle: number; scaled?: boolean }
1510
+ | { kind: "path"; path: "circle" | "rect" | "shape" };
1511
+ rotWithShape?: boolean;
1512
+ }
1513
+ | {
1514
+ kind: "pattern";
1515
+ preset: string;
1516
+ fg?: { color: string; colorType: "srgbClr" | "schemeClr" };
1517
+ bg?: { color: string; colorType: "srgbClr" | "schemeClr" };
1518
+ };
1519
+ line?: { color?: string; widthEmu?: number; noLine?: boolean };
1520
+ /** True when the shape's geometry + txbxContent presence make it a text box. */
1521
+ isTextBox?: boolean;
1522
+ /** Raw w:txbxContent XML, preserved for serialization + lossless round-trip. */
1523
+ txbxContentXml?: string;
1524
+ /**
1525
+ * Parsed block-level structure from `w:txbxContent`, populated when a
1526
+ * `blockParser` callback is supplied during parse (CO4 F3.3).
1527
+ *
1528
+ * Type is deliberately structural (`{ type: string; ... }`) rather than
1529
+ * canonical `BlockNode[]` because the recursion stops at the parse layer
1530
+ * before the style + numbering normalization pass that converts
1531
+ * `ParsedBlockNode` → canonical `BlockNode`. Consumers that need the fully
1532
+ * normalized form run normalization on this subtree explicitly. Testing
1533
+ * that `txbxBlocks.length > 0` proves the recursion executed.
1534
+ */
1535
+ txbxBlocks?: ReadonlyArray<{ type: string; [key: string]: unknown }>;
1536
+ rawXml: string;
1537
+ }
1538
+
1539
+ export interface DrawingFrameNode {
1540
+ type: "drawing_frame";
1541
+ anchor: AnchorGeometry;
1542
+ content:
1543
+ | PictureContent
1544
+ | ShapeContent
1545
+ | { type: "chart_preview"; rawXml: string }
1546
+ | { type: "smartart_preview"; rawXml: string }
1547
+ | { type: "opaque"; rawXml: string };
1548
+ }
1549
+
1210
1550
  export interface OpaqueBlockNode {
1211
1551
  type: "opaque_block";
1212
1552
  fragmentId: string;
@@ -1865,6 +2205,71 @@ function validateDocumentNode(
1865
2205
  case "vml_shape":
1866
2206
  expectString(record.rawXml, `${path}.rawXml`, issues);
1867
2207
  return;
2208
+ case "drawing_frame": {
2209
+ const anchor = asPlainObject(record.anchor, `${path}.anchor`, issues);
2210
+ const content = asPlainObject(record.content, `${path}.content`, issues);
2211
+ if (anchor) {
2212
+ const extent = asPlainObject(anchor.extent, `${path}.anchor.extent`, issues);
2213
+ if (anchor.display !== "inline" && anchor.display !== "floating") {
2214
+ issues.push({
2215
+ path: `${path}.anchor.display`,
2216
+ message: "anchor.display must be 'inline' or 'floating'.",
2217
+ });
2218
+ }
2219
+ if (
2220
+ anchor.wrapMode !== "none" &&
2221
+ anchor.wrapMode !== "square" &&
2222
+ anchor.wrapMode !== "tight" &&
2223
+ anchor.wrapMode !== "through" &&
2224
+ anchor.wrapMode !== "topAndBottom"
2225
+ ) {
2226
+ issues.push({
2227
+ path: `${path}.anchor.wrapMode`,
2228
+ message: "anchor.wrapMode must be one of none, square, tight, through, or topAndBottom.",
2229
+ });
2230
+ }
2231
+ if (extent) {
2232
+ if (typeof extent.widthEmu !== "number") {
2233
+ issues.push({
2234
+ path: `${path}.anchor.extent.widthEmu`,
2235
+ message: "anchor.extent.widthEmu must be a number.",
2236
+ });
2237
+ }
2238
+ if (typeof extent.heightEmu !== "number") {
2239
+ issues.push({
2240
+ path: `${path}.anchor.extent.heightEmu`,
2241
+ message: "anchor.extent.heightEmu must be a number.",
2242
+ });
2243
+ }
2244
+ }
2245
+ }
2246
+ if (content) {
2247
+ const contentType = expectString(content.type, `${path}.content.type`, issues);
2248
+ if (
2249
+ contentType !== "picture" &&
2250
+ contentType !== "shape" &&
2251
+ contentType !== "chart_preview" &&
2252
+ contentType !== "smartart_preview" &&
2253
+ contentType !== "opaque"
2254
+ ) {
2255
+ issues.push({
2256
+ path: `${path}.content.type`,
2257
+ message: "drawing_frame.content.type must be picture, shape, chart_preview, smartart_preview, or opaque.",
2258
+ });
2259
+ }
2260
+ if (contentType === "picture") {
2261
+ expectString(content.blipRef, `${path}.content.blipRef`, issues);
2262
+ } else if (
2263
+ contentType === "shape" ||
2264
+ contentType === "chart_preview" ||
2265
+ contentType === "smartart_preview" ||
2266
+ contentType === "opaque"
2267
+ ) {
2268
+ expectString(content.rawXml, `${path}.content.rawXml`, issues);
2269
+ }
2270
+ }
2271
+ return;
2272
+ }
1868
2273
  default:
1869
2274
  issues.push({
1870
2275
  path: `${path}.type`,
@@ -2656,6 +3061,19 @@ function validateDocumentNodeReferences(
2656
3061
  message: "mediaId must reference an existing media catalog item.",
2657
3062
  });
2658
3063
  }
3064
+ } else if (type === "drawing_frame") {
3065
+ const content = asPlainObject(record.content, `${path}.content`, []);
3066
+ if (
3067
+ content &&
3068
+ content.type === "picture" &&
3069
+ typeof content.mediaId === "string" &&
3070
+ !references.mediaIds.has(content.mediaId)
3071
+ ) {
3072
+ issues.push({
3073
+ path: `${path}.content.mediaId`,
3074
+ message: "content.mediaId must reference an existing media catalog item.",
3075
+ });
3076
+ }
2659
3077
  } else if (type === "opaque_inline" || type === "opaque_block") {
2660
3078
  if (typeof record.fragmentId === "string" && !references.fragmentIds.has(record.fragmentId)) {
2661
3079
  issues.push({