@beyondwork/docx-react-component 1.0.30 → 1.0.32

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 (38) hide show
  1. package/README.md +6 -0
  2. package/package.json +1 -1
  3. package/src/api/public-types.ts +16 -1
  4. package/src/api/session-state.ts +2 -0
  5. package/src/io/docx-session.ts +16 -3
  6. package/src/io/ooxml/parse-footnotes.ts +23 -33
  7. package/src/io/ooxml/parse-headers-footers.ts +20 -21
  8. package/src/io/ooxml/workflow-payload.ts +311 -8
  9. package/src/model/snapshot.ts +113 -1
  10. package/src/runtime/document-runtime.ts +207 -33
  11. package/src/runtime/surface-projection.ts +156 -7
  12. package/src/ui/WordReviewEditor.tsx +13 -5
  13. package/src/ui/editor-surface-controller.tsx +2 -0
  14. package/src/ui/headless/selection-tool-resolver.ts +4 -1
  15. package/src/ui/headless/selection-tool-types.ts +1 -2
  16. package/src/ui/workflow-surface-blocked-rails.ts +19 -1
  17. package/src/ui-tailwind/chrome/responsive-chrome.ts +46 -0
  18. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +4 -4
  19. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +5 -5
  20. package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +3 -3
  21. package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +4 -4
  22. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +14 -9
  23. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +4 -5
  24. package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +5 -5
  25. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +6 -6
  26. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +9 -9
  27. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +173 -124
  28. package/src/ui-tailwind/editor-surface/pm-decorations.ts +88 -14
  29. package/src/ui-tailwind/editor-surface/pm-schema.ts +29 -0
  30. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +13 -1
  31. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +3 -3
  32. package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +20 -0
  33. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +26 -0
  34. package/src/ui-tailwind/review/tw-review-rail.tsx +9 -1
  35. package/src/ui-tailwind/theme/editor-theme.css +8 -0
  36. package/src/ui-tailwind/toolbar/toolbar-layout.ts +47 -0
  37. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +367 -22
  38. package/src/ui-tailwind/tw-review-workspace.tsx +131 -4
@@ -62,6 +62,11 @@ import { preserveEditorSelectionMouseDown } from "../ui/headless/preserve-editor
62
62
  import { TwAlertBanner } from "./chrome/tw-alert-banner";
63
63
  import { TwLayoutPanel } from "./chrome/tw-layout-panel";
64
64
  import { TwPageRuler } from "./chrome/tw-page-ruler";
65
+ import {
66
+ getInitialReviewRailOpen,
67
+ isNarrowChromeViewport,
68
+ resolveResponsiveChromeState,
69
+ } from "./chrome/responsive-chrome";
65
70
  import { ChromePresetToolbar } from "./chrome/chrome-preset-toolbar";
66
71
  import {
67
72
  resolveChromePreset,
@@ -258,7 +263,15 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
258
263
  chromeOptions,
259
264
  chromeVisibility: props.chromeVisibility,
260
265
  });
261
- const showReviewRail = chromeVisibility.reviewRail && (caps?.reviewRailVisible ?? true);
266
+ const reviewRailAvailable = chromeVisibility.reviewRail && (caps?.reviewRailVisible ?? true);
267
+ const [viewportWidth, setViewportWidth] = useState<number | undefined>(() => readViewportWidth());
268
+ const [reviewRailOpen, setReviewRailOpen] = useState(() =>
269
+ getInitialReviewRailOpen({
270
+ viewportWidth: readViewportWidth(),
271
+ reviewRailAvailable,
272
+ }),
273
+ );
274
+ const responsiveChromeSignatureRef = useRef<string | null>(null);
262
275
  const headings = props.documentNavigation?.headings ?? [];
263
276
  const headerVariant = snapshot.pageLayout?.headerVariants[0]?.variant ?? "default";
264
277
  const footerVariant = snapshot.pageLayout?.footerVariants[0]?.variant ?? "default";
@@ -318,6 +331,15 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
318
331
  snapshot.readOnly ||
319
332
  snapshot.activeStory.kind !== "main" ||
