@beyondwork/docx-react-component 1.0.59 → 1.0.61

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 (46) hide show
  1. package/package.json +33 -44
  2. package/src/api/public-types.ts +43 -0
  3. package/src/core/state/editor-state.ts +2 -0
  4. package/src/io/docx-session.ts +167 -8
  5. package/src/io/export/serialize-footnotes.ts +36 -5
  6. package/src/io/export/serialize-headers-footers.ts +7 -0
  7. package/src/io/export/serialize-main-document.ts +25 -18
  8. package/src/io/export/serialize-paragraph-formatting.ts +6 -0
  9. package/src/io/export/serialize-settings.ts +130 -3
  10. package/src/io/normalize/normalize-text.ts +8 -4
  11. package/src/io/ooxml/parse-footnotes.ts +11 -0
  12. package/src/io/ooxml/parse-headers-footers.ts +117 -42
  13. package/src/io/ooxml/parse-main-document.ts +20 -8
  14. package/src/io/ooxml/parse-paragraph-formatting.ts +25 -1
  15. package/src/io/ooxml/parse-settings.ts +91 -1
  16. package/src/io/ooxml/workflow-payload.ts +6 -1
  17. package/src/model/canonical-document.ts +36 -2
  18. package/src/model/snapshot.ts +2 -0
  19. package/src/runtime/diagnostics/build-diagnostic.ts +2 -0
  20. package/src/runtime/diagnostics/code-metadata-table.ts +9 -0
  21. package/src/runtime/document-runtime.ts +770 -21
  22. package/src/runtime/footnote-resolver.ts +32 -8
  23. package/src/runtime/layout/layout-engine-version.ts +7 -1
  24. package/src/runtime/layout/measurement-backend-canvas.ts +1 -1
  25. package/src/runtime/layout/measurement-backend-empirical.ts +1 -1
  26. package/src/runtime/layout/paginated-layout-engine.ts +41 -8
  27. package/src/runtime/layout/resolved-formatting-document.ts +11 -9
  28. package/src/runtime/layout/resolved-formatting-state.ts +4 -0
  29. package/src/runtime/numbering-prefix.ts +26 -2
  30. package/src/runtime/query-scopes.ts +103 -2
  31. package/src/runtime/surface-projection.ts +75 -14
  32. package/src/runtime/table-schema.ts +26 -0
  33. package/src/ui/WordReviewEditor.tsx +25 -0
  34. package/src/ui/editor-runtime-boundary.ts +1 -0
  35. package/src/ui/editor-shell-view.tsx +8 -0
  36. package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +514 -0
  37. package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -0
  38. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +55 -6
  39. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -0
  40. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +4 -0
  41. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +9 -1
  42. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +16 -0
  43. package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +319 -0
  44. package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +248 -0
  45. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +4 -0
  46. package/src/ui-tailwind/tw-review-workspace.tsx +54 -3
@@ -132,11 +132,35 @@ function readShading(node: XmlElementNode): ParagraphShading | undefined {
132
132
  const val = node.attributes["w:val"] ?? node.attributes.val;
133
133
  const fill = node.attributes["w:fill"] ?? node.attributes.fill;
134
134
  const color = node.attributes["w:color"] ?? node.attributes.color;
135
- if (!val && !fill && !color) return undefined;
135
+ const themeFill = node.attributes["w:themeFill"] ?? node.attributes.themeFill;
136
+ const themeFillTint = node.attributes["w:themeFillTint"] ?? node.attributes.themeFillTint;
137
+ const themeFillShade = node.attributes["w:themeFillShade"] ?? node.attributes.themeFillShade;
138
+ const themeColor = node.attributes["w:themeColor"] ?? node.attributes.themeColor;
139
+ const themeColorTint = node.attributes["w:themeColorTint"] ?? node.attributes.themeColorTint;
140
+ const themeColorShade = node.attributes["w:themeColorShade"] ?? node.attributes.themeColorShade;
141
+ if (
142
+ !val &&
143
+ !fill &&
144
+ !color &&
145
+ !themeFill &&
146
+ !themeFillTint &&
147
+ !themeFillShade &&
148
+ !themeColor &&
149
+ !themeColorTint &&
150
+ !themeColorShade
151
+ ) {
152
+ return undefined;
153
+ }
136
154
  return {
137
155
  ...(val ? { val } : {}),
138
156
  ...(fill ? { fill } : {}),
139
157
  ...(color ? { color } : {}),
158
+ ...(themeFill ? { themeFill } : {}),
159
+ ...(themeFillTint ? { themeFillTint } : {}),
160
+ ...(themeFillShade ? { themeFillShade } : {}),
161
+ ...(themeColor ? { themeColor } : {}),
162
+ ...(themeColorTint ? { themeColorTint } : {}),
163
+ ...(themeColorShade ? { themeColorShade } : {}),
140
164
  };
141
165
  }
142
166
 
@@ -3,6 +3,7 @@ import type {
3
3
  ClrSchemeMapping,
4
4
  ClrSchemeMappingSlot,
5
5
  DocumentSettings,
6
+ FootnoteProperties,
6
7
  ThemeColorSlot,
7
8
  } from "../../model/canonical-document.ts";
8
9
  import { parseXml } from "./xml-parser.ts";
@@ -51,6 +52,13 @@ export function parseSettingsXml(xml: string): DocumentSettings {
51
52
  const compatPartition = compat ? partitionCompat(compat) : undefined;
52
53
  const rootCompatFlags = readRootCompatFlags(settingsElement);
53
54
  const themeFontLangElement = findChildElementOptional(settingsElement, "themeFontLang");
55
+ const defaultTabStop = readDefaultTabStop(settingsElement);
56
+ const footnotePr = readFootnoteLikeProperties(
57
+ findChildElementOptional(settingsElement, "footnotePr"),
58
+ );
59
+ const endnotePr = readFootnoteLikeProperties(
60
+ findChildElementOptional(settingsElement, "endnotePr"),
61
+ );
54
62
  const clrSchemeMapping = parseClrSchemeMapping(settingsElement);
55
63
  const unmodelled = readUnmodelledSettingsChildren(settingsElement);
56
64
 
@@ -71,6 +79,9 @@ export function parseSettingsXml(xml: string): DocumentSettings {
71
79
  ...(themeFontLangElement
72
80
  ? { themeFontLang: { ...themeFontLangElement.attributes } }
73
81
  : {}),
82
+ ...(defaultTabStop !== undefined ? { defaultTabStop } : {}),
83
+ ...(footnotePr ? { footnotePr } : {}),
84
+ ...(endnotePr ? { endnotePr } : {}),
74
85
  ...(clrSchemeMapping !== undefined ? { clrSchemeMapping } : {}),
75
86
  ...(unmodelled.length > 0 ? { unmodelledSettingsChildren: unmodelled } : {}),
76
87
  };
@@ -86,6 +97,9 @@ const MODELLED_SETTINGS_CHILD_NAMES = new Set<string>([
86
97
  "zoom",
87
98
  "compat",
88
99
  "themeFontLang",
100
+ "defaultTabStop",
101
+ "footnotePr",
102
+ "endnotePr",
89
103
  "clrSchemeMapping",
90
104
  ]);
91
105
 
@@ -130,6 +144,83 @@ function readRootCompatFlags(
130
144
  return flags;
131
145
  }
132
146
 
147
+ function readDefaultTabStop(
148
+ settingsElement: XmlElementNode,
149
+ ): number | undefined {
150
+ const element = findChildElementOptional(settingsElement, "defaultTabStop");
151
+ if (!element) return undefined;
152
+ const rawValue = element.attributes["w:val"] ?? element.attributes.val;
153
+ if (!rawValue) return undefined;
154
+ const parsed = Number.parseInt(rawValue, 10);
155
+ return Number.isFinite(parsed) ? parsed : undefined;
156
+ }
157
+
158
+ function readFootnoteLikeProperties(
159
+ element: XmlElementNode | undefined,
160
+ ): FootnoteProperties | undefined {
161
+ if (!element) return undefined;
162
+
163
+ const result: FootnoteProperties = {};
164
+ for (const child of element.children) {
165
+ if (child.type !== "element") continue;
166
+ const name = localName(child.name);
167
+ const val = child.attributes["w:val"] ?? child.attributes.val;
168
+
169
+ if (name === "pos") {
170
+ if (
171
+ val === "pageBottom" ||
172
+ val === "beneathText" ||
173
+ val === "sectEnd" ||
174
+ val === "docEnd"
175
+ ) {
176
+ result.pos = val;
177
+ }
178
+ continue;
179
+ }
180
+
181
+ if (name === "numFmt") {
182
+ if (
183
+ val === "decimal" ||
184
+ val === "upperRoman" ||
185
+ val === "lowerRoman" ||
186
+ val === "upperLetter" ||
187
+ val === "lowerLetter" ||
188
+ val === "ordinal" ||
189
+ val === "cardinalText" ||
190
+ val === "ordinalText" ||
191
+ val === "hex" ||
192
+ val === "chicago" ||
193
+ val === "bullet" ||
194
+ val === "ideographDigital" ||
195
+ val === "japaneseCounting" ||
196
+ val === "arabicAbjad" ||
197
+ val === "arabicAlpha" ||
198
+ val === "none"
199
+ ) {
200
+ result.numFmt = val;
201
+ }
202
+ continue;
203
+ }
204
+
205
+ if (name === "numStart") {
206
+ const parsed = Number.parseInt(val ?? "", 10);
207
+ if (Number.isFinite(parsed)) {
208
+ result.numStart = Math.max(1, parsed);
209
+ }
210
+ continue;
211
+ }
212
+
213
+ if (
214
+ name === "numRestart" &&
215
+ (val === "continuous" || val === "eachSect" || val === "eachPage")
216
+ ) {
217
+ result.numRestart = val;
218
+ }
219
+ }
220
+
221
+ return Object.keys(result).length > 0 ? result : undefined;
222
+ }
223
+
133
224
  interface CompatPartition {
134
225
  compatSettings: CompatSetting[];
135
226
  compatFlags: Record<string, boolean>;
@@ -218,4 +309,3 @@ function readZoomLevel(
218
309
 
219
310
  return { zoomLevel: parsed };
220
311
  }
221
-
@@ -13,6 +13,7 @@ import type {
13
13
  WorkflowWorkItem,
14
14
  } from "../../api/public-types.ts";
15
15
  import type { EditorStateNamespace, EditorStateLocation } from "../../api/editor-state-types.ts";
16
+ import { sha256Hex } from "../source-package-provenance.ts";
16
17
  import {
17
18
  validateWorkflowPayloadEnvelope,
18
19
  type ValidatorIssue,
@@ -42,6 +43,8 @@ export interface EditorStatePayload {
42
43
  unknownNamespaces?: Array<{ name: string; rawXml: string }>;
43
44
  }
44
45
 
46
+ const workflowAnchorHashEncoder = new TextEncoder();
47
+
45
48
  // ---------------------------------------------------------------------------
46
49
  // Schema 1.1 parser helpers (fail-closed per spec §8.2)
47
50
  // ---------------------------------------------------------------------------
@@ -747,7 +750,9 @@ function createAnchorBindingHash(
747
750
  from: number,
748
751
  to: number,
749
752
  ): string {
750
- return `${serializeStoryTargetKey(storyTarget)}:${from}:${to}`;
753
+ return sha256Hex(
754
+ workflowAnchorHashEncoder.encode(`${serializeStoryTargetKey(storyTarget)}:${from}:${to}`),
755
+ );
751
756
  }
752
757
 
753
758
  function getPreservedExtensionsXml(sourcePackage: OpcPackage, payloadPartPath: string): string {
@@ -346,14 +346,20 @@ export interface FootnoteDefinition {
346
346
  * footnote/endnote entries with `w:type="separator"` and
347
347
  * `w:type="continuationSeparator"`.
348
348
  *
349
- * Content is stored as raw inner XML (the run children of the <w:p>) so
350
- * Lane 3a page-chrome can render the horizontal rule without re-parsing.
349
+ * The canonical model keeps both the legacy run-only payload and the full
350
+ * first-paragraph XML. The paragraph form closes round-trip fidelity for
351
+ * imported separator paragraph properties, while the run form remains for
352
+ * back-compat with the current note-separator runtime helpers.
351
353
  */
352
354
  export interface FootnoteSeparators {
353
355
  /** Raw XML of the <w:r> children inside the separator paragraph. */
354
356
  separatorContent?: string;
357
+ /** Full XML of the first separator paragraph. */
358
+ separatorParagraphXml?: string;
355
359
  /** Raw XML of the <w:r> children inside the continuationSeparator paragraph. */
356
360
  continuationSeparatorContent?: string;
361
+ /** Full XML of the first continuation-separator paragraph. */
362
+ continuationSeparatorParagraphXml?: string;
357
363
  }
358
364
 
359
365
  export interface FootnoteCollection {
@@ -399,6 +405,22 @@ export interface CompatSetting {
399
405
  export interface DocumentSettings {
400
406
  evenAndOddHeaders?: boolean;
401
407
  zoomLevel?: "pageWidth" | "onePage" | number;
408
+ /**
409
+ * Document-wide default tab stop interval from `<w:defaultTabStop w:val>`.
410
+ * Value is stored in twips and feeds layout measurement whenever a paragraph
411
+ * does not declare an explicit next tab stop.
412
+ */
413
+ defaultTabStop?: number;
414
+ /**
415
+ * Settings-level default footnote configuration from `<w:footnotePr>`.
416
+ * Section-level `SectionProperties.footnotePr` overrides this when present.
417
+ */
418
+ footnotePr?: FootnoteProperties;
419
+ /**
420
+ * Settings-level default endnote configuration from `<w:endnotePr>`.
421
+ * Section-level `SectionProperties.endnotePr` overrides this when present.
422
+ */
423
+ endnotePr?: EndnoteProperties;
402
424
  /**
403
425
  * Ordered list of <w:compatSetting> entries inside <w:compat>. Insertion
404
426
  * order is preserved for serializer diff stability.
@@ -606,6 +628,18 @@ export interface ParagraphShading {
606
628
  fill?: string;
607
629
  color?: string;
608
630
  val?: string;
631
+ /**
632
+ * Theme shading references (§17.3.5). When `themeFill` is set and `fill`
633
+ * is absent or `"auto"`, render-time shading resolves through the theme
634
+ * color resolver. The raw theme attrs remain on the canonical model so
635
+ * export can round-trip the original `<w:shd>` byte-for-byte.
636
+ */
637
+ themeFill?: string;
638
+ themeFillTint?: string;
639
+ themeFillShade?: string;
640
+ themeColor?: string;
641
+ themeColorTint?: string;
642
+ themeColorShade?: string;
609
643
  }
610
644
 
611
645
  /** Body of an OOXML `<w:rPr>` (run properties). All fields optional; absence = "not specified at this level". */
@@ -28,6 +28,7 @@ export type EditorWarningCode =
28
28
  | "export_roundtrip_risk"
29
29
  | "comment_anchor_detached"
30
30
  | "revision_anchor_detached"
31
+ | "workflow_scope_invalidated"
31
32
  | "large_document_degraded"
32
33
  | "font_substitution"
33
34
  | "image_missing"
@@ -235,6 +236,7 @@ const EDITOR_WARNING_CODES = new Set<EditorWarningCode>([
235
236
  "export_roundtrip_risk",
236
237
  "comment_anchor_detached",
237
238
  "revision_anchor_detached",
239
+ "workflow_scope_invalidated",
238
240
  "large_document_degraded",
239
241
  "font_substitution",
240
242
  "image_missing",
@@ -131,6 +131,7 @@ export function buildDiagnosticFromLegacyWarningCode(
131
131
  | "export_roundtrip_risk"
132
132
  | "comment_anchor_detached"
133
133
  | "revision_anchor_detached"
134
+ | "workflow_scope_invalidated"
134
135
  | "large_document_degraded"
135
136
  | "font_substitution"
136
137
  | "image_missing",
@@ -143,6 +144,7 @@ export function buildDiagnosticFromLegacyWarningCode(
143
144
  export_roundtrip_risk: "export.roundtrip_risk",
144
145
  comment_anchor_detached: "review.comment_anchor_detached",
145
146
  revision_anchor_detached: "review.revision_anchor_detached",
147
+ workflow_scope_invalidated: "runtime.workflow_scope_invalidated",
146
148
  large_document_degraded: "runtime.large_document_degraded",
147
149
  font_substitution: "runtime.font_substitution",
148
150
  image_missing: "runtime.image_missing",
@@ -80,6 +80,15 @@ export const CODE_METADATA_TABLE: Readonly<
80
80
  "review.revision_anchor_detached": row("review", "info",
81
81
  "A tracked change's anchor was invalidated by a structural edit.",
82
82
  "retry-safe", { kind: "none" }, ["review", "tracked-change"], "review-revision-anchor-detached"),
83
+ "runtime.workflow_scope_invalidated": row("runtime", "warning",
84
+ "A workflow scope lost its trusted anchor and is no longer enforcing against live text.",
85
+ "requires-input",
86
+ {
87
+ kind: "fallback",
88
+ suggestion:
89
+ "Use warning.details.scopeId + warning.details.lastKnownRange to relocate the intended text, then reapply the scope.",
90
+ },
91
+ ["runtime", "workflow", "scope"], "runtime-workflow-scope-invalidated"),
83
92
  "runtime.large_document_degraded": row("runtime", "warning",
84
93
  "The document is large enough that some rendering optimizations have been relaxed.",
85
94
  "retry-safe", { kind: "none" }, ["runtime", "perf"], "runtime-large-document-degraded"),