@beyondwork/docx-react-component 1.0.57 → 1.0.59

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 (135) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -1
  3. package/src/api/awareness-identity-types.ts +4 -2
  4. package/src/api/comment-negotiation-types.ts +4 -1
  5. package/src/api/external-custody-types.ts +16 -0
  6. package/src/api/internal/build-ref-projections.ts +108 -0
  7. package/src/api/package-version.ts +1 -1
  8. package/src/api/participants-types.ts +11 -1
  9. package/src/api/public-types.ts +1149 -8
  10. package/src/api/scope-metadata-resolver-types.ts +6 -0
  11. package/src/compare/diff-engine.ts +3 -0
  12. package/src/core/commands/formatting-commands.ts +1 -0
  13. package/src/core/commands/index.ts +225 -16
  14. package/src/core/commands/legacy-form-field-commands.ts +181 -0
  15. package/src/core/commands/table-structure-commands.ts +149 -31
  16. package/src/core/selection/mapping.ts +20 -0
  17. package/src/core/state/editor-state.ts +2 -1
  18. package/src/index.ts +28 -0
  19. package/src/io/docx-session.ts +22 -3
  20. package/src/io/export/export-session.ts +11 -7
  21. package/src/io/export/ooxml-namespaces.ts +47 -0
  22. package/src/io/export/reattach-preserved-parts.ts +4 -16
  23. package/src/io/export/serialize-comments.ts +3 -131
  24. package/src/io/export/serialize-ffdata.ts +89 -0
  25. package/src/io/export/serialize-headers-footers.ts +5 -0
  26. package/src/io/export/serialize-main-document.ts +224 -34
  27. package/src/io/export/serialize-numbering.ts +22 -2
  28. package/src/io/export/serialize-revisions.ts +99 -0
  29. package/src/io/export/serialize-tables.ts +9 -0
  30. package/src/io/export/split-review-boundaries.ts +1 -0
  31. package/src/io/export/table-properties-xml.ts +14 -0
  32. package/src/io/load-scheduler.ts +70 -28
  33. package/src/io/normalize/normalize-text.ts +13 -0
  34. package/src/io/ooxml/_mini-xml.ts +198 -0
  35. package/src/io/ooxml/canonicalize-payload.ts +1 -4
  36. package/src/io/ooxml/chart/chart-style-table.ts +4 -3
  37. package/src/io/ooxml/chart/parse-chart-space.ts +2 -4
  38. package/src/io/ooxml/chart/parse-series.ts +2 -1
  39. package/src/io/ooxml/chart/resolve-color.ts +2 -2
  40. package/src/io/ooxml/chart/types.ts +6 -434
  41. package/src/io/ooxml/comment-presentation-payload.ts +6 -5
  42. package/src/io/ooxml/highlight-colors.ts +8 -5
  43. package/src/io/ooxml/parse-anchor.ts +68 -53
  44. package/src/io/ooxml/parse-comments.ts +14 -142
  45. package/src/io/ooxml/parse-complex-content.ts +3 -106
  46. package/src/io/ooxml/parse-drawing.ts +100 -195
  47. package/src/io/ooxml/parse-ffdata.ts +93 -0
  48. package/src/io/ooxml/parse-fields.ts +7 -146
  49. package/src/io/ooxml/parse-fill.ts +88 -8
  50. package/src/io/ooxml/parse-font-table.ts +5 -105
  51. package/src/io/ooxml/parse-footnotes.ts +28 -152
  52. package/src/io/ooxml/parse-headers-footers.ts +106 -212
  53. package/src/io/ooxml/parse-inline-media.ts +3 -200
  54. package/src/io/ooxml/parse-main-document.ts +180 -217
  55. package/src/io/ooxml/parse-numbering.ts +154 -335
  56. package/src/io/ooxml/parse-object.ts +147 -0
  57. package/src/io/ooxml/parse-ole-relationship.ts +82 -0
  58. package/src/io/ooxml/parse-paragraph-formatting.ts +7 -10
  59. package/src/io/ooxml/parse-picture-sdt.ts +85 -0
  60. package/src/io/ooxml/parse-picture.ts +120 -39
  61. package/src/io/ooxml/parse-revisions.ts +285 -51
  62. package/src/io/ooxml/parse-settings.ts +6 -99
  63. package/src/io/ooxml/parse-shapes.ts +25 -140
  64. package/src/io/ooxml/parse-styles.ts +3 -218
  65. package/src/io/ooxml/parse-tables.ts +76 -256
  66. package/src/io/ooxml/parse-theme.ts +1 -4
  67. package/src/io/ooxml/property-grab-bag.ts +5 -47
  68. package/src/io/ooxml/xml-element-serialize.ts +32 -0
  69. package/src/io/ooxml/xml-parser.ts +183 -0
  70. package/src/legal/bookmarks.ts +1 -1
  71. package/src/legal/cross-references.ts +1 -1
  72. package/src/legal/defined-terms.ts +1 -1
  73. package/src/legal/{_document-root.ts → document-root.ts} +8 -0
  74. package/src/legal/signature-blocks.ts +1 -1
  75. package/src/model/canonical-document.ts +165 -6
  76. package/src/model/chart-types.ts +439 -0
  77. package/src/model/snapshot.ts +3 -1
  78. package/src/review/store/comment-remapping.ts +24 -11
  79. package/src/review/store/revision-actions.ts +482 -2
  80. package/src/review/store/revision-store.ts +15 -0
  81. package/src/review/store/revision-types.ts +76 -0
  82. package/src/runtime/collab/remote-cursor-awareness.ts +24 -0
  83. package/src/runtime/collab/runtime-collab-sync.ts +33 -0
  84. package/src/runtime/diagnostics/build-diagnostic.ts +151 -0
  85. package/src/runtime/diagnostics/code-metadata-table.ts +221 -0
  86. package/src/runtime/document-runtime.ts +544 -35
  87. package/src/runtime/document-search.ts +176 -0
  88. package/src/runtime/edit-ops/index.ts +18 -2
  89. package/src/runtime/footnote-resolver.ts +130 -0
  90. package/src/runtime/layout/layout-engine-instance.ts +31 -4
  91. package/src/runtime/layout/layout-engine-version.ts +37 -1
  92. package/src/runtime/layout/page-graph.ts +14 -1
  93. package/src/runtime/layout/resolved-formatting-state.ts +21 -0
  94. package/src/runtime/numbering-prefix.ts +17 -0
  95. package/src/runtime/query-scopes.ts +183 -0
  96. package/src/runtime/resolved-numbering-geometry.ts +37 -6
  97. package/src/runtime/revision-runtime.ts +27 -1
  98. package/src/runtime/scope-resolver.ts +60 -0
  99. package/src/runtime/selection/post-edit-validator.ts +60 -6
  100. package/src/runtime/structure-ops/index.ts +20 -4
  101. package/src/runtime/surface-projection.ts +293 -18
  102. package/src/runtime/table-schema.ts +6 -0
  103. package/src/runtime/theme-color-resolver.ts +2 -2
  104. package/src/runtime/units.ts +9 -0
  105. package/src/runtime/workflow-rail-segments.ts +4 -0
  106. package/src/ui/WordReviewEditor.tsx +258 -44
  107. package/src/ui/editor-runtime-boundary.ts +13 -0
  108. package/src/ui/editor-shell-view.tsx +4 -1
  109. package/src/ui/headless/chrome-registry.ts +53 -0
  110. package/src/ui/headless/selection-tool-resolver.ts +11 -1
  111. package/src/ui-tailwind/chrome/chrome-preset-model.ts +13 -0
  112. package/src/ui-tailwind/chrome/tw-command-palette-mount.tsx +96 -0
  113. package/src/ui-tailwind/chrome/tw-context-menu.tsx +2 -1
  114. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +5 -4
  115. package/src/ui-tailwind/chrome/tw-mode-dock.tsx +6 -2
  116. package/src/ui-tailwind/chrome/use-container-breakpoint.ts +111 -0
  117. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +23 -9
  118. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +158 -0
  119. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +6 -7
  120. package/src/ui-tailwind/editor-surface/pm-schema.ts +105 -17
  121. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +13 -0
  122. package/src/ui-tailwind/editor-surface/shape-renderer.ts +76 -14
  123. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +18 -1
  124. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +2 -0
  125. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +18 -2
  126. package/src/ui-tailwind/index.ts +9 -0
  127. package/src/ui-tailwind/page-chrome-model.ts +77 -5
  128. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +56 -1
  129. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +2 -0
  130. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +116 -113
  131. package/src/ui-tailwind/review/tw-review-rail-footer.tsx +2 -2
  132. package/src/ui-tailwind/theme/tokens.ts +14 -0
  133. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +5 -0
  134. package/src/ui-tailwind/tw-review-workspace.tsx +52 -87
  135. package/src/validation/diagnostics.ts +1 -0
