@beyondwork/docx-react-component 1.0.80 → 1.0.82

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 (29) hide show
  1. package/package.json +1 -1
  2. package/src/api/public-types.ts +12 -13
  3. package/src/api/v3/_runtime-handle.ts +4 -0
  4. package/src/api/v3/runtime/document.ts +61 -3
  5. package/src/api/v3/runtime/review.ts +55 -2
  6. package/src/api/v3/ui/chrome-composition.ts +10 -2
  7. package/src/io/normalize/normalize-text.ts +4 -1
  8. package/src/io/ooxml/parse-drawing.ts +4 -0
  9. package/src/model/canonical-document.ts +2 -0
  10. package/src/ui/WordReviewEditor.tsx +132 -3
  11. package/src/ui/editor-shell-view.tsx +1 -0
  12. package/src/ui-tailwind/chrome/editor-action-registry.ts +373 -0
  13. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +59 -35
  14. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +2 -0
  15. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
  16. package/src/ui-tailwind/chrome/use-context-menu-controller.ts +15 -10
  17. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +1 -0
  18. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +256 -37
  19. package/src/ui-tailwind/editor-surface/pm-schema.ts +54 -1
  20. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +31 -1
  21. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +15 -0
  22. package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +24 -5
  23. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +35 -6
  24. package/src/ui-tailwind/page-stack/use-visible-block-range.ts +333 -43
  25. package/src/ui-tailwind/review-workspace/types.ts +1 -0
  26. package/src/ui-tailwind/review-workspace/use-page-markers.ts +273 -24
  27. package/src/ui-tailwind/review-workspace/use-workspace-composition.ts +46 -6
  28. package/src/ui-tailwind/theme/editor-theme.css +3 -5
  29. package/src/ui-tailwind/tw-review-workspace.tsx +117 -14
