@beyondwork/docx-react-component 1.0.36 → 1.0.38

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 (107) hide show
  1. package/README.md +103 -13
  2. package/package.json +1 -1
  3. package/src/api/package-version.ts +13 -0
  4. package/src/api/public-types.ts +402 -1
  5. package/src/core/commands/index.ts +18 -1
  6. package/src/core/commands/section-layout-commands.ts +58 -0
  7. package/src/core/commands/table-grid.ts +431 -0
  8. package/src/core/commands/table-structure-commands.ts +815 -55
  9. package/src/core/selection/mapping.ts +6 -0
  10. package/src/io/docx-session.ts +24 -9
  11. package/src/io/export/build-app-properties-xml.ts +88 -0
  12. package/src/io/export/serialize-comments.ts +6 -1
  13. package/src/io/export/serialize-footnotes.ts +10 -9
  14. package/src/io/export/serialize-headers-footers.ts +11 -10
  15. package/src/io/export/serialize-main-document.ts +328 -50
  16. package/src/io/export/serialize-numbering.ts +114 -24
  17. package/src/io/export/serialize-tables.ts +87 -11
  18. package/src/io/export/table-properties-xml.ts +174 -20
  19. package/src/io/export/twip.ts +66 -0
  20. package/src/io/normalize/normalize-text.ts +20 -0
  21. package/src/io/ooxml/parse-footnotes.ts +62 -1
  22. package/src/io/ooxml/parse-headers-footers.ts +62 -1
  23. package/src/io/ooxml/parse-main-document.ts +158 -1
  24. package/src/io/ooxml/parse-tables.ts +249 -0
  25. package/src/legal/bookmarks.ts +78 -0
  26. package/src/model/canonical-document.ts +45 -0
  27. package/src/review/store/scope-tag-diff.ts +130 -0
  28. package/src/runtime/document-layout.ts +4 -2
  29. package/src/runtime/document-navigation.ts +2 -306
  30. package/src/runtime/document-runtime.ts +287 -11
  31. package/src/runtime/layout/default-page-format.ts +96 -0
  32. package/src/runtime/layout/docx-font-loader.ts +143 -0
  33. package/src/runtime/layout/index.ts +233 -0
  34. package/src/runtime/layout/inert-layout-facet.ts +59 -0
  35. package/src/runtime/layout/layout-engine-instance.ts +628 -0
  36. package/src/runtime/layout/layout-invalidation.ts +257 -0
  37. package/src/runtime/layout/layout-measurement-provider.ts +175 -0
  38. package/src/runtime/layout/margin-preset-catalog.ts +178 -0
  39. package/src/runtime/layout/measurement-backend-canvas.ts +307 -0
  40. package/src/runtime/layout/measurement-backend-empirical.ts +208 -0
  41. package/src/runtime/layout/page-format-catalog.ts +233 -0
  42. package/src/runtime/layout/page-fragment-mapper.ts +179 -0
  43. package/src/runtime/layout/page-graph.ts +452 -0
  44. package/src/runtime/layout/page-layout-snapshot-adapter.ts +70 -0
  45. package/src/runtime/layout/page-story-resolver.ts +195 -0
  46. package/src/runtime/layout/paginated-layout-engine.ts +921 -0
  47. package/src/runtime/layout/project-block-fragments.ts +91 -0
  48. package/src/runtime/layout/public-facet.ts +1398 -0
  49. package/src/runtime/layout/resolved-formatting-document.ts +317 -0
  50. package/src/runtime/layout/resolved-formatting-state.ts +430 -0
  51. package/src/runtime/layout/table-render-plan.ts +229 -0
  52. package/src/runtime/render/block-fragment-projection.ts +35 -0
  53. package/src/runtime/render/decoration-resolver.ts +189 -0
  54. package/src/runtime/render/index.ts +57 -0
  55. package/src/runtime/render/pending-op-delta-reader.ts +129 -0
  56. package/src/runtime/render/render-frame-types.ts +317 -0
  57. package/src/runtime/render/render-kernel.ts +755 -0
  58. package/src/runtime/scope-tag-registry.ts +95 -0
  59. package/src/runtime/surface-projection.ts +1 -0
  60. package/src/runtime/text-ack-range.ts +49 -0
  61. package/src/runtime/view-state.ts +67 -0
  62. package/src/runtime/workflow-markup.ts +1 -5
  63. package/src/runtime/workflow-rail-segments.ts +280 -0
  64. package/src/ui/WordReviewEditor.tsx +99 -15
  65. package/src/ui/editor-runtime-boundary.ts +10 -1
  66. package/src/ui/editor-shell-view.tsx +6 -0
  67. package/src/ui/editor-surface-controller.tsx +3 -0
  68. package/src/ui/headless/chrome-registry.ts +501 -0
  69. package/src/ui/headless/scoped-chrome-policy.ts +183 -0
  70. package/src/ui/headless/selection-tool-context.ts +2 -0
  71. package/src/ui/headless/selection-tool-resolver.ts +36 -17
  72. package/src/ui/headless/selection-tool-types.ts +10 -0
  73. package/src/ui-tailwind/chrome/chrome-preset-model.ts +23 -2
  74. package/src/ui-tailwind/chrome/role-action-sets.ts +74 -0
  75. package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
  76. package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +163 -0
  77. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +57 -92
  78. package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
  79. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
  80. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +274 -138
  81. package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +90 -0
  82. package/src/ui-tailwind/chrome-overlay/index.ts +22 -0
  83. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +86 -0
  84. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
  85. package/src/ui-tailwind/chrome-overlay/tw-workspace-view-switcher.tsx +95 -0
  86. package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +337 -0
  87. package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +100 -0
  88. package/src/ui-tailwind/editor-surface/perf-probe.ts +27 -1
  89. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +20 -2
  90. package/src/ui-tailwind/editor-surface/pm-decorations.ts +93 -23
  91. package/src/ui-tailwind/editor-surface/predicted-position-map.ts +78 -0
  92. package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +63 -0
  93. package/src/ui-tailwind/editor-surface/predicted-tx-gate.ts +39 -0
  94. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +176 -6
  95. package/src/ui-tailwind/index.ts +33 -0
  96. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
  97. package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
  98. package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
  99. package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
  100. package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
  101. package/src/ui-tailwind/theme/editor-theme.css +505 -144
  102. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +559 -0
  103. package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
  104. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
  105. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +2 -2
  106. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +304 -166
  107. package/src/ui-tailwind/tw-review-workspace.tsx +163 -2