320
333
  effectiveSelectionMode !== "edit";
334
+ const responsiveChrome = useMemo(
335
+ () =>
336
+ resolveResponsiveChromeState({
337
+ viewportWidth,
338
+ reviewRailAvailable,
339
+ reviewRailOpen,
340
+ }),
341
+ [reviewRailAvailable, reviewRailOpen, viewportWidth],
342
+ );
321
343
  const toolbarInteractionPolicy: ToolbarInteractionPolicy | undefined = caps
322
344
  ? {
323
345
  mode: effectiveSelectionMode,
@@ -341,6 +363,37 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
341
363
  }
342
364
  }, [isPageWorkspace, snapshot.activeStory.kind]);
343
365
 
366
+ useEffect(() => {
367
+ if (typeof window === "undefined") {
368
+ return;
369
+ }
370
+
371
+ const updateViewportWidth = () => {
372
+ setViewportWidth(readViewportWidth());
373
+ };
374
+
375
+ updateViewportWidth();
376
+ window.addEventListener("resize", updateViewportWidth);
377
+ return () => {
378
+ window.removeEventListener("resize", updateViewportWidth);
379
+ };
380
+ }, []);
381
+
382
+ useEffect(() => {
383
+ const responsiveSignature = `${reviewRailAvailable ? "1" : "0"}:${isNarrowChromeViewport(viewportWidth) ? "n" : "d"}`;
384
+ if (responsiveChromeSignatureRef.current === responsiveSignature) {
385
+ return;
386
+ }
387
+
388
+ responsiveChromeSignatureRef.current = responsiveSignature;
389
+ setReviewRailOpen(
390
+ getInitialReviewRailOpen({
391
+ viewportWidth,
392
+ reviewRailAvailable,
393
+ }),
394
+ );
395
+ }, [reviewRailAvailable, viewportWidth]);
396
+
344
397
  const dismissSelectionToolbar = useCallback(() => {
345
398
  props.onDismissSelectionToolbar?.();
346
399
  }, [props.onDismissSelectionToolbar]);
@@ -353,6 +406,25 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
353
406
  [dismissSelectionToolbar],
354
407
  );
355
408
 
409
+ useEffect(() => {
410
+ if (!responsiveChrome.showDrawerReviewRail || typeof window === "undefined") {
411
+ return;
412
+ }
413
+
414
+ const handleKeyDown = (event: KeyboardEvent) => {
415
+ if (event.key !== "Escape") {
416
+ return;
417
+ }
418
+
419
+ setReviewRailOpen(false);
420
+ };
421
+
422
+ window.addEventListener("keydown", handleKeyDown);
423
+ return () => {
424
+ window.removeEventListener("keydown", handleKeyDown);
425
+ };
426
+ }, [responsiveChrome.showDrawerReviewRail]);
427
+
356
428
  return (
357
429
  <Tooltip.Provider delayDuration={400}>
358
430
  <div className="flex h-full flex-col bg-canvas text-primary">
@@ -366,12 +438,15 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
366
438
  blockedReasons={blockedReasons}
367
439
  showDiagnosticsChrome={chromeVisibility.alerts}
368
440
  interactionPolicy={toolbarInteractionPolicy}
441
+ compactMode={responsiveChrome.isNarrow}
369
442
  workspaceMode={props.workspaceMode}
370
443
  zoomLevel={props.zoomLevel}
371
444
  formattingState={props.formattingState}
372
445
  activeListContext={props.activeListContext}
373
446
  styleCatalog={props.styleCatalog}
374
447
  showTrackedChanges={props.showTrackedChanges}
448
+ showSidebarToggle={responsiveChrome.showSidebarToggle}
449
+ isSidebarOpen={reviewRailOpen}
375
450
  onUndo={runWithSelectionToolbarDismiss(props.onUndo)}
376
451
  onRedo={runWithSelectionToolbarDismiss(props.onRedo)}
377
452
  onSetParagraphStyle={props.onSetParagraphStyle
@@ -444,6 +519,10 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
444
519
  dismissSelectionToolbar();
445
520
  props.onWorkspaceModeChange(value);
446
521
  }}