@@ -43,7 +43,10 @@ import {
43
43
  import { TwContextBand } from "./chrome/tw-context-band";
44
44
  import { TwRoleActionRegion } from "./toolbar/tw-role-action-region";
45
45
  import { LocalSurfaceArbiterContext } from "./chrome/local-surface-arbiter";
46
- import { TwWorkspaceChromeHost } from "./chrome/tw-workspace-chrome-host";
46
+ import {
47
+ TwWorkspaceChromeHost,
48
+ type TwWorkspaceChromeHostController,
49
+ } from "./chrome/tw-workspace-chrome-host";
47
50
  import { TwChromeOverlay, TwPageStackOverlayLayer } from "./chrome-overlay";
48
51
  import { TwFloatingImageLayer } from "./page-stack/tw-floating-image-layer.tsx";
49
52
  import { shouldHidePageBorderForSelection } from "./review-workspace/paragraph-layout.ts";
@@ -85,16 +88,14 @@ export type {
85
88
  import type { EditorRole } from "../api/public-types.ts";
86
89
 
87
90
  // Default shell-header modes for the workspace's default composition.
88
- // Designsystem §6.1 prescribes a 4-mode switcher (edit / review / workflow
89
- // / more). "more" is kept in the layout for parity but marked disabled
90
- // until its handler is defined by refactor/11 Slice 7 + refactor/10
91
- // Slice 5 (Phase Q debug UX). Hosts that want a fully-wired 4-mode set
92
- // supply their own `shellHeader` prop.
91
+ // Designsystem §6.1 prescribes a 4-mode switcher; all four are reachable in
92
+ // the product path. "More" is a shell-owned diagnostics/search posture, so it
93
+ // uses `ChromeCompositionInput.modeOverride` instead of changing runtime role.
93
94
  const DEFAULT_WORKSPACE_SHELL_MODES: readonly ShellHeaderModeOption[] = [
94
95
  { id: "edit", label: "Edit" },
95
96
  { id: "review", label: "Review" },
96
97
  { id: "workflow", label: "Workflow" },
97
- { id: "more", label: "More", disabled: true },
98
+ { id: "more", label: "More" },
98
99
  ];
99
100
 
100
101
  function editorRoleToShellMode(role: EditorRole): ShellHeaderMode {
@@ -121,6 +122,47 @@ function shellModeToEditorRole(mode: ShellHeaderMode): EditorRole | null {
121
122
  }
122
123
  }
123
124
 
125
+ function TwMoreContextBandContent(props: {
126
+ onOpenDiagnostics: () => void;
127
+ onOpenCompatibility: () => void;
128
+ onOpenOutline: () => void;
129
+ onOpenSearch?: () => void;
130
+ }): React.JSX.Element {
131
+ return (
132
+ <div
133
+ className="flex min-w-0 items-center gap-1"
134
+ data-testid="more-context-band-content"
135
+ >
136
+ <ContextBandButton label="Diagnostics" onClick={props.onOpenDiagnostics} />
137
+ <ContextBandButton label="Compatibility" onClick={props.onOpenCompatibility} />
138
+ <ContextBandButton label="Outline" onClick={props.onOpenOutline} />
139
+ <ContextBandButton
140
+ label="Search"
141
+ onClick={props.onOpenSearch}
142
+ disabled={!props.onOpenSearch}
143
+ />
144
+ </div>
145
+ );
146
+ }
147
+
148
+ function ContextBandButton(props: {
149
+ label: string;
150
+ onClick?: () => void;
151
+ disabled?: boolean;
152
+ }): React.JSX.Element {
153
+ return (
154
+ <button
155
+ type="button"
156
+ disabled={props.disabled}
157
+ onMouseDown={preserveEditorSelectionMouseDown}
158
+ onClick={props.onClick}
159
+ className="inline-flex h-7 items-center rounded-[var(--radius-md)] px-2.5 text-xs font-medium text-secondary transition-colors hover:bg-hover focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)] disabled:cursor-not-allowed disabled:opacity-40"
160
+ >
161
+ {props.label}
162
+ </button>
163
+ );
164
+ }
165
+
124
166
  export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
125
167
  const props = {
126
168
  ...inputProps,
@@ -128,6 +170,10 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
128
170
  } as TwReviewWorkspaceProps & EditorCommandBag;
129
171
  const { snapshot, viewState } = props;
130
172
  const selectionToolbarRootRef = useRef<HTMLDivElement>(null);
173
+ const workspaceChromeControllerRef =
174
+ useRef<TwWorkspaceChromeHostController | null>(null);
175
+ const [shellModeOverride, setShellModeOverride] =
176
+ useState<ShellHeaderMode | null>(null);
131
177
  // P8.11 — body slot wrapping `{props.document}` (the PM surface) + scroll
132
178
  // root ref. The chrome layer's `TwPageStackChromeLayer` needs both to
133
179
  // measure per-page rects and to reparent PM's DOM node across band
@@ -320,6 +366,9 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
320
366
  pageStackScrollRoot,
321
367
  snapshot,
322
368
  layoutFacet: props.layoutFacet,
369
+ geometryFacet: props.geometryFacet,
370
+ renderFrameRevision,
371
+ viewportScale: zoomScale,
323
372
  });
324
373
 
325
374
  const { dismissSelectionToolbar, runWithSelectionToolbarDismiss } =
@@ -382,18 +431,46 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
382
431
  props.onSetParagraphTabStops,
383
432
  ]);
384
433
 
434
+ const setWorkspaceChromeController = useCallback(
435
+ (instance: TwWorkspaceChromeHostController | null) => {
436
+ workspaceChromeControllerRef.current = instance;
437
+ const ref = props.chromeControllerRef;
438
+ if (!ref) return;
439
+ if (typeof ref === "function") {
440
+ ref(instance);
441
+ return;
442
+ }
443
+ (ref as React.MutableRefObject<TwWorkspaceChromeHostController | null>).current =
444
+ instance;
445
+ },
446
+ [props.chromeControllerRef],
447
+ );
448
+
449
+ const openTableMoreMenu = useCallback(
450
+ (coords: { clientX: number; clientY: number }) => {
451
+ workspaceChromeControllerRef.current?.openWithKinds({
452
+ ...coords,
453
+ kinds: ["table-cell"],
454
+ });
455
+ },
456
+ [],
457
+ );
458
+
385
459
  // Audit §2.4 — the shell header is ALWAYS present in default composition.
386
460
  // When the host does not supply a pre-assembled shell node, fall back to
387
461
  // a default TwShellHeader wired to the workspace's editor-role state so
388
462
  // the mode tabs actually change the active role instead of being
389
- // decorative. The "more" tab is included for layout parity with §6.1
390
- // but disabled until its handler is defined (Phase Q debug UX / Slice 7).
463
+ // decorative. The "more" tab is shell-owned diagnostics/search posture:
464
+ // it drives composition mode via `modeOverride` and does not mutate the
465
+ // runtime editor role.
391
466
  // Host-supplied shells continue to win (back-compat).
392
467
  const defaultShellModes: readonly ShellHeaderModeOption[] =
393
468
  DEFAULT_WORKSPACE_SHELL_MODES;
394
469
  const defaultShellActiveMode: ShellHeaderMode = editorRoleToShellMode(
395
470
  viewState.editorRole,
396
471
  );
472
+ const activeShellMode: ShellHeaderMode =
473
+ shellModeOverride === "more" ? "more" : defaultShellActiveMode;
397
474
  // coord-11 §21 — host-supplied shellHeader wins; otherwise the default
398
475
  // TwShellHeader mounts only when `chromeVisibility.shellHeader === true`
399
476
  // (default for every preset except `selection`). The `selection` preset
@@ -406,8 +483,15 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
406
483
  ? (
407
484
  <TwShellHeader
408
485
  modes={defaultShellModes}
409
- activeMode={defaultShellActiveMode}
486
+ activeMode={activeShellMode}
410
487
  onModeChange={(mode) => {
488
+ if (mode === "more") {
489
+ setShellModeOverride("more");
490
+ setReviewRailOpen(true);
491
+ props.onActiveRailTabChange?.("health");
492
+ return;
493
+ }
494
+ setShellModeOverride(null);
411
495
  const nextRole = shellModeToEditorRole(mode);
412
496
  if (nextRole !== null && nextRole !== viewState.editorRole) {
413
497
  props.onEditorRoleChange?.(nextRole);
@@ -444,10 +528,17 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
444
528
  const composition = useWorkspaceComposition({
445
529
  chromePreset,
446
530
  chromeOptions: props.chromeOptions,
531
+ chromeVisibility: props.chromeVisibility,
447
532
  reviewMode: props.reviewMode,
448
533
  role: viewState.editorRole,
534
+ readOnly: snapshot.readOnly,
449
535
  markupDisplay: props.markupDisplay,
536
+ activeRailTab: props.activeRailTab,
537
+ railOpen: reviewRailOpen,
538
+ density: props.density,
539
+ containerWidth: viewportWidth,
450
540
  diagnosticsSignal,
541
+ modeOverride: shellModeOverride === "more" ? "more" : undefined,
451
542
  });
452
543
  const showHealthRailTab = composition.rail.visibleTabs.has("health");
453
544
 
@@ -481,7 +572,20 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
481
572
  */}
482
573
  {chromeVisibility.toolbar ? (
483
574
  <TwContextBand mode={composition.mode}>
484
- {viewState.editorRole ? (
575
+ {composition.mode === "more" ? (
576
+ <TwMoreContextBandContent
577
+ onOpenDiagnostics={() => {
578
+ setReviewRailOpen(true);
579
+ props.onActiveRailTabChange?.("health");
580
+ }}
581
+ onOpenCompatibility={() => {
582
+ setReviewRailOpen(true);
583
+ props.onActiveRailTabChange?.("health");
584
+ }}
585
+ onOpenOutline={() => setNavOpen(true)}
586
+ onOpenSearch={props.reviewRailFooter?.onSearch}
587
+ />
588
+ ) : viewState.editorRole ? (
485
589
  <TwRoleActionRegion
486
590
  role={viewState.editorRole}
487
591
  policy={scopedChromePolicy}
@@ -574,9 +678,7 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
574
678
  <TwWorkspaceChromeHost
575
679
  mode={composition.mode}
576
680
  editorActionHost={props.editorActionHost}
577
- {...(props.chromeControllerRef
578
- ? { controllerRef: props.chromeControllerRef }
579
- : {})}
681
+ controllerRef={setWorkspaceChromeController}
580
682
  {...(props.commandPaletteDisabled !== undefined
581
683
  ? { paletteDisabled: props.commandPaletteDisabled }
582
684
  : {})}
@@ -875,6 +977,7 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
875
977
  onDistributeColumnsEvenly={props.onDistributeColumnsEvenly}
876
978
  onSetTableAlignment={props.onSetTableAlignment}
877
979
  onSetCellVerticalAlign={props.onSetCellVerticalAlign}
980
+ onOpenTableMore={openTableMoreMenu}
878
981
  chromePins={viewState.chromePins}
879
982
  onChromePinChange={props.onChromePinChange}
880
983
  />