@beyondwork/docx-react-component 1.0.43 → 1.0.45

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 (48) hide show
  1. package/README.md +17 -0
  2. package/package.json +44 -32
  3. package/src/api/public-types.ts +139 -3
  4. package/src/core/commands/formatting-commands.ts +7 -1
  5. package/src/core/commands/index.ts +27 -2
  6. package/src/core/commands/text-commands.ts +59 -0
  7. package/src/core/selection/review-anchors.ts +131 -21
  8. package/src/index.ts +16 -1
  9. package/src/io/chart-preview-resolver.ts +281 -0
  10. package/src/io/docx-session.ts +21 -1
  11. package/src/io/export/build-app-properties-xml.ts +1 -1
  12. package/src/io/export/serialize-comments.ts +38 -9
  13. package/src/io/export/twip.ts +1 -1
  14. package/src/io/normalize/normalize-text.ts +33 -0
  15. package/src/io/ooxml/parse-comments.ts +0 -33
  16. package/src/io/ooxml/parse-complex-content.ts +14 -0
  17. package/src/io/ooxml/parse-main-document.ts +4 -0
  18. package/src/preservation/opaque-region.ts +5 -0
  19. package/src/review/store/comment-remapping.ts +2 -2
  20. package/src/runtime/document-runtime.ts +316 -25
  21. package/src/runtime/edit-dispatch/dispatch-text-command.ts +98 -0
  22. package/src/runtime/edit-dispatch/index.ts +2 -0
  23. package/src/runtime/edit-dispatch/list-aware-dispatch.ts +125 -0
  24. package/src/runtime/editor-surface/capabilities.ts +411 -0
  25. package/src/runtime/layout/inert-layout-facet.ts +2 -0
  26. package/src/runtime/layout/layout-engine-instance.ts +46 -0
  27. package/src/runtime/layout/layout-engine-version.ts +41 -0
  28. package/src/runtime/layout/public-facet.ts +30 -0
  29. package/src/runtime/prerender/cache-envelope.ts +29 -0
  30. package/src/runtime/prerender/cache-key.ts +66 -0
  31. package/src/runtime/prerender/font-fingerprint.ts +17 -0
  32. package/src/runtime/prerender/graph-canonicalize.ts +121 -0
  33. package/src/runtime/prerender/indexeddb-cache.ts +184 -0
  34. package/src/runtime/prerender/prerender-document.ts +145 -0
  35. package/src/runtime/render/block-fragment-projection.ts +2 -0
  36. package/src/runtime/selection/post-edit-validator.ts +77 -0
  37. package/src/runtime/surface-projection.ts +35 -2
  38. package/src/ui/WordReviewEditor.tsx +75 -192
  39. package/src/ui/editor-runtime-boundary.ts +5 -1
  40. package/src/ui/runtime-shortcut-dispatch.ts +28 -68
  41. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +76 -165
  42. package/src/ui-tailwind/editor-surface/pm-schema.ts +18 -0
  43. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +23 -0
  44. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -0
  45. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +38 -0
  46. package/src/ui-tailwind/page-stack/use-visible-block-range.ts +157 -0
  47. package/src/ui-tailwind/theme/editor-theme.css +47 -14
  48. package/src/ui-tailwind/tw-review-workspace.tsx +131 -29