522
+ onToggleSidebar={() => {
523
+ dismissSelectionToolbar();
524
+ setReviewRailOpen((open) => !open);
525
+ }}
447
526
  onZoomChange={props.onZoomChange
448
527
  ? (level) => {
449
528
  dismissSelectionToolbar();
@@ -483,7 +562,7 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
483
562
  workflowBlockedReasons={blockedReasons}
484
563
  /> : null}
485
564
 
486
- <div className="flex flex-1 min-h-0">
565
+ <div className="relative flex flex-1 min-h-0">
487
566
  {/* Collapsible document navigator — page mode only */}
488
567
  {isPageWorkspace && chromeVisibility.pageChrome ? (
489
568
  <aside
@@ -889,8 +968,8 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
889
968
  ) : null}
890
969
  </div>
891
970
 
892
- {/* Review rail — hidden in editing mode unless toggled */}
893
- {showReviewRail ? <TwReviewRail
971
+ {/* Review rail — docked on desktop, drawer-backed on narrow layouts */}
972
+ {responsiveChrome.showDockedReviewRail ? <TwReviewRail
894
973
  activeTab={props.activeRailTab}
895
974
  currentUserId={props.currentUserId}
896
975
  comments={snapshot.comments}
@@ -917,12 +996,60 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
917
996
  onAcceptAllChanges={props.onAcceptAllChanges}
918
997
  onRejectAllChanges={props.onRejectAllChanges}
919
998
  /> : null}
999
+
1000
+ {responsiveChrome.showDrawerReviewRail ? (
1001
+ <div
1002
+ className="pointer-events-none absolute inset-0 z-30 flex justify-end"
1003
+ data-testid="review-rail-drawer"
1004
+ >
1005
+ <button
1006
+ type="button"
1007
+ aria-label="Close sidebar overlay"
1008
+ className="pointer-events-auto absolute inset-0 border-0 bg-[color:rgba(21,26,23,0.08)] dark:bg-[color:rgba(0,0,0,0.32)]"
1009
+ onClick={() => setReviewRailOpen(false)}
1010
+ />
1011
+ <div className="pointer-events-auto relative h-full">
1012
+ <TwReviewRail
1013
+ variant="drawer"
1014
+ activeTab={props.activeRailTab}
1015
+ currentUserId={props.currentUserId}
1016
+ comments={snapshot.comments}
1017
+ trackedChanges={snapshot.trackedChanges}
1018
+ compatibility={snapshot.compatibility}
1019
+ warnings={snapshot.warnings}
1020
+ markupDisplay={markupDisplay}
1021
+ contextAnalytics={
1022
+ chromeVisibility.contextAnalytics
1023
+ ? props.currentScopeContextAnalytics
1024
+ : null
1025
+ }
1026
+ activeCommentId={props.activeCommentId}
1027
+ activeRevisionId={props.activeRevisionId}
1028
+ onActiveTabChange={props.onActiveRailTabChange}
1029
+ onOpenComment={props.onOpenComment}
1030
+ onResolveComment={props.onResolveComment}
1031
+ onReopenComment={props.onReopenComment}
1032
+ onAddReply={props.onAddReply}
1033
+ onEditBody={props.onEditBody}
1034
+ onOpenRevision={props.onOpenRevision}
1035
+ onAcceptRevision={props.onAcceptRevision}
1036
+ onRejectRevision={props.onRejectRevision}
1037
+ onAcceptAllChanges={props.onAcceptAllChanges}
1038
+ onRejectAllChanges={props.onRejectAllChanges}
1039
+ />
1040
+ </div>
1041
+ </div>
1042
+ ) : null}
920
1043
  </div>
921
1044
  </div>
922
1045
  </Tooltip.Provider>
923
1046
  );
924
1047
  }
925
1048
 
1049
+ function readViewportWidth(): number | undefined {
1050
+ return typeof window === "undefined" ? undefined : window.innerWidth;
1051
+ }
1052
+
926
1053
  function shouldHidePageBorderForSelection(
927
1054
  selection: EditorViewStateSnapshot["selection"],
928
1055
  ): boolean {