@beyondwork/docx-react-component 1.0.58 → 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 (134) hide show
  1. package/README.md +2 -2
  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 +978 -10
  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 +72 -42
  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 +159 -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 +476 -34
  87. package/src/runtime/document-search.ts +115 -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 +5 -8
  96. package/src/runtime/resolved-numbering-geometry.ts +37 -6
  97. package/src/runtime/revision-runtime.ts +27 -1
  98. package/src/runtime/selection/post-edit-validator.ts +60 -6
  99. package/src/runtime/structure-ops/index.ts +20 -4
  100. package/src/runtime/surface-projection.ts +290 -21
  101. package/src/runtime/table-schema.ts +6 -0
  102. package/src/runtime/theme-color-resolver.ts +2 -2
  103. package/src/runtime/units.ts +9 -0
  104. package/src/runtime/workflow-rail-segments.ts +4 -0
  105. package/src/ui/WordReviewEditor.tsx +187 -43
  106. package/src/ui/editor-runtime-boundary.ts +10 -0
  107. package/src/ui/editor-shell-view.tsx +4 -1
  108. package/src/ui/headless/chrome-registry.ts +53 -0
  109. package/src/ui/headless/selection-tool-resolver.ts +11 -1
  110. package/src/ui-tailwind/chrome/chrome-preset-model.ts +13 -0
  111. package/src/ui-tailwind/chrome/tw-command-palette-mount.tsx +96 -0
  112. package/src/ui-tailwind/chrome/tw-context-menu.tsx +2 -1
  113. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +5 -4
  114. package/src/ui-tailwind/chrome/tw-mode-dock.tsx +6 -2
  115. package/src/ui-tailwind/chrome/use-container-breakpoint.ts +111 -0
  116. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +0 -9
  117. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +1 -0
  118. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +6 -7
  119. package/src/ui-tailwind/editor-surface/pm-schema.ts +87 -25
  120. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +9 -0
  121. package/src/ui-tailwind/editor-surface/shape-renderer.ts +76 -14
  122. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +18 -1
  123. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +2 -0
  124. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +18 -2
  125. package/src/ui-tailwind/index.ts +9 -0
  126. package/src/ui-tailwind/page-chrome-model.ts +77 -5
  127. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +56 -1
  128. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +2 -0
  129. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +116 -113
  130. package/src/ui-tailwind/review/tw-review-rail-footer.tsx +2 -2
  131. package/src/ui-tailwind/theme/tokens.ts +14 -0
  132. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +5 -0
  133. package/src/ui-tailwind/tw-review-workspace.tsx +29 -87
  134. package/src/validation/diagnostics.ts +1 -0
@@ -1,5 +1,7 @@
1
1
  import type { CommentThread } from "../../review/store/comment-store.ts";
2
2
  import { createImportedCommentThread } from "../../review/store/comment-thread.ts";
3
+ import { parseXmlWithOffsets as parseXml } from "./xml-parser.ts";
4
+ import { localName, readStringAttr } from "./xml-attr-helpers.ts";
3
5
 
4
6
  interface XmlElementNode {
5
7
  type: "element";
@@ -293,7 +295,7 @@ function parseCommentDefinitions(
293
295
  return [];
294
296
  }
295
297
 
296
- const root = parseXml(commentsXml);
298
+ const root = parseXml(commentsXml) as XmlElementNode;
297
299
  const commentsElement = findChildElement(root, "comments");
298
300
  const definitions: ImportedCommentDefinition[] = [];
299
301
  let order = 0;
@@ -303,16 +305,16 @@ function parseCommentDefinitions(
303
305
  continue;
304
306
  }
305
307
 
306
- const commentId = child.attributes["w:id"] ?? child.attributes.id;
308
+ const commentId = readStringAttr(child, "w:id");
307
309
  if (!commentId) {
308
310
  continue;
309
311
  }
310
312
 
311
- const authorId = child.attributes["w:author"] ?? child.attributes.author;
313
+ const authorId = readStringAttr(child, "w:author");
312
314
  const createdAt = normalizeImportedTimestamp(
313
- child.attributes["w:date"] ?? child.attributes.date,
315
+ readStringAttr(child, "w:date"),
314
316
  );
315
- const initials = child.attributes["w:initials"] ?? child.attributes.initials;
317
+ const initials = readStringAttr(child, "w:initials");
316
318
  const paragraphNodes = child.children.filter(
317
319
  (node): node is XmlElementNode => node.type === "element" && localName(node.name) === "p",
318
320
  );
@@ -359,7 +361,7 @@ function parseCommentExtensions(xml: string | undefined): Map<string, CommentExt
359
361
  return new Map();
360
362
  }