@@ -23,6 +23,7 @@ import type {
23
23
  import type {
24
24
  ParsedAltChunkNode,
25
25
  ParsedBlockNode,
26
+ ParsedChartPreviewNode,
26
27
  ParsedCustomXmlNode,
27
28
  ParsedHyperlinkNode,
28
29
  ParsedInlineNode,
@@ -31,6 +32,7 @@ import type {
31
32
  ParsedParagraphNode,
32
33
  ParsedSectionBreakNode,
33
34
  ParsedSdtNode,
35
+ ParsedSmartArtPreviewNode,
34
36
  ParsedTableBlockNode,
35
37
  ParsedTableCellNode,
36
38
  ParsedTableRowNode,
@@ -476,6 +478,7 @@ function normalizeInlineChildren(
476
478
  state.cursor += 1;
477
479
  break;
478
480
  case "chart_preview":
481
+ registerComplexPreviewMedia(state, node);
479
482
  normalized.push({
480
483
  type: "chart_preview",
481
484
  ...(node.previewMediaId ? { previewMediaId: node.previewMediaId } : {}),
@@ -484,6 +487,7 @@ function normalizeInlineChildren(
484
487
  state.cursor += 1;
485
488
  break;
486
489
  case "smartart_preview":
490
+ registerComplexPreviewMedia(state, node);
487
491
  normalized.push({
488
492
  type: "smartart_preview",
489
493
  ...(node.previewMediaId ? { previewMediaId: node.previewMediaId } : {}),
@@ -611,6 +615,35 @@ function normalizeImageNode(
611
615
  };
612
616
  }
613
617
 
618
+ /**
619
+ * Register a chart/SmartArt preview bitmap in the media catalog so the
620
+ * surface renderer can resolve `previewMediaId` → `previewSrc` the same
621
+ * way it does for inline images. Chart/SmartArt nodes weren't previously
622
+ * registered because only image nodes walked through `normalizeImageNode`.
623
+ * Needed by docs/plans/lane-5-charts.md Stage 0 (real mc:Fallback bitmaps) and
624
+ * Stage 0B (synthesized previews from the demo harness decorator) —
625
+ * without this, previewMediaId sits in the canonical model but
626
+ * `canonicalDocument.media.items` has no corresponding MediaItem and
627
+ * the editor's mediaPreview resolver skips the bitmap.
628
+ */
629
+ function registerComplexPreviewMedia(
630
+ state: NormalizationState,
631
+ node: ParsedChartPreviewNode | ParsedSmartArtPreviewNode,
632
+ ): void {
633
+ if (!node.previewMediaId) return;
634
+ if (state.media.items[node.previewMediaId]) return; // already registered (e.g. via another reference)
635
+ const packagePartName =
636
+ node.previewPackagePartName ?? `/${node.previewMediaId.slice("media:".length)}`;
637
+ const filename =
638
+ packagePartName.slice(packagePartName.lastIndexOf("/") + 1) || "chart-preview.bin";
639
+ state.media.items[node.previewMediaId] = {
640
+ mediaId: node.previewMediaId,
641
+ contentType: node.previewContentType ?? "application/octet-stream",
642
+ filename,
643
+ packagePartName,
644
+ };
645
+ }
646
+
614
647
  function normalizeHyperlink(node: ParsedHyperlinkNode): {
615
648
  type: "hyperlink";
616
649
  href: string;
@@ -173,39 +173,6 @@ export function parseCommentsFromOoxml(
173
173
  continue;
174
174
  }
175
175
 
176
- if (startParagraphIndex !== endParagraphIndex) {
177
- diagnostics.push({
178
- commentId: rootCommentId,
179
- code: "multi_paragraph_anchor_preserve_only",
180
- message:
181
- "Comment anchor spans multiple paragraphs. Thread is visible but detached; cross-paragraph anchoring is not yet supported for live editing.",
182
- featureClass: "preserve-only",
183
- detachedReason: "multi-paragraph",
184
- actionabilityNote: "The comment thread and body are preserved. Operators see the thread in the sidebar but cannot navigate to an inline highlight.",
185
- });
186
- threads.push(
187
- createImportedCommentThread({
188
- commentId: rootCommentId,
189
- body: rootDefinition.body,
190
- createdBy,
191
- createdAt,
192
- range: detachedRange,
193
- entries,
194
- status: "detached",
195
- resolution,
196
- metadata: {
197
- source: "import",
198
- rootOoxmlCommentId: rootDefinition.commentId,
199
- rootParaId: rootDefinition.paraId,
200
- detachedReason: "multi-paragraph",
201
- actionabilityNote:
202
- "The comment thread and body are preserved. Operators see the thread in the sidebar but cannot navigate to an inline highlight.",
203
- },
204
- }),
205
- );
206
- continue;
207
- }
208
-
209
176
  threads.push(
210
177
  createImportedCommentThread({
211
178
  commentId: rootCommentId,
@@ -21,6 +21,10 @@ export interface ParsedChartContent {
21
21
  type: "chart_preview";
22
22
  /** Media ID of the fallback preview image, if one is present in mc:Fallback. */
23
23
  previewMediaId?: string;
24
+ /** Absolute package path of the preview media (e.g. `/word/media/chartN.png`); required by the media catalog when the preview media needs an entry. */
25
+ previewPackagePartName?: string;
26
+ /** MIME type of the preview media (e.g. `image/png`, `image/svg+xml`). */
27
+ previewContentType?: string;
24
28
  /** Original drawing XML slice for lossless round-trip export. */
25
29
  rawXml: string;
26
30
  }
@@ -29,6 +33,10 @@ export interface ParsedSmartArtContent {
29
33
  type: "smartart_preview";
30
34
  /** Media ID of the fallback preview image, if one is present in mc:Fallback. */
31
35
  previewMediaId?: string;
36
+ /** Absolute package path of the preview media. */
37
+ previewPackagePartName?: string;
38
+ /** MIME type of the preview media. */
39
+ previewContentType?: string;
32
40
  /** Original drawing XML slice for lossless round-trip export. */
33
41
  rawXml: string;
34
42
  }
@@ -116,6 +124,8 @@ function parseAlternateContent(
116
124
 
117
125
  // Extract fallback preview image if present
118
126
  let previewMediaId: string | undefined;
127
+ let previewPackagePartName: string | undefined;
128
+ let previewContentType: string | undefined;
119
129
  if (fallback) {
120
130
  const fallbackBlip = findFirstDescendant(fallback, "blip");
121
131
  if (fallbackBlip) {
@@ -132,6 +142,8 @@ function parseAlternateContent(
132
142
  const mediaPart = mediaParts.get(packagePartName);
133
143
  if (mediaPart) {
134
144
  previewMediaId = `media:${packagePartName.slice(1)}`;
145
+ previewPackagePartName = packagePartName;
146
+ previewContentType = mediaPart.contentType;
135
147
  }
136
148
  }
137
149
  }
@@ -141,6 +153,8 @@ function parseAlternateContent(
141
153
  return {
142
154
  type: contentType,
143
155
  ...(previewMediaId ? { previewMediaId } : {}),
156
+ ...(previewPackagePartName ? { previewPackagePartName } : {}),
157
+ ...(previewContentType ? { previewContentType } : {}),
144
158
  rawXml: fullDrawingXml,
145
159
  };
146
160
  }
@@ -211,12 +211,16 @@ export interface ParsedOpaqueInlineNode {
211
211
  export interface ParsedChartPreviewNode {
212
212
  type: "chart_preview";
213
213
  previewMediaId?: string;
214
+ previewPackagePartName?: string;
215
+ previewContentType?: string;
214
216
  rawXml: string;
215
217
  }
216
218
 
217
219
  export interface ParsedSmartArtPreviewNode {
218
220
  type: "smartart_preview";
219
221
  previewMediaId?: string;
222
+ previewPackagePartName?: string;
223
+ previewContentType?: string;
220
224
  rawXml: string;
221
225
  }
222
226
 
@@ -121,6 +121,11 @@ export function createOpaqueRegionSurfaceModel(
121
121
  export function createOpaqueRegionSurfaceModelFromSurfaceBlock(
122
122
  block: Extract<SurfaceBlockSnapshot, { kind: "opaque_block" }>,
123
123
  ): OpaqueRegionSurfaceModel {
124
+ if (block.state === "placeholder-culled") {
125
+ throw new Error(
126
+ "createOpaqueRegionSurfaceModelFromSurfaceBlock: placeholder-culled block has no real fragment",
127
+ );
128
+ }
124
129
  return {
125
130
  fragmentId: block.fragmentId,
126
131
  warningId: block.warningId,
@@ -4,7 +4,7 @@ import {
4
4
  getAnchorRange,
5
5
  mapReviewAnchor,
6
6
  mappingTouchesAnchorContent,
7
- rangeStaysWithinSingleParagraph,
7
+ rangeStaysWithinCommentableStory,
8
8
  type ReviewAnchor,
9
9
  } from "../../core/selection/review-anchors.ts";
10
10
  import type { TransactionMapping } from "../../core/selection/mapping.ts";
@@ -85,7 +85,7 @@ function normalizeCommentAnchor(
85
85
 
86
86
  if (
87
87
  mappedAnchor.kind === "range" &&
88
- !rangeStaysWithinSingleParagraph(nextContent, mappedAnchor.range)
88
+ !rangeStaysWithinCommentableStory(nextContent, mappedAnchor.range)
89
89
  ) {
90
90
  return detachReviewAnchor(previousRange, "invalidatedByStructureChange");
91
91
  }