@beyondwork/docx-react-component 1.0.42 → 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 (82) hide show
  1. package/README.md +17 -0
  2. package/package.json +5 -4
  3. package/src/api/editor-state-types.ts +110 -0
  4. package/src/api/public-types.ts +333 -4
  5. package/src/core/commands/formatting-commands.ts +7 -1
  6. package/src/core/commands/index.ts +60 -10
  7. package/src/core/commands/text-commands.ts +59 -0
  8. package/src/core/search/search-text.ts +15 -2
  9. package/src/core/selection/review-anchors.ts +131 -21
  10. package/src/index.ts +29 -1
  11. package/src/io/chart-preview-resolver.ts +281 -0
  12. package/src/io/docx-session.ts +692 -2
  13. package/src/io/export/build-app-properties-xml.ts +1 -1
  14. package/src/io/export/serialize-comments.ts +38 -9
  15. package/src/io/export/twip.ts +1 -1
  16. package/src/io/load-scheduler.ts +230 -0
  17. package/src/io/normalize/normalize-text.ts +116 -0
  18. package/src/io/ooxml/parse-comments.ts +0 -33
  19. package/src/io/ooxml/parse-complex-content.ts +14 -0
  20. package/src/io/ooxml/parse-main-document.ts +4 -0
  21. package/src/io/ooxml/workflow-payload-validator.ts +97 -1
  22. package/src/io/ooxml/workflow-payload.ts +172 -1
  23. package/src/preservation/opaque-region.ts +5 -0
  24. package/src/review/store/comment-remapping.ts +2 -2
  25. package/src/runtime/collab-session.ts +1 -1
  26. package/src/runtime/document-runtime.ts +661 -42
  27. package/src/runtime/edit-dispatch/dispatch-text-command.ts +98 -0
  28. package/src/runtime/edit-dispatch/index.ts +2 -0
  29. package/src/runtime/edit-dispatch/list-aware-dispatch.ts +125 -0
  30. package/src/runtime/editor-state-channel.ts +544 -0
  31. package/src/runtime/editor-state-integration.ts +217 -0
  32. package/src/runtime/editor-surface/capabilities.ts +411 -0
  33. package/src/runtime/layout/index.ts +2 -0
  34. package/src/runtime/layout/inert-layout-facet.ts +4 -0
  35. package/src/runtime/layout/layout-engine-instance.ts +63 -2
  36. package/src/runtime/layout/layout-engine-version.ts +41 -0
  37. package/src/runtime/layout/paginated-layout-engine.ts +211 -14
  38. package/src/runtime/layout/public-facet.ts +430 -1
  39. package/src/runtime/perf-counters.ts +28 -0
  40. package/src/runtime/prerender/cache-envelope.ts +29 -0
  41. package/src/runtime/prerender/cache-key.ts +66 -0
  42. package/src/runtime/prerender/font-fingerprint.ts +17 -0
  43. package/src/runtime/prerender/graph-canonicalize.ts +121 -0
  44. package/src/runtime/prerender/indexeddb-cache.ts +184 -0
  45. package/src/runtime/prerender/prerender-document.ts +145 -0
  46. package/src/runtime/render/block-fragment-projection.ts +2 -0
  47. package/src/runtime/render/render-frame-types.ts +17 -0
  48. package/src/runtime/render/render-kernel.ts +172 -29
  49. package/src/runtime/selection/post-edit-validator.ts +77 -0
  50. package/src/runtime/surface-projection.ts +45 -7
  51. package/src/runtime/workflow-markup.ts +71 -16
  52. package/src/ui/WordReviewEditor.tsx +142 -237
  53. package/src/ui/editor-command-bag.ts +14 -0
  54. package/src/ui/editor-runtime-boundary.ts +115 -12
  55. package/src/ui/editor-shell-view.tsx +10 -0
  56. package/src/ui/editor-surface-controller.tsx +5 -0
  57. package/src/ui/headless/selection-helpers.ts +10 -0
  58. package/src/ui/runtime-shortcut-dispatch.ts +28 -68
  59. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +62 -2
  60. package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +281 -0
  61. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +48 -0
  62. package/src/ui-tailwind/editor-surface/paste-plain-text.ts +72 -0
  63. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +118 -8
  64. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +76 -165
  65. package/src/ui-tailwind/editor-surface/pm-schema.ts +170 -4
  66. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +58 -7
  67. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -0
  68. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +265 -0
  69. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +8 -255
  70. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +47 -0
  71. package/src/ui-tailwind/index.ts +5 -1
  72. package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +57 -0
  73. package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +71 -0
  74. package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +73 -0
  75. package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +74 -0
  76. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +477 -0
  77. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +374 -0
  78. package/src/ui-tailwind/page-stack/use-visible-block-range.ts +157 -0
  79. package/src/ui-tailwind/review/comment-markdown-renderer.tsx +155 -0
  80. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +77 -16
  81. package/src/ui-tailwind/theme/editor-theme.css +47 -14
  82. package/src/ui-tailwind/tw-review-workspace.tsx +303 -123
@@ -34,12 +34,20 @@ import {
34
34
  type DocumentRuntimeEvent,
35
35
  type DocumentRuntime,
36
36
  } from "../runtime/document-runtime.ts";
37
+ import { createEditorStateChannel } from "../runtime/editor-state-channel.ts";
37
38
  import {
38
39
  createRuntimeCommandAppliedBridge,
39
40
  type RuntimeCommandAppliedBridge,
40
41
  } from "../runtime/collab/runtime-collab-sync.ts";
41
42
  import { createInertLayoutFacet } from "../runtime/layout/index.ts";
42
- import { loadDocxEditorSession } from "../io/docx-session.ts";
43
+ import {
44
+ loadDocxEditorSession,
45
+ loadDocxEditorSessionAsync,
46
+ } from "../io/docx-session.ts";
47
+ import {
48
+ createLoadScheduler,
49
+ type LoadScheduler,
50
+ } from "../io/load-scheduler.ts";
43
51
  import {
44
52
  decodePersistedSourcePackageBytes,
45
53
  hasValidPersistedSourcePackageDigest,
@@ -48,6 +56,7 @@ import {
48
56
  createEditorViewStateSnapshot,
49
57
  createViewState,
50
58
  } from "../runtime/view-state.ts";
59
+ import { hydrateEditorStateFromEnvelope } from "../runtime/editor-state-integration.ts";
51
60
  import {
52
61
  recordPerfSample,
53
62
  } from "../ui-tailwind/editor-surface/perf-probe.ts";
@@ -62,6 +71,14 @@ export interface ResolvedSource {
62
71
  initialDocx?: Uint8Array | ArrayBuffer;
63
72
  initialSessionState?: EditorSessionState;
64
73
  initialSnapshot?: PersistedEditorSnapshot;
74
+ /**
75
+ * Fastload P6: when the boundary hook pre-loads the docx via the
76
+ * async loader (DOM environments only), it stashes the session here
77
+ * so `createRuntime` reuses it instead of running another synchronous
78
+ * load. Undefined in SSR / Node test fallback — `createRuntime` then
79
+ * does the classic synchronous load.
80
+ */
81
+ preloadedDocxSession?: ReturnType<typeof loadDocxEditorSession>;
65
82
  }
66
83
 
67
84
  export interface CreateRuntimeArgs {
@@ -280,6 +297,9 @@ export function useEditorRuntimeBoundary(
280
297
  const pendingReadySourceRef = useRef<"docx" | "session" | "snapshot" | null>(null);
281
298
  const autosaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
282
299
  const lastSavedRevisionTokenRef = useRef<string | null>(null);
300
+ // Fastload P6: scheduler used by the DOM-side async docx loader. Held
301
+ // here so it can be disposed on unmount / source change.
302
+ const loadSchedulerRef = useRef<LoadScheduler | null>(null);
283
303
  const hostAdapterRef = useRef(hostAdapter);
284
304
  const datastoreRef = useRef(datastore);
285
305
  const onEventRef = useRef(onEvent);
@@ -355,6 +375,10 @@ export function useEditorRuntimeBoundary(
355
375
  lastSavedRevisionTokenRef.current = null;
356
376
  runtimeRef.current?.dispose?.();
357
377
  runtimeRef.current = null;
378
+ // Fastload P6: dispose any scheduler held from a previous mount /
379
+ // source cycle before allocating a fresh one for this load.
380
+ loadSchedulerRef.current?.dispose();
381
+ loadSchedulerRef.current = null;
358
382
  setRuntime(null);
359
383
 
360
384
  try {
@@ -375,6 +399,33 @@ export function useEditorRuntimeBoundary(
375
399
  return;
376
400
  }
377
401
 
402
+ // Fastload P6: in a DOM environment, preload the docx via the
403
+ // async loader so normalize-body yields mid-walk and the browser
404
+ // can paint the skeleton while the rest of the parse finishes.
405
+ // SSR / Node tests fall through to the synchronous load inside
406
+ // `createRuntime`.
407
+ if (
408
+ source.initialDocx !== undefined &&
409
+ source.preloadedDocxSession === undefined &&
410
+ typeof document !== "undefined"
411
+ ) {
412
+ const scheduler = createLoadScheduler();
413
+ loadSchedulerRef.current = scheduler;
414
+ const preloaded = await loadDocxEditorSessionAsync({
415
+ documentId,
416
+ sourceLabel: source.sourceLabel,
417
+ bytes: source.initialDocx,
418
+ editorBuild: "dev",
419
+ scheduler,
420
+ });
421
+ if (cancelled) {
422
+ scheduler.dispose();
423
+ loadSchedulerRef.current = null;
424
+ return;
425
+ }
426
+ source.preloadedDocxSession = preloaded;
427
+ }
428
+
378
429
  const nextRuntime = createRuntime(
379
430
  {
380
431
  documentId,
@@ -480,6 +531,11 @@ export function useEditorRuntimeBoundary(
480
531
  }
481
532
  runtimeRef.current?.dispose?.();
482
533
  runtimeRef.current = null;
534
+ // Fastload P6: release any pending idle callbacks the load scheduler
535
+ // registered so React unmount doesn't leak setTimeout/IdleCallback
536
+ // handles.
537
+ loadSchedulerRef.current?.dispose();
538
+ loadSchedulerRef.current = null;
483
539
  };
484
540
  }, []);
485
541
 
@@ -568,14 +624,16 @@ function createRuntime(
568
624
  handlers: RuntimeLifecycleHandlers = {},
569
625
  ): WordReviewEditorRuntime {
570
626
  const bootstrapEvents: DocumentRuntimeEvent[] = [];
571
- const docxSession = args.source.initialDocx
572
- ? loadDocxEditorSession({
573
- documentId: args.documentId,
574
- sourceLabel: args.source.sourceLabel,
575
- bytes: args.source.initialDocx,
576
- editorBuild: "dev",
577
- })
578
- : undefined;
627
+ const docxSession =
628
+ args.source.preloadedDocxSession ??
629
+ (args.source.initialDocx
630
+ ? loadDocxEditorSession({
631
+ documentId: args.documentId,
632
+ sourceLabel: args.source.sourceLabel,
633
+ bytes: args.source.initialDocx,
634
+ editorBuild: "dev",
635
+ })
636
+ : undefined);
579
637
  const snapshotExportResolution = !args.source.initialDocx
580
638
  ? resolvePackageBackedExportSession(args)
581
639
  : undefined;
@@ -594,7 +652,7 @@ function createRuntime(
594
652
  ? applySessionExportBarrier(initialSessionState, snapshotExportResolution.barrier)
595
653
  : initialSessionState;
596
654
 
597
- const runtime: WordReviewEditorRuntime = Object.assign(createDocumentRuntime({
655
+ const baseRuntime = createDocumentRuntime({
598
656
  documentId: args.documentId,
599
657
  initialSessionState: runtimeSessionState,
600
658
  sourceKind: args.source.source,
@@ -629,7 +687,39 @@ function createRuntime(
629
687
  },
630
688
  defaultAuthorId: args.currentUserId,
631
689
  onCommandApplied: args.commandAppliedBridge?.onCommandApplied,
632
- }), {
690
+ });
691
+
692
+ // Schema 1.2: drive load-path hydration from the parsed envelope.
693
+ if (docxSession?.initialEditorStatePayload) {
694
+ void hydrateEditorStateFromEnvelope({
695
+ editorState: docxSession.initialEditorStatePayload,
696
+ channel: baseRuntime.editorStateChannel,
697
+ applyBlob: (ns, data) => {
698
+ switch (ns) {
699
+ case "hostAnnotations":
700
+ baseRuntime.setHostAnnotationOverlay(data as import("../api/public-types.ts").HostAnnotationOverlay);
701
+ break;
702
+ case "workflowOverlay":
703
+ baseRuntime.setWorkflowOverlay(data as import("../api/public-types.ts").WorkflowOverlay);
704
+ break;
705
+ case "workflowMetadata":
706
+ // Metadata is split across definitions + entries in the runtime;
707
+ // inline payload carries the full snapshot so we apply entries.
708
+ if (data && typeof data === "object" && "entries" in (data as object)) {
709
+ baseRuntime.setWorkflowMetadataEntries(
710
+ (data as { entries: import("../api/public-types.ts").WorkflowMetadataEntry[] }).entries,
711
+ );
712
+ }
713
+ break;
714
+ case "workItems":
715
+ // workItems namespace is reserved; no runtime setter yet.
716
+ break;
717
+ }
718
+ },
719
+ });
720
+ }
721
+
722
+ const runtime: WordReviewEditorRuntime = Object.assign(baseRuntime, {
633
723
  drainBootstrapEvents: () => bootstrapEvents.splice(0, bootstrapEvents.length),
634
724
  emitBlockedCommand: (
635
725
  command: string,
@@ -806,7 +896,9 @@ function createLoadingRuntimeBridge(input: {
806
896
  openComment: () => undefined,
807
897
  resolveComment: () => undefined,
808
898
  reopenComment: () => undefined,
809
- addCommentReply: () => undefined,
899
+ addCommentReply: () => {
900
+ throw createLoadingBoundaryError(input.snapshot.documentId, "comment");
901
+ },
810
902
  editCommentBody: () => undefined,
811
903
  acceptChange: () => undefined,
812
904
  rejectChange: () => undefined,
@@ -908,6 +1000,17 @@ function createLoadingRuntimeBridge(input: {
908
1000
  items: [],
909
1001
  }),
910
1002
  getRuntimeContextAnalytics: () => null,
1003
+ // Schema 1.2 — no-op stubs for loading boundary (SSR / headless path).
1004
+ configureEditorStatePolicy: () => undefined,
1005
+ registerEditorStateResolver: () => undefined,
1006
+ registerEditorStatePersister: () => undefined,
1007
+ getEditorStateKey: () => undefined,
1008
+ retryPendingPersist: async () => undefined,
1009
+ editorStateChannel: createEditorStateChannel(),
1010
+ getPerfCountersSnapshot: () => ({}),
1011
+ resetPerfCounters: () => undefined,
1012
+ setVisibleBlockRange: () => undefined,
1013
+ requestViewportRefresh: () => undefined,
911
1014
  };
912
1015
  }
913
1016
 
@@ -64,6 +64,16 @@ export interface EditorShellViewProps {
64
64
  interactionGuardSnapshot?: InteractionGuardSnapshot;
65
65
  chromePreset?: WordReviewEditorChromePreset;
66
66
  chromeOptions?: Partial<WordReviewEditorChromeOptions>;
67
+ /** P9g — live collab session for the `"collab"` chrome preset's top nav. */
68
+ collabSession?: import("../runtime/collab-session.ts").CollabSession;
69
+ collabTransportStatus?: import("../api/awareness-identity-types.ts").TransportStatus;
70
+ collabActorId?: string;
71
+ collabSendBaseline?: {
72
+ originDocumentId: string;
73
+ originPayloadId: string;
74
+ originContentHash: string;
75
+ payloadXml: string;
76
+ };
67
77
  reviewQueue?: ReviewQueueSnapshot;
68
78
  documentContextAnalytics?: RuntimeContextAnalyticsSnapshot | null;
69
79
  selectionContextAnalytics?: RuntimeContextAnalyticsSnapshot | null;
@@ -52,6 +52,11 @@ export interface EditorSurfaceControllerProps {
52
52
  onUndo?: () => void;
53
53
  onRedo?: () => void;
54
54
  onBlockedInput?: (command: "paste" | "drop", message: string) => void;
55
+ onPasteApplied?: (meta: {
56
+ segmentCount: number;
57
+ charCount: number;
58
+ source: "paste" | "drop";
59
+ }) => void;
55
60
  onCommentActivated?: (commentId: string) => void;
56
61
  onRevisionActivated?: (revisionId: string) => void;
57
62
  workflowScopes?: readonly WorkflowScope[];
@@ -1,5 +1,15 @@
1
1
  import type { SelectionSnapshot } from "../../api/public-types";
2
2
 
3
+ /**
4
+ * Headless-UI-side `createSelectionSnapshot` that produces the **public**
5
+ * `EditorAnchorProjection` shape (top-level `from`/`to`). The runtime-facing
6
+ * twin at `src/core/state/editor-state.ts` produces the internal
7
+ * `RangeAnchor` shape (`range: { from, to }`). The two are *not*
8
+ * interchangeable — they serve different type contracts. See the
9
+ * `EditorAnchorProjection` definitions in `src/api/public-types.ts` vs
10
+ * `src/core/selection/mapping.ts`. Do not merge without first unifying
11
+ * those two definitions.
12
+ */
3
13
  export function createSelectionSnapshot(anchor: number, head = anchor): SelectionSnapshot {
4
14
  const from = Math.min(anchor, head);
5
15
  const to = Math.max(anchor, head);
@@ -2,6 +2,7 @@ import type {
2
2
  StyleCatalogSnapshot,
3
3
  WorkflowBlockedCommandReason,
4
4
  } from "../api/public-types.ts";
5
+ import { CAPABILITY_BY_ID } from "../runtime/editor-surface/capabilities.ts";
5
6
 
6
7
  export interface ShortcutKeyInput {
7
8
  key: string;
@@ -113,87 +114,35 @@ export function resolveShellShortcut(
113
114
  return { kind: "delegate", shortcut: "zoom-reset" };
114
115
  }
115
116
 
116
- if (isPasteTextOnlyShortcut(input, key)) {
117
- return {
118
- kind: "block",
119
- command: "pasteTextOnly",
120
- reason: createUnsupportedShortcutReason(
121
- "Plain-text paste is not supported in the mounted editor yet.",
122
- ),
123
- };
124
- }
125
-
126
117
  if (isReplaceShortcut(input, key)) {
127
- return {
128
- kind: "block",
129
- command: "replaceText",
130
- reason: createUnsupportedShortcutReason(
131
- "Replace shortcuts are not supported in the mounted editor yet.",
132
- ),
133
- };
118
+ return resolveBlockedCapability("replaceText");
134
119
  }
135
120
 
136
121
  if (
137
122
  isGoToShortcut(input, key) ||
138
123
  (key === "f5" && !input.shiftKey)
139
124
  ) {
140
- return {
141
- kind: "block",
142
- command: "goTo",
143
- reason: createUnsupportedShortcutReason(
144
- "Go To shortcuts are not supported in the mounted editor yet.",
145
- ),
146
- };
125
+ return resolveBlockedCapability("goTo");
147
126
  }
148
127
 
149
128
  if (isModShiftShortcut(input, key, "e")) {
150
- return {
151
- kind: "block",
152
- command: "toggleTrackChanges",
153
- reason: createUnsupportedShortcutReason(
154
- "Track changes authoring shortcuts are not supported in the mounted editor.",
155
- ),
156
- };
129
+ return resolveBlockedCapability("toggleTrackChanges");
157
130
  }
158
131
 
159
132
  if (key === "f7" && !input.shiftKey) {
160
- return {
161
- kind: "block",
162
- command: "checkSpelling",
163
- reason: createUnsupportedShortcutReason(
164
- "Spelling shortcuts are not supported in the mounted editor.",
165
- ),
166
- };
133
+ return resolveBlockedCapability("checkSpelling");
167
134
  }
168
135
 
169
136
  if (key === "f7" && input.shiftKey) {
170
- return {
171
- kind: "block",
172
- command: "openThesaurus",
173
- reason: createUnsupportedShortcutReason(
174
- "Thesaurus shortcuts are not supported in the mounted editor.",
175
- ),
176
- };
137
+ return resolveBlockedCapability("openThesaurus");
177
138
  }
178
139
 
179
140
  if (key === "f8") {
180
- return {
181
- kind: "block",
182
- command: "extendSelection",
183
- reason: createUnsupportedShortcutReason(
184
- "Extend-selection shortcuts are not supported in the mounted editor.",
185
- ),
186
- };
141
+ return resolveBlockedCapability("extendSelection");
187
142
  }
188
143
 
189
144
  if (key === "f5" && input.shiftKey) {
190
- return {
191
- kind: "block",
192
- command: "lastEdit",
193
- reason: createUnsupportedShortcutReason(
194
- "Last-edit shortcuts are not supported in the mounted editor.",
195
- ),
196
- };
145
+ return resolveBlockedCapability("lastEdit");
197
146
  }
198
147
 
199
148
  return { kind: "none" };
@@ -262,10 +211,27 @@ export function resolveHeadingShortcutStyleId(
262
211
  return null;
263
212
  }
264
213
 
265
- function createUnsupportedShortcutReason(message: string): WorkflowBlockedCommandReason {
214
+ /**
215
+ * Look up a `blocked` capability by id and produce the matching
216
+ * `ShellShortcutResolution`. The capability's `blockReason` becomes
217
+ * the dispatcher's returned reason, so editing the user-facing
218
+ * message in `capabilities.ts` is enough to update every shell-layer
219
+ * block path — no dispatcher change needed. Throws if the id is not
220
+ * registered as a blocked capability; the C.1 anchor-check test
221
+ * guarantees every id this file references is present.
222
+ */
223
+ function resolveBlockedCapability(id: string): ShellShortcutResolution {
224
+ const cap = CAPABILITY_BY_ID.get(id);
225
+ if (!cap || cap.kind !== "blocked" || !cap.blockReason) {
226
+ throw new Error(
227
+ `resolveShellShortcut: capability ${id} is not registered as a blocked entry. ` +
228
+ "See src/runtime/editor-surface/capabilities.ts.",
229
+ );
230
+ }
266
231
  return {
267
- code: "unsupported_surface",
268
- message,
232
+ kind: "block",
233
+ command: id,
234
+ reason: cap.blockReason as WorkflowBlockedCommandReason,
269
235
  };
270
236
  }
271
237
 
@@ -357,9 +323,3 @@ function isZoomOutShortcut(input: ShortcutKeyInput, key: string): boolean {
357
323
  (key === "-" || key === "_" || key === "subtract");
358
324
  }
359
325
 
360
- function isPasteTextOnlyShortcut(input: ShortcutKeyInput, key: string): boolean {
361
- return key === "v" &&
362
- Boolean(input.ctrlKey || input.metaKey) &&
363
- !input.altKey &&
364
- Boolean(input.shiftKey);
365
- }
@@ -1,15 +1,75 @@
1
1
  import React from "react";
2
2
 
3
- import type { WordReviewEditorChromePreset } from "../../api/public-types";
3
+ import type {
4
+ WordReviewEditorChromeOptions,
5
+ WordReviewEditorChromePreset,
6
+ } from "../../api/public-types";
7
+ import type { CollabSession } from "../../runtime/collab-session.ts";
8
+ import type { TransportStatus } from "../../api/awareness-identity-types.ts";
4
9
  import {
5
10
  TwToolbar,
6
11
  type TwToolbarProps,
7
12
  } from "../toolbar/tw-toolbar";
13
+ import { CollabTopNavContainer } from "./collab-top-nav-container";
8
14
 
9
15
  export interface ChromePresetToolbarProps extends TwToolbarProps {
10
16
  chromePreset: WordReviewEditorChromePreset;
17
+ /** P9g — optional collab session. Rendered above the toolbar when the `"collab"` preset is active. */
18
+ collabSession?: CollabSession;
19
+ collabTransportStatus?: TransportStatus;
20
+ activeCommentId?: string;
21
+ collabActorId?: string;
22
+ collabSendBaseline?: {
23
+ originDocumentId: string;
24
+ originPayloadId: string;
25
+ originContentHash: string;
26
+ payloadXml: string;
27
+ };
28
+ /**
29
+ * Sub-surface visibility toggles from `resolveChromePresetOptions`.
30
+ * When omitted the container defaults match the `"collab"` preset.
31
+ */
32
+ chromeOptionsResolved?: Pick<
33
+ WordReviewEditorChromeOptions,
34
+ | "showCollabTopNav"
35
+ | "showCollabPresenceStrip"
36
+ | "showCollabRoleChip"
37
+ | "showCollabAudienceChip"
38
+ | "showCollabTamperBanner"
39
+ | "showCollabNegotiationActionBar"
40
+ | "showCollabSendToSupplier"
41
+ >;
11
42
  }
12
43
 
13
44
  export function ChromePresetToolbar(props: ChromePresetToolbarProps) {
14
- return <TwToolbar {...props} preset={props.chromePreset} />;
45
+ const {
46
+ collabSession,
47
+ collabTransportStatus,
48
+ activeCommentId,
49
+ collabActorId,
50
+ collabSendBaseline,
51
+ chromeOptionsResolved,
52
+ ...toolbarProps
53
+ } = props;
54
+
55
+ const collabActive =
56
+ props.chromePreset === "collab" && collabSession !== undefined;
57
+
58
+ return (
59
+ <>
60
+ {collabActive ? (
61
+ <CollabTopNavContainer
62
+ session={collabSession}
63
+ {...(activeCommentId !== undefined ? { activeCommentId } : {})}
64
+ actorId={collabActorId ?? "local"}
65
+ {...(collabTransportStatus
66
+ ? { transportStatus: collabTransportStatus }
67
+ : {})}
68
+ {...(chromeOptionsResolved ? { visibility: chromeOptionsResolved } : {})}
69
+ {...(collabSendBaseline ? { sendBaseline: collabSendBaseline } : {})}
70
+ />
71
+ ) : null}
72
+ <TwToolbar {...toolbarProps} preset={props.chromePreset} />
73
+ </>
74
+ );
15
75
  }