361
363
 
362
- const root = parseXml(xml);
364
+ const root = parseXml(xml) as XmlElementNode;
363
365
  const commentsElement = findChildElement(root, "commentsEx");
364
366
  const extensions = new Map<string, CommentExtensionRecord>();
365
367
 
@@ -393,7 +395,7 @@ function parseCommentDurableIds(xml: string | undefined): Map<string, string> {
393
395
  return new Map();
394
396
  }
395
397
 
396
- const root = parseXml(xml);
398
+ const root = parseXml(xml) as XmlElementNode;
397
399
  const commentsIdsElement = findChildElement(root, "commentsIds");
398
400
  const durableIds = new Map<string, string>();
399
401
 
@@ -417,7 +419,7 @@ function parsePeopleAuthors(xml: string | undefined): string[] {
417
419
  return [];
418
420
  }
419
421
 
420
- const root = parseXml(xml);
422
+ const root = parseXml(xml) as XmlElementNode;
421
423
  const peopleElement = findChildElement(root, "people");
422
424
  const authors = new Set<string>();
423
425
 
@@ -445,7 +447,7 @@ function extractRootTag(xml: string | undefined, localTagName: string): string |
445
447
  }
446
448
 
447
449
  function parseCommentAnchors(documentXml: string): Map<string, CommentAnchorBounds> {
448
- const root = parseXml(documentXml);
450
+ const root = parseXml(documentXml) as XmlElementNode;
449
451
  const documentElement = findChildElement(root, "document");
450
452
  const bodyElement = findChildElement(documentElement, "body");
451
453
  const anchors = new Map<string, CommentAnchorBounds>();
@@ -562,7 +564,7 @@ function walkInlineNode(
562
564
 
563
565
  switch (localName(node.name)) {
564
566
  case "commentRangeStart": {
565
- const commentId = node.attributes["w:id"] ?? node.attributes.id;
567
+ const commentId = readStringAttr(node, "w:id");
566
568
  if (commentId) {
567
569
  const bounds = ensureCommentAnchor(anchors, commentId);
568
570
  bounds.start = getCursor();
@@ -571,7 +573,7 @@ function walkInlineNode(
571
573
  return;
572
574
  }
573
575
  case "commentRangeEnd": {
574
- const commentId = node.attributes["w:id"] ?? node.attributes.id;
576
+ const commentId = readStringAttr(node, "w:id");
575
577
  if (commentId) {
576
578
  const bounds = ensureCommentAnchor(anchors, commentId);
577
579
  bounds.end = getCursor();
@@ -580,7 +582,7 @@ function walkInlineNode(
580
582
  return;
581
583
  }
582
584
  case "commentReference": {
583
- const commentId = node.attributes["w:id"] ?? node.attributes.id;
585
+ const commentId = readStringAttr(node, "w:id");
584
586
  if (commentId) {
585
587
  const bounds = ensureCommentAnchor(anchors, commentId);
586
588
  bounds.referenceAt = getCursor();
@@ -674,103 +676,6 @@ function toDetachedRange(anchor: CommentAnchorBounds): { from: number; to: numbe
674
676
  };
675
677
  }
676
678
 
677
- function parseXml(xml: string): XmlElementNode {
678
- const root: XmlElementNode = {
679
- type: "element",
680
- name: "#document",
681
- attributes: {},
682
- children: [],
683
- start: 0,
684
- end: xml.length,
685
- };
686
- const stack: XmlElementNode[] = [root];
687
- const tokenPattern =
688
- /<!--[\s\S]*?-->|<\?[\s\S]*?\?>|<!DOCTYPE[\s\S]*?>|<!\[CDATA\[[\s\S]*?\]\]>|<[^>]+>|[^<]+/gu;
689
-
690
- for (const match of xml.matchAll(tokenPattern)) {
691
- const token = match[0] ?? "";
692
- const start = match.index ?? 0;
693
- const end = start + token.length;
694
-
695
- if (token.startsWith("<?") || token.startsWith("<!DOCTYPE") || token.startsWith("<!--")) {
696
- continue;
697
- }
698
-
699
- if (token.startsWith("<![CDATA[")) {
700
- const text = token.slice(9, -3);
701
- stack[stack.length - 1]?.children.push({
702
- type: "text",
703
- text,
704
- start,
705
- end,
706
- });
707
- continue;
708
- }
709
-
710
- if (token.startsWith("</")) {
711
- const node = stack.pop();
712
- if (!node) {
713
- throw new Error("Malformed XML: unexpected closing tag.");
714
- }
715
- node.end = end;
716
- continue;
717
- }
718
-
719
- if (token.startsWith("<")) {
720
- const selfClosing = /\/>$/.test(token);
721
- const tagBody = token.slice(1, token.length - (selfClosing ? 2 : 1)).trim();
722
- const { name, attributes } = parseTag(tagBody);
723
- const node: XmlElementNode = {
724
- type: "element",
725
- name,
726
- attributes,
727
- children: [],
728
- start,
729
- end,
730
- };
731
- stack[stack.length - 1]?.children.push(node);
732
- if (!selfClosing) {
733
- stack.push(node);
734
- }
735
- continue;
736
- }
737
-
738
- const text = decodeXmlText(token);
739
- if (text.length > 0) {
740
- stack[stack.length - 1]?.children.push({
741
- type: "text",
742
- text,
743
- start,
744
- end,
745
- });
746
- }
747
- }
748
-
749
- if (stack.length !== 1) {
750
- throw new Error("Malformed XML: unclosed tag.");
751
- }
752
-
753
- return root;
754
- }
755
-
756
- function parseTag(tagBody: string): { name: string; attributes: Record<string, string> } {
757
- const whitespaceIndex = tagBody.search(/\s/u);
758
- const name = whitespaceIndex === -1 ? tagBody : tagBody.slice(0, whitespaceIndex);
759
- const rawAttributes = whitespaceIndex === -1 ? "" : tagBody.slice(whitespaceIndex + 1);
760
- const attributes: Record<string, string> = {};
761
- const pattern = /([A-Za-z_][A-Za-z0-9:._-]*)\s*=\s*("([^"]*)"|'([^']*)')/gu;
762
-
763
- for (const match of rawAttributes.matchAll(pattern)) {
764
- const key = match[1];
765
- const value = match[3] ?? match[4] ?? "";
766
- if (key) {
767
- attributes[key] = decodeXmlText(value);
768
- }
769
- }
770
-
771
- return { name, attributes };
772
- }
773
-
774
679
  function findChildElement(node: XmlElementNode, name: string): XmlElementNode {
775
680
  const match = node.children.find(
776
681
  (child): child is XmlElementNode =>
@@ -784,36 +689,3 @@ function findChildElement(node: XmlElementNode, name: string): XmlElementNode {
784
689
  return match;
785
690
  }
786
691
 
787
- function localName(name: string): string {
788
- const index = name.indexOf(":");
789
- return index === -1 ? name : name.slice(index + 1);
790
- }
791
-
792
- function decodeXmlText(text: string): string {
793
- return text.replace(
794
- /&(?:#x([0-9A-Fa-f]+)|#([0-9]+)|([A-Za-z]+));/gu,
795
- (_, hex, dec, named) => {
796
- if (hex) {
797
- return String.fromCodePoint(Number.parseInt(hex, 16));
798
- }
799
- if (dec) {
800
- return String.fromCodePoint(Number.parseInt(dec, 10));
801
- }
802
-
803
- switch (named) {
804
- case "amp":
805
- return "&";
806
- case "lt":
807
- return "<";
808
- case "gt":
809
- return ">";
810
- case "quot":
811
- return "\"";
812
- case "apos":
813
- return "'";
814
- default:
815
- return `&${named};`;
816
- }
817
- },
818
- );
819
- }
@@ -13,6 +13,8 @@ import type { OpcRelationship } from "./part-manifest.ts";
13
13
  import { normalizePartPath, resolveRelationshipTarget } from "./part-manifest.ts";
14
14
  import { parseChartSpace } from "./chart/parse-chart-space.ts";
15
15
  import type { ChartModel } from "./chart/types.ts";
16
+ import { parseXml } from "./xml-parser.ts";
17
+ import { localName } from "./xml-attr-helpers.ts";
16
18
 
17
19
  export interface InlineMediaPart {
18
20
  path: string;
@@ -260,21 +262,7 @@ function isSmartArtUri(uri: string): boolean {
260
262
  );
261
263
  }
262
264
 
263
- // ---- Minimal XML parser (local, self-contained) ----
264
-
265
- interface XmlElementNode {
266
- type: "element";
267
- name: string;
268
- attributes: Record<string, string>;
269
- children: XmlNode[];
270
- }
271
-
272
- interface XmlTextNode {
273
- type: "text";
274
- text: string;
275
- }
276
-
277
- type XmlNode = XmlElementNode | XmlTextNode;
265
+ import type { XmlElementNode } from "./xml-element.ts";
278
266
 
279
267
  function findFirstChild(node: XmlElementNode, local: string): XmlElementNode | undefined {
280
268
  for (const child of node.children) {
@@ -295,95 +283,4 @@ function findFirstDescendant(node: XmlElementNode, local: string): XmlElementNod
295
283
  return undefined;
296
284
  }
297
285
 
298
- function localName(name: string): string {
299
- const i = name.indexOf(":");
300
- return i >= 0 ? name.slice(i + 1) : name;
301
- }
302
-
303
- function parseXml(xml: string): XmlElementNode {
304
- const root: XmlElementNode = { type: "element", name: "__root__", attributes: {}, children: [] };
305
- const stack: XmlElementNode[] = [root];
306
- let cursor = 0;
307
286
 
308
- while (cursor < xml.length) {
309
- if (xml.startsWith("<!--", cursor)) {
310
- const end = xml.indexOf("-->", cursor);
311
- cursor = end >= 0 ? end + 3 : xml.length;
312
- continue;
313
- }
314
- if (xml.startsWith("<?", cursor)) {
315
- const end = xml.indexOf("?>", cursor);
316
- cursor = end >= 0 ? end + 2 : xml.length;
317
- continue;
318
- }
319
- if (xml[cursor] !== "<") {
320
- const nextTag = xml.indexOf("<", cursor);
321
- const end = nextTag >= 0 ? nextTag : xml.length;
322
- const text = xml.slice(cursor, end);
323
- if (text.length > 0) {
324
- stack[stack.length - 1]?.children.push({ type: "text", text });
325
- }
326
- cursor = end;
327
- continue;
328
- }
329
- if (xml[cursor + 1] === "/") {
330
- const end = xml.indexOf(">", cursor);
331
- stack.pop();
332
- cursor = end + 1;
333
- continue;
334
- }
335
- const tagEnd = findTagEnd(xml, cursor);
336
- const tagBody = xml.slice(cursor + 1, tagEnd);
337
- const selfClosing = /\/\s*$/.test(tagBody);
338
- const { name, attributes } = parseTag(tagBody.replace(/\/\s*$/, "").trim());
339
- const element: XmlElementNode = { type: "element", name, attributes, children: [] };
340
- stack[stack.length - 1]?.children.push(element);
341
- if (!selfClosing) stack.push(element);
342
- cursor = tagEnd + 1;
343
- }
344
-
345
- return root;
346
- }
347
-
348
- function parseTag(tagBody: string): { name: string; attributes: Record<string, string> } {
349
- let cursor = 0;
350
- while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) cursor++;
351
- const nameStart = cursor;
352
- while (cursor < tagBody.length && !/\s/.test(tagBody[cursor] ?? "")) cursor++;
353
- const name = tagBody.slice(nameStart, cursor);
354
- const attributes: Record<string, string> = {};
355
-
356
- while (cursor < tagBody.length) {
357
- while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) cursor++;
358
- if (cursor >= tagBody.length) break;
359
- const keyStart = cursor;
360
- while (cursor < tagBody.length && !/[\s=]/.test(tagBody[cursor] ?? "")) cursor++;
361
- const key = tagBody.slice(keyStart, cursor);
362
- while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) cursor++;
363
- if (tagBody[cursor] !== "=") { attributes[key] = ""; continue; }
364
- cursor++;
365
- while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) cursor++;
366
- const quote = tagBody[cursor];
367
- if (quote !== `"` && quote !== `'`) break;
368
- cursor++;
369
- const valueStart = cursor;
370
- while (cursor < tagBody.length && tagBody[cursor] !== quote) cursor++;
371
- attributes[key] = tagBody.slice(valueStart, cursor);
372
- cursor++;
373
- }
374
-
375
- return { name, attributes };
376
- }
377
-
378
- function findTagEnd(xml: string, start: number): number {
379
- let cursor = start + 1;
380
- let quote: string | null = null;
381
- while (cursor < xml.length) {
382
- const c = xml[cursor];
383
- if (quote) { if (c === quote) quote = null; cursor++; continue; }
384
- if (c === `"` || c === `'`) { quote = c; cursor++; continue; }
385
- if (c === ">") return cursor;
386
- cursor++;
387
- }
388
- return xml.length - 1;
389
- }