@@ -57,6 +57,10 @@ import type {
57
57
  SelectionToolAnchor,
58
58
  } from "../ui/headless/selection-tool-types";
59
59
  import type { MarkupDisplay } from "../ui/headless/comment-decoration-model";
60
+ import {
61
+ resolveScopedChromePolicy,
62
+ shouldRenderSelectionToolKind,
63
+ } from "../ui/headless/scoped-chrome-policy";
60
64
  import type { EditorCommandBag } from "../ui/editor-command-bag.ts";
61
65
  import { preserveEditorSelectionMouseDown } from "../ui/headless/preserve-editor-selection";
62
66
  import { TwAlertBanner } from "./chrome/tw-alert-banner";
@@ -78,6 +82,7 @@ import { TwSelectionToolHost } from "./chrome/tw-selection-tool-host";
78
82
  import { TwReviewRail, type ReviewRailTab } from "./review/tw-review-rail";
79
83
  import { TwStatusBar } from "./status/tw-status-bar";
80
84
  import { type ToolbarInteractionPolicy } from "./toolbar/tw-toolbar";
85
+ import { TwChromeOverlay } from "./chrome-overlay";
81
86
 
82
87
  export type ReviewWorkspaceChromeVisibility = WordReviewEditorChromeVisibility;
83
88
 
@@ -88,6 +93,38 @@ export interface TwReviewWorkspaceProps {
88
93
  currentUserId?: string;
89
94
  capabilities?: SessionCapabilities;
90
95
  reviewMode?: "editing" | "review";
96
+ /**
97
+ * Runtime-owned layout facet. Optional so existing tests + host apps
98
+ * continue to mount the workspace without installing a facet. When
99
+ * supplied, the ChromeOverlay plane (scope rail, workflow dock, etc.)
100
+ * renders over the document column.
101
+ */
102
+ layoutFacet?: import("../runtime/layout/index.ts").WordReviewEditorLayoutFacet;
103
+ /**
104
+ * Optional shell header mounted above the formatting toolbar. Pass a
105
+ * pre-assembled `<TwShellHeader />` with brand / mode switcher /
106
+ * primaryAction, or any other ReactNode. Hosts that do not supply this
107
+ * get the legacy layout.
108
+ */
109
+ shellHeader?: ReactNode;
110
+ /**
111
+ * Optional host-provided Workflow-tab override for the review rail.
112
+ * When unset the rail renders the built-in `TwWorkflowTab` sourced from
113
+ * `layoutFacet.getAllScopeRailSegments()`.
114
+ */
115
+ reviewRailWorkflowTab?: ReactNode;
116
+ reviewRailWorkflowCount?: number;
117
+ reviewRailWorkflowScopesTitle?: string;
118
+ reviewRailIntelligenceEyebrow?: string;
119
+ /** Opt in to the editorial DOCUMENT INTELLIGENCE header + underline tab chip. */
120
+ reviewRailIntelligenceHeader?: boolean;
121
+ /** Optional SEARCH / HELP utility footer at the bottom of the rail. */
122
+ reviewRailFooter?: {
123
+ onSearch?: () => void;
124
+ helpHref?: string;
125
+ searchLabel?: string;
126
+ helpLabel?: string;
127
+ };
91
128
  document: ReactNode;
92
129
  workspaceMode: WorkspaceMode;
93
130
  zoomLevel?: ZoomLevel;
@@ -110,6 +147,17 @@ export interface TwReviewWorkspaceProps {
110
147
  activeSelectionTool?: ActiveSelectionToolModel | null;
111
148
  selectionToolAnchor?: SelectionToolAnchor | null;
112
149
  documentNavigation?: DocumentNavigationSnapshot;
150
+ /**
151
+ * R2.3: chrome-pin change handler. When supplied, selection tools
152
+ * expose their detach affordance and persist pin state through to
153
+ * runtime ViewState (via the host's `setChromePin` action). When
154
+ * omitted, the detach handle is suppressed — the tool behaves as
155
+ * a non-pinnable anchored panel (pre-R2 behavior for most kinds).
156
+ */
157
+ onChromePinChange?: (
158
+ surface: import("../api/public-types").ChromePinSurface,
159
+ pin: import("../api/public-types").PinState | null,
160
+ ) => void;
113
161
  onWorkspaceModeChange?: (value: WorkspaceMode) => void;
114
162
  onZoomChange?: (level: ZoomLevel) => void;
115
163
  onActiveRailTabChange?: (value: ReviewRailTab) => void;
@@ -340,6 +388,25 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
340
388
  }),
341
389
  [reviewRailAvailable, reviewRailOpen, viewportWidth],
342
390
  );
391
+ const scopedChromePolicy = useMemo(
392
+ () =>
393
+ resolveScopedChromePolicy({
394
+ preset: chromePreset,
395
+ compactMode: responsiveChrome.isNarrow,
396
+ capabilities: caps,
397
+ interactionGuardSnapshot: props.interactionGuardSnapshot,
398
+ workflowScopeSnapshot: props.workflowScopeSnapshot,
399
+ activeListContext: props.activeListContext,
400
+ }),
401
+ [
402
+ caps,
403
+ chromePreset,
404
+ props.activeListContext,
405
+ props.interactionGuardSnapshot,
406
+ props.workflowScopeSnapshot,
407
+ responsiveChrome.isNarrow,
408
+ ],
409
+ );
343
410
  const toolbarInteractionPolicy: ToolbarInteractionPolicy | undefined = caps
344
411
  ? {
345
412
  mode: effectiveSelectionMode,
@@ -428,6 +495,7 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
428
495
  return (
429
496
  <Tooltip.Provider delayDuration={400}>
430
497
  <div className="flex h-full flex-col bg-canvas text-primary">
498
+ {props.shellHeader}
431
499
  {chromeVisibility.toolbar ? (
432
500
  <div className="px-3 pt-3">
433
501
  <ChromePresetToolbar
@@ -438,6 +506,7 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
438
506
  blockedReasons={blockedReasons}
439
507
  showDiagnosticsChrome={chromeVisibility.alerts}
440
508
  interactionPolicy={toolbarInteractionPolicy}
509
+ scopedChromePolicy={scopedChromePolicy}
441
510
  compactMode={responsiveChrome.isNarrow}
442
511
  workspaceMode={props.workspaceMode}
443
512
  zoomLevel={props.zoomLevel}
@@ -537,11 +606,50 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
537
606
  dismissSelectionToolbar();
538
607
  props.onShowTrackedChangesChange(show);
539
608
  }}
609
+ role={viewState.editorRole}
610
+ reviewQueue={props.reviewQueue}
611
+ markupDisplay={markupDisplay}
612
+ onMarkScopePosture={props.onMarkSectionForReview
613
+ ? runWithSelectionToolbarDismiss(props.onMarkSectionForReview)
614
+ : undefined}
615
+ onReviewPrev={props.onGoToPreviousReviewItem
616
+ ? runWithSelectionToolbarDismiss(props.onGoToPreviousReviewItem)
617
+ : undefined}
618
+ onReviewNext={props.onGoToNextReviewItem
619
+ ? runWithSelectionToolbarDismiss(props.onGoToNextReviewItem)
620
+ : undefined}
621
+ onReviewAccept={(() => {
622
+ const active = props.reviewQueue?.items[props.reviewQueue.activeIndex];
623
+ if (active?.kind !== "change" || !props.onAcceptRevision) {
624
+ return undefined;
625
+ }
626
+ // ReviewQueueItem.itemId for a "change" entry is the
627
+ // revision id (set by the runtime review-queue projection).
628
+ const revisionId = active.itemId;
629
+ return () => {
630
+ dismissSelectionToolbar();
631
+ props.onAcceptRevision?.(revisionId);
632
+ };
633
+ })()}
634
+ onReviewReject={(() => {
635
+ const active = props.reviewQueue?.items[props.reviewQueue.activeIndex];
636
+ if (active?.kind !== "change" || !props.onRejectRevision) {
637
+ return undefined;
638
+ }
639
+ const revisionId = active.itemId;
640
+ return () => {
641
+ dismissSelectionToolbar();
642
+ props.onRejectRevision?.(revisionId);
643
+ };
644
+ })()}
540
645
  />
541
646
  </div>
542
647
  ) : null}
543
648
 
544
- {chromePreset === "review" && chromeOptions.showReviewQueueBar && props.reviewQueue ? (
649
+ {viewState.editorRole !== "review" &&
650
+ chromePreset === "review" &&
651
+ chromeOptions.showReviewQueueBar &&
652
+ props.reviewQueue ? (
545
653
  <TwReviewQueueBar
546
654
  queue={props.reviewQueue}
547
655
  onPrevious={props.onGoToPreviousReviewItem
@@ -817,7 +925,9 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
817
925
  />
818
926
  </div>
819
927
  ) : null}
820
- {chromeVisibility.selectionOverlay && gatedSelectionTool ? (
928
+ {chromeVisibility.selectionOverlay &&
929
+ gatedSelectionTool &&
930
+ shouldRenderSelectionToolKind(scopedChromePolicy, gatedSelectionTool.kind) ? (
821
931
  <TwSelectionToolHost
822
932
  tool={gatedSelectionTool}
823
933
  contextAnalytics={
@@ -857,6 +967,8 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
857
967
  onSetImageFrame={props.onSetImageFrame}
858
968
  onRestartNumbering={props.onRestartNumbering}
859
969
  onContinueNumbering={props.onContinueNumbering}
970
+ chromePins={viewState.chromePins}
971
+ onChromePinChange={props.onChromePinChange}
860
972
  />
861
973
  ) : null}
862
974
  <div
@@ -925,6 +1037,15 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
925
1037
  ) : null}
926
1038
  <div className={isPageWorkspace ? "relative z-10" : undefined}>
927
1039
  {props.document}
1040
+ {props.layoutFacet ? (
1041
+ <TwChromeOverlay
1042
+ facet={props.layoutFacet}
1043
+ activeWorkspaceView={
1044
+ props.reviewMode === "review" ? "review" : "workflow"
1045
+ }
1046
+ showWorkspaceDock={chromeVisibility.pageChrome}
1047
+ />
1048
+ ) : null}
928
1049
  </div>
929
1050
  {isPageWorkspace && chromeVisibility.pageChrome ? (
930
1051
  <div
@@ -949,6 +1070,32 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
949
1070
  </div>
950
1071
  </div>
951
1072
  </div>
1073
+ {isPageWorkspace && (props.documentNavigation?.pages.length ?? 0) > 1 ? (
1074
+ <div className="flex flex-col items-center gap-8 pb-8" data-testid="page-stack-continuation">
1075
+ {props.documentNavigation!.pages.slice(1).map((page) => (
1076
+ <div
1077
+ key={`page-${page.pageIndex}`}
1078
+ data-wre-page-frame="true"
1079
+ data-page-index={page.pageIndex}
1080
+ className="wre-page-chrome wre-page-surface relative mx-auto w-full max-w-[840px] overflow-hidden rounded-[2px] bg-canvas"
1081
+ style={{
1082
+ minHeight: "600px",
1083
+ boxShadow: "0 1px 2px rgba(0,0,0,0.08), 0 2px 8px rgba(0,0,0,0.04)",
1084
+ border: "1px solid var(--color-border, rgba(0,0,0,0.08))",
1085
+ }}
1086
+ >
1087
+ <div className="absolute left-4 top-3 text-[11px] uppercase tracking-[0.12em] text-tertiary">
1088
+ Page {page.pageIndex + 1} of {props.documentNavigation!.pageCount}
1089
+ </div>
1090
+ <div className="absolute inset-0 flex items-center justify-center text-sm text-secondary">
1091
+ Continuation of document flow.
1092
+ <br />
1093
+ (Editing occurs in the page above.)
1094
+ </div>
1095
+ </div>
1096
+ ))}
1097
+ </div>
1098
+ ) : null}
952
1099
  </div>
953
1100
 
954
1101
  {chromeVisibility.statusBar ? (
@@ -995,6 +1142,13 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
995
1142
  onRejectRevision={props.onRejectRevision}
996
1143
  onAcceptAllChanges={props.onAcceptAllChanges}
997
1144
  onRejectAllChanges={props.onRejectAllChanges}
1145
+ scopeRailSegments={props.layoutFacet?.getAllScopeRailSegments?.() ?? []}
1146
+ workflowTab={props.reviewRailWorkflowTab}
1147
+ workflowCount={props.reviewRailWorkflowCount}
1148
+ workflowScopesTitle={props.reviewRailWorkflowScopesTitle}
1149
+ intelligenceEyebrow={props.reviewRailIntelligenceEyebrow}
1150
+ intelligenceHeader={props.reviewRailIntelligenceHeader}
1151
+ railFooter={props.reviewRailFooter}
998
1152
  /> : null}
999
1153
 
1000
1154
  {responsiveChrome.showDrawerReviewRail ? (
@@ -1036,6 +1190,13 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
1036
1190
  onRejectRevision={props.onRejectRevision}
1037
1191
  onAcceptAllChanges={props.onAcceptAllChanges}
1038
1192
  onRejectAllChanges={props.onRejectAllChanges}
1193
+ scopeRailSegments={props.layoutFacet?.getAllScopeRailSegments?.() ?? []}
1194
+ workflowTab={props.reviewRailWorkflowTab}
1195
+ workflowCount={props.reviewRailWorkflowCount}
1196
+ workflowScopesTitle={props.reviewRailWorkflowScopesTitle}
1197
+ intelligenceEyebrow={props.reviewRailIntelligenceEyebrow}
1198
+ intelligenceHeader={props.reviewRailIntelligenceHeader}
1199
+ railFooter={props.reviewRailFooter}
1039
1200
  />
1040
1201
  </div>
1041
1202
  </div>