@@ -16,6 +16,7 @@
16
16
  * and any future OOXML import path can share the same implementation.
17
17
  */
18
18
  import type { XmlElementNode } from "./xml-element.ts";
19
+ import { localName } from "./xml-attr-helpers.ts";
19
20
 
20
21
  const ROOT_TAG = "__root__";
21
22
 
@@ -112,6 +113,188 @@ export function parseXml(xml: string): XmlElementNode {
112
113
  return root;
113
114
  }
114
115
 
116
+ /**
117
+ * Like `parseXml` but records byte-offset positions (`start`, `end`) on every
118
+ * produced element and text node. Required by parsers that need to re-slice the
119
+ * source string to extract raw XML fragments (e.g. `rawXml` preservation,
120
+ * `sourceXml.slice(node.start, node.end)` patterns).
121
+ *
122
+ * The canonical `parseXml` above is lighter and preferred when position
123
+ * information is not needed.
124
+ */
125
+ export function parseXmlWithOffsets(xml: string): XmlElementNode {
126
+ const root: XmlElementNode = {
127
+ type: "element",
128
+ name: "__root__",
129
+ attributes: {},
130
+ children: [],
131
+ start: 0,
132
+ end: xml.length,
133
+ };
134
+ const stack: XmlElementNode[] = [root];
135
+ let cursor = 0;
136
+
137
+ while (cursor < xml.length) {
138
+ if (xml.startsWith("<!--", cursor)) {
139
+ const end = xml.indexOf("-->", cursor);
140
+ cursor = end >= 0 ? end + 3 : xml.length;
141
+ continue;
142
+ }
143
+
144
+ if (xml.startsWith("<?", cursor)) {
145
+ const end = xml.indexOf("?>", cursor);
146
+ cursor = end >= 0 ? end + 2 : xml.length;
147
+ continue;
148
+ }
149
+
150
+ if (xml.startsWith("<![CDATA[", cursor)) {
151
+ const end = xml.indexOf("]]>", cursor);
152
+ const textEnd = end >= 0 ? end : xml.length;
153
+ stack[stack.length - 1]?.children.push({
154
+ type: "text",
155
+ text: xml.slice(cursor + 9, textEnd),
156
+ start: cursor,
157
+ end: end >= 0 ? end + 3 : xml.length,
158
+ });
159
+ cursor = end >= 0 ? end + 3 : xml.length;
160
+ continue;
161
+ }
162
+
163
+ if (xml[cursor] !== "<") {
164
+ const nextTag = xml.indexOf("<", cursor);
165
+ const end = nextTag >= 0 ? nextTag : xml.length;
166
+ const text = decodeXmlEntities(xml.slice(cursor, end));
167
+ if (text.length > 0) {
168
+ stack[stack.length - 1]?.children.push({ type: "text", text, start: cursor, end });
169
+ }
170
+ cursor = end;
171
+ continue;
172
+ }
173
+
174
+ if (xml[cursor + 1] === "/") {
175
+ const end = xml.indexOf(">", cursor);
176
+ if (end < 0) {
177
+ throw new Error("Malformed XML: missing closing >.");
178
+ }
179
+ const name = xml.slice(cursor + 2, end).trim();
180
+ const current = stack.pop();
181
+ if (!current || localName(current.name) !== localName(name)) {
182
+ throw new Error(`Malformed XML: unexpected closing tag </${name}>.`);
183
+ }
184
+ current.end = end + 1;
185
+ cursor = end + 1;
186
+ continue;
187
+ }
188
+
189
+ const tagEnd = findTagEndWithOffsets(xml, cursor);
190
+ const tagBody = xml.slice(cursor + 1, tagEnd);
191
+ const selfClosing = /\/\s*$/.test(tagBody);
192
+ const { name, attributes: tagAttributes } = parseTagWithOffsets(
193
+ tagBody.replace(/\/\s*$/, "").trim(),
194
+ );
195
+ const element: XmlElementNode = {
196
+ type: "element",
197
+ name,
198
+ attributes: tagAttributes,
199
+ children: [],
200
+ start: cursor,
201
+ end: tagEnd + 1,
202
+ };
203
+ stack[stack.length - 1]?.children.push(element);
204
+
205
+ if (!selfClosing) {
206
+ stack.push(element);
207
+ }
208
+
209
+ cursor = tagEnd + 1;
210
+ }
211
+
212
+ if (stack.length !== 1) {
213
+ throw new Error("Malformed XML: unclosed element.");
214
+ }
215
+
216
+ return root;
217
+ }
218
+
219
+
220
+ function parseTagWithOffsets(
221
+ tagBody: string,
222
+ ): { name: string; attributes: Record<string, string> } {
223
+ let cursor = 0;
224
+ while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) {
225
+ cursor += 1;
226
+ }
227
+ const nameStart = cursor;
228
+ while (cursor < tagBody.length && !/\s/.test(tagBody[cursor] ?? "")) {
229
+ cursor += 1;
230
+ }
231
+ const name = tagBody.slice(nameStart, cursor);
232
+ const attributes: Record<string, string> = {};
233
+
234
+ while (cursor < tagBody.length) {
235
+ while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) {
236
+ cursor += 1;
237
+ }
238
+ if (cursor >= tagBody.length) break;
239
+ const keyStart = cursor;
240
+ while (cursor < tagBody.length && !/[\s=]/.test(tagBody[cursor] ?? "")) {
241
+ cursor += 1;
242
+ }
243
+ const key = tagBody.slice(keyStart, cursor);
244
+ while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) {
245
+ cursor += 1;
246
+ }
247
+ if (tagBody[cursor] !== "=") {
248
+ attributes[key] = "";
249
+ continue;
250
+ }
251
+ cursor += 1;
252
+ while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) {
253
+ cursor += 1;
254
+ }
255
+ const quote = tagBody[cursor];
256
+ if (quote !== `"` && quote !== `'`) {
257
+ throw new Error(`Malformed XML attribute ${key}.`);
258
+ }
259
+ cursor += 1;
260
+ const valueStart = cursor;
261
+ while (cursor < tagBody.length && tagBody[cursor] !== quote) {
262
+ cursor += 1;
263
+ }
264
+ attributes[key] = decodeXmlEntities(tagBody.slice(valueStart, cursor));
265
+ cursor += 1;
266
+ }
267
+
268
+ return { name, attributes };
269
+ }
270
+
271
+ function findTagEndWithOffsets(xml: string, start: number): number {
272
+ let cursor = start + 1;
273
+ let quote: string | null = null;
274
+
275
+ while (cursor < xml.length) {
276
+ const current = xml[cursor];
277
+ if (quote) {
278
+ if (current === quote) {
279
+ quote = null;
280
+ }
281
+ cursor += 1;
282
+ continue;
283
+ }
284
+ if (current === `"` || current === `'`) {
285
+ quote = current;
286
+ cursor += 1;
287
+ continue;
288
+ }
289
+ if (current === ">") {
290
+ return cursor;
291
+ }
292
+ cursor += 1;
293
+ }
294
+
295
+ throw new Error("Malformed XML: missing >.");
296
+ }
297
+
115
298
  /** Decode the standard XML entity references plus numeric character references. */
116
299
  export function decodeXmlEntities(text: string): string {
117
300
  return text
@@ -7,7 +7,7 @@ import type {
7
7
  FieldRegistry,
8
8
  FieldRegistryEntry,
9
9
  } from "../model/canonical-document.ts";
10
- import { resolveWalkableRoot } from "./_document-root.ts";
10
+ import { resolveWalkableRoot } from "./document-root.ts";
11
11
 
12
12
  export interface LegalBookmark {
13
13
  bookmarkId: string;
@@ -12,7 +12,7 @@ import type {
12
12
  ParagraphNode,
13
13
  TocEntry,
14
14
  } from "../model/canonical-document.ts";
15
- import { resolveWalkableRoot } from "./_document-root.ts";
15
+ import { resolveWalkableRoot } from "./document-root.ts";
16
16
 
17
17
  export interface CrossReferencePattern {
18
18
  kind: "section" | "clause" | "article" | "schedule" | "exhibit" | "appendix";
@@ -4,7 +4,7 @@ import type {
4
4
  DocumentNode,
5
5
  ParagraphNode,
6
6
  } from "../model/canonical-document.ts";
7
- import { resolveWalkableRoot } from "./_document-root.ts";
7
+ import { resolveWalkableRoot } from "./document-root.ts";
8
8
 
9
9
  export interface DefinedTermOccurrence {
10
10
  paragraphIndex: number;
@@ -4,6 +4,14 @@ import type {
4
4
  DocumentRootNode,
5
5
  } from "../model/canonical-document.ts";
6
6
 
7
+ // Compile-time guard: CanonicalDocument must never gain a `type` field,
8
+ // because resolveWalkableRoot uses `!("type" in document)` to distinguish
9
+ // a CanonicalDocument envelope from a DocumentNode union member.
10
+ type _CanonicalDocumentHasNoTypeField = "type" extends keyof CanonicalDocument
11
+ ? never
12
+ : true;
13
+ const _: _CanonicalDocumentHasNoTypeField = true;
14
+
7
15
  /**
8
16
  * Accept either a full `CanonicalDocument` (or a Pick of it carrying `content`)
9
17
  * or a raw `DocumentNode`, and return the node to walk.
@@ -4,7 +4,7 @@ import type {
4
4
  DocumentNode,
5
5
  ParagraphNode,
6
6
  } from "../model/canonical-document.ts";
7
- import { resolveWalkableRoot } from "./_document-root.ts";
7
+ import { resolveWalkableRoot } from "./document-root.ts";
8
8
 
9
9
  export interface SignatureBlockCandidate {
10
10
  startIndex: number;
@@ -16,10 +16,10 @@ import type {
16
16
  ScopeMarkerStartNode,
17
17
  ScopeMarkerEndNode,
18
18
  } from "./scope-markers.ts";
19
- import type { ChartModel } from "../io/ooxml/chart/types.ts";
19
+ import type { ChartModel } from "./chart-types.ts";
20
20
 
21
21
  export type { ScopeMarkerStartNode, ScopeMarkerEndNode } from "./scope-markers.ts";
22
- export type { ChartModel } from "../io/ooxml/chart/types.ts";
22
+ export type { ChartModel } from "./chart-types.ts";
23
23
 
24
24
  const CANONICAL_DOCUMENT_TOP_LEVEL_KEYS = [
25
25
  "schemaVersion",
@@ -284,6 +284,7 @@ export interface NumberingLevelOverrideDefinition {
284
284
  paragraphGeometry?: NumberingLevelParagraphGeometry;
285
285
  runProperties?: CanonicalRunFormatting;
286
286
  restartAfterLevel?: number;
287
+ picBulletId?: string;
287
288
  }
288
289
 
289
290
  export interface NumberingInstance {
@@ -555,7 +556,8 @@ export type DocumentNode =
555
556
  | ShapeNode
556
557
  | WordArtNode
557
558
  | VmlShapeNode
558
- | DrawingFrameNode;
559
+ | DrawingFrameNode
560
+ | OleEmbedNode;
559
561
 
560
562
  export interface DocumentRootNode {
561
563
  type: "doc";
@@ -833,6 +835,19 @@ export interface CellShading {
833
835
  fill?: string;
834
836
  color?: string;
835
837
  val?: string;
838
+ /**
839
+ * SOW gap G3 — theme-shading references (§17.3.5). When `themeFill` is set
840
+ * and `fill` is absent or "auto", the runtime resolves the shading via
841
+ * `ThemeColorResolver.resolveWordThemeColor(themeFill, themeFillTint,
842
+ * themeFillShade)`. Fields are kept verbatim so export can round-trip the
843
+ * original `w:shd` element without re-encoding the resolved hex.
844
+ */
845
+ themeFill?: string;
846
+ themeFillTint?: string;
847
+ themeFillShade?: string;
848
+ themeColor?: string;
849
+ themeColorTint?: string;
850
+ themeColorShade?: string;
836
851
  }
837
852
 
838
853
  export interface TableCellMargins {
@@ -1062,6 +1077,41 @@ export interface FieldNode {
1062
1077
  includeNumbering?: boolean;
1063
1078
  includeLevel?: boolean;
1064
1079
  };
1080
+ /** Legacy form-field metadata from w:ffData (textInput, checkBox, ddList). Round-trip only; no UI for MVP. */
1081
+ legacyFormField?: LegacyFormFieldNode;
1082
+ }
1083
+
1084
+ export type LegacyFormFieldKind = "textInput" | "checkBox" | "ddList";
1085
+
1086
+ export interface LegacyFormFieldNode {
1087
+ kind: LegacyFormFieldKind;
1088
+ name?: string;
1089
+ enabled?: boolean;
1090
+ calcOnExit?: boolean;
1091
+ textInput?: {
1092
+ default?: string;
1093
+ maxLength?: number;
1094
+ format?: string;
1095
+ };
1096
+ checkBox?: {
1097
+ size?: number;
1098
+ default?: boolean;
1099
+ checked?: boolean;
1100
+ };
1101
+ ddList?: {
1102
+ default?: number;
1103
+ listEntry?: string[];
1104
+ };
1105
+ /** Verbatim <w:ffData>…</w:ffData> XML for lossless round-trip. */
1106
+ rawXml: string;
1107
+ /**
1108
+ * Phase V.a: set to `true` when typed fields have been mutated via
1109
+ * `setLegacyFormFieldValue` since parse. On serialize, the `rawXml` is
1110
+ * regenerated from the typed fields via `regenerateFFDataRawXml`.
1111
+ * Un-mutated nodes emit the preserved `rawXml` verbatim for lossless
1112
+ * round-trip.
1113
+ */
1114
+ mutated?: boolean;
1065
1115
  }
1066
1116
 
1067
1117
  // ─── Field registry ─────────────────────────────────────────────────────────
@@ -1198,7 +1248,10 @@ export interface SectionProperties {
1198
1248
  */
1199
1249
  export interface FootnoteProperties {
1200
1250
  pos?: "pageBottom" | "beneathText" | "sectEnd" | "docEnd";
1201
- numFmt?: "decimal" | "upperRoman" | "lowerRoman" | "upperLetter" | "lowerLetter" | "chicago" | "none";
1251
+ numFmt?: "decimal" | "upperRoman" | "lowerRoman" | "upperLetter" | "lowerLetter"
1252
+ | "ordinal" | "cardinalText" | "ordinalText" | "hex" | "chicago" | "bullet"
1253
+ | "ideographDigital" | "japaneseCounting" | "arabicAbjad" | "arabicAlpha"
1254
+ | "none";
1202
1255
  numStart?: number;
1203
1256
  numRestart?: "continuous" | "eachSect" | "eachPage";
1204
1257
  }
@@ -1289,7 +1342,8 @@ export type InlineNode =
1289
1342
  | ShapeNode
1290
1343
  | WordArtNode
1291
1344
  | VmlShapeNode
1292
- | DrawingFrameNode;
1345
+ | DrawingFrameNode
1346
+ | OleEmbedNode;
1293
1347
 
1294
1348
  export interface TextNode {
1295
1349
  type: "text";
@@ -1450,10 +1504,50 @@ export interface VmlShapeNode {
1450
1504
  rawXml: string;
1451
1505
  }
1452
1506
 
1507
+ /**
1508
+ * Preserve-only OLE embedded object (Lane 7c Slice 7c.1).
1509
+ *
1510
+ * Captures `<w:object>` subtrees whose child is `<o:OLEObject>` — Excel
1511
+ * ranges, PowerPoint slides, and other OLE-packaged payloads embedded in
1512
+ * the document via an OPC relationship. The original object XML is
1513
+ * retained in `rawXml` and re-emitted verbatim on export; the binary
1514
+ * package part is preserved through the opaque-fragment / package-part
1515
+ * store reached via `relationshipId`.
1516
+ *
1517
+ * Native render is out of scope: this node models the preservation
1518
+ * contract only. Placeholder badge chrome (Lane 6b post-v2.0) will hang
1519
+ * off `{progId, relationshipId}` when the chrome slice activates.
1520
+ *
1521
+ * Non-OLE `<w:object>` elements (legacy VML-wrapped objects without
1522
+ * `<o:OLEObject>`) continue to flow through the generic opaque-fragment
1523
+ * path and do NOT materialize as `OleEmbedNode` — parsers must fall
1524
+ * through when `<o:OLEObject>` is absent.
1525
+ */
1526
+ export interface OleEmbedNode {
1527
+ type: "ole_embed";
1528
+ id: string;
1529
+ progId?: string;
1530
+ embedType: "oleObject";
1531
+ relationshipId: string;
1532
+ metadata: {
1533
+ originalFilename?: string;
1534
+ classId?: string;
1535
+ shapeId?: string;
1536
+ };
1537
+ rawXml: string;
1538
+ }
1539
+
1453
1540
  export interface AnchorGeometry {
1454
1541
  display: "inline" | "floating";
1455
1542
  extent: { widthEmu: number; heightEmu: number };
1456
1543
  wrapMode: "none" | "square" | "tight" | "through" | "topAndBottom";
1544
+ /**
1545
+ * Polygon coords (in OOXML's 21600ths-of-image units) when wrapMode is
1546
+ * "tight" or "through" and `wp:wrapPolygon` is present. First point from
1547
+ * `wp:start`; subsequent points from sequential `wp:lineTo`. Consumed by
1548
+ * Lane 6 P7 float-wrap for proper polygon text flow.
1549
+ */
1550
+ wrapPolygon?: Array<{ x: number; y: number }>;
1457
1551
  positionH?: { relativeFrom: string; align?: string; offset?: number };
1458
1552
  positionV?: { relativeFrom: string; align?: string; offset?: number };
1459
1553
  distMargins?: { top?: number; bottom?: number; left?: number; right?: number };
@@ -1462,22 +1556,66 @@ export interface AnchorGeometry {
1462
1556
  layoutInCell?: boolean;
1463
1557
  allowOverlap?: boolean;
1464
1558
  simplePos?: boolean;
1465
- docPr?: { id: string; name?: string; descr?: string };
1559
+ docPr?: {
1560
+ id: string;
1561
+ name?: string;
1562
+ descr?: string;
1563
+ /** `wp:docPr hidden="1"` — Word UI hides the frame from the user. */
1564
+ hidden?: boolean;
1565
+ };
1566
+ /**
1567
+ * `wp:cNvGraphicFramePr/a:graphicFrameLocks` — object-manipulation locks
1568
+ * consumed by Lane 6 object chrome (disable resize handles / move, etc.).
1569
+ */
1570
+ frameLocks?: {
1571
+ noChangeAspect?: boolean;
1572
+ noResize?: boolean;
1573
+ noMove?: boolean;
1574
+ noRot?: boolean;
1575
+ noSelect?: boolean;
1576
+ noGrp?: boolean;
1577
+ };
1466
1578
  }
1467
1579
 
1468
1580
  export interface PictureContent {
1469
1581
  type: "picture";
1470
1582
  blipRef: string;
1583
+ /**
1584
+ * Phase 4.4 G4 — true when the image references an external URL via
1585
+ * `a:blip r:link` (bytes not in the .docx package) rather than embedded
1586
+ * via `r:embed`. External-linked images can't resolve to mediaId;
1587
+ * `state: "missing"` in surface projection.
1588
+ */
1589
+ isLinked?: boolean;
1471
1590
  /** Resolved media catalog ID (e.g. "media:word/media/image1.png"), populated by parse-drawing. */
1472
1591
  mediaId?: string;
1473
1592
  /** Absolute package path for media catalog lookup. */
1474
1593
  packagePartName?: string;
1475
1594
  srcRect?: { top: number; bottom: number; left: number; right: number };
1476
1595
  stretch?: boolean;
1596
+ /**
1597
+ * Phase 4.5 G7 — `pic:blipFill/a:tile` (tiled image repeat). Mutually
1598
+ * exclusive with `stretch`. tx/ty = offset (EMUs), sx/sy = scale ×1000,
1599
+ * flip = "x"|"y"|"xy", algn = alignment token.
1600
+ */
1601
+ tile?: {
1602
+ tx?: number;
1603
+ ty?: number;
1604
+ sx?: number;
1605
+ sy?: number;
1606
+ flip?: "x" | "y" | "xy";
1607
+ algn?: string;
1608
+ };
1477
1609
  rotation?: number;
1478
1610
  flipH?: boolean;
1479
1611
  flipV?: boolean;
1480
1612
  presetGeom?: string;
1613
+ /** N11.b — DrawingML a:softEdge feather radius in EMU. */
1614
+ softEdgeRadius?: number;
1615
+ /** N11.b — DrawingML a:outerShdw attributes. `dir` is in 60000ths of a degree; `blurRad`/`dist` in EMU. */
1616
+ outerShadow?: { blurRad: number; dist: number; dir: number; color: string; colorType: "srgbClr" | "schemeClr" };
1617
+ /** N11.b — DrawingML a:glow radius in EMU + color. */
1618
+ glow?: { radius: number; color: string; colorType: "srgbClr" | "schemeClr" };
1481
1619
  /** Original w:drawing XML slice, preserved for lossless round-trip serialization. */
1482
1620
  rawXml?: string;
1483
1621
  }
@@ -2264,6 +2402,27 @@ function validateDocumentNode(
2264
2402
  }
2265
2403
  return;
2266
2404
  }
2405
+ case "ole_embed":
2406
+ expectString(record.id, `${path}.id`, issues);
2407
+ expectString(record.relationshipId, `${path}.relationshipId`, issues);
2408
+ expectString(record.rawXml, `${path}.rawXml`, issues);
2409
+ if (record.embedType !== "oleObject") {
2410
+ issues.push({
2411
+ path: `${path}.embedType`,
2412
+ message: "embedType must be 'oleObject'.",
2413
+ });
2414
+ }
2415
+ if (record.progId !== undefined) {
2416
+ expectString(record.progId, `${path}.progId`, issues);
2417
+ }
2418
+ if (record.metadata === undefined || record.metadata === null ||
2419
+ typeof record.metadata !== "object") {
2420
+ issues.push({
2421
+ path: `${path}.metadata`,
2422
+ message: "metadata must be an object.",
2423
+ });
2424
+ }
2425
+ return;
2267
2426
  default:
2268
2427
  issues.push({
2269
2428
  path: `${path}.type`,