@beyondwork/docx-react-component 1.0.87 → 1.0.89
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.
- package/package.json +1 -1
- package/src/api/v3/_runtime-handle.ts +5 -0
- package/src/api/v3/ai/replacement.ts +82 -0
- package/src/api/v3/runtime/content.ts +3 -0
- package/src/api/v3/runtime/formatting.ts +64 -0
- package/src/core/commands/formatting-commands.ts +107 -0
- package/src/core/state/text-transaction.ts +11 -4
- package/src/runtime/document-runtime.ts +293 -27
- package/src/runtime/scopes/_scope-dependencies.ts +1 -0
- package/src/runtime/scopes/action-validation.ts +12 -3
- package/src/runtime/scopes/audit-bundle.ts +2 -2
- package/src/runtime/scopes/compiler-service.ts +70 -0
- package/src/runtime/scopes/formatting/apply.ts +262 -0
- package/src/runtime/scopes/index.ts +12 -0
- package/src/runtime/scopes/replacement/propose.ts +2 -0
- package/src/runtime/scopes/scope-kinds/paragraph.ts +1 -0
- package/src/runtime/scopes/semantic-scope-types.ts +48 -4
- package/src/runtime/scopes/workflow-overlap.ts +9 -11
- package/src/shell/session-bootstrap.ts +1 -0
- package/src/ui/WordReviewEditor.tsx +277 -28
- package/src/ui/editor-command-bag.ts +11 -0
- package/src/ui/editor-shell-view.tsx +10 -0
- package/src/ui/headless/chrome-registry.ts +6 -6
- package/src/ui/headless/role-action-sets.ts +4 -10
- package/src/ui/headless/selection-tool-resolver.ts +11 -0
- package/src/ui-tailwind/chrome/editor-action-registry.ts +1 -1
- package/src/ui-tailwind/chrome/tw-context-band.tsx +7 -7
- package/src/ui-tailwind/chrome/tw-detach-handle.tsx +13 -18
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +8 -5
- package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +100 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +0 -40
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +9 -7
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +17 -1
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +6 -1
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +17 -7
- package/src/ui-tailwind/editor-surface/preserve-position.ts +61 -11
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +20 -5
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +52 -6
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +5 -1
- package/src/ui-tailwind/review/tw-review-rail.tsx +5 -0
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +32 -38
- package/src/ui-tailwind/review-workspace/types.ts +2 -0
- package/src/ui-tailwind/theme/editor-theme.css +25 -12
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +13 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +6 -15
- package/src/ui-tailwind/tw-review-workspace.tsx +28 -18
- package/src/ui-tailwind/workflow-scope-layers.ts +70 -0
|
@@ -54,6 +54,7 @@ import type {
|
|
|
54
54
|
SurfaceInlineSegment,
|
|
55
55
|
TableOp,
|
|
56
56
|
CanonicalDocumentFragment,
|
|
57
|
+
CanonicalDocumentEnvelope,
|
|
57
58
|
TableOpResult,
|
|
58
59
|
PublicTableEvent,
|
|
59
60
|
PublicTableRenderPlan,
|
|
@@ -136,6 +137,7 @@ import { findTextMatchesForRuntime, searchRuntimeDocument } from "../shell/searc
|
|
|
136
137
|
import {
|
|
137
138
|
type TwProseMirrorSurfaceRef,
|
|
138
139
|
} from "../ui-tailwind/editor-surface/tw-prosemirror-surface";
|
|
140
|
+
import { TwInlineFindBar } from "../ui-tailwind/chrome/tw-inline-find-bar";
|
|
139
141
|
import type { MediaPreviewDescriptor } from "../ui-tailwind/editor-surface/pm-state-from-snapshot";
|
|
140
142
|
import {
|
|
141
143
|
incrementInvalidationCounter,
|
|
@@ -1062,13 +1064,17 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1062
1064
|
} = props;
|
|
1063
1065
|
|
|
1064
1066
|
const [activeRailTab, setActiveRailTab] = useState<ReviewRailTab>("comments");
|
|
1067
|
+
const [inlineFindOpen, setInlineFindOpen] = useState(false);
|
|
1068
|
+
const [inlineFindQuery, setInlineFindQuery] = useState("");
|
|
1069
|
+
const [inlineFindResults, setInlineFindResults] = useState<readonly SearchResultSnapshot[]>([]);
|
|
1070
|
+
const [inlineFindActiveIndex, setInlineFindActiveIndex] = useState(0);
|
|
1065
1071
|
const [localMarkupDisplay, setLocalMarkupDisplay] =
|
|
1066
1072
|
useState<WorkflowMarkupMode | null>(() => suggestionsEnabled ? "all" : null);
|
|
1067
1073
|
const [activeRevisionId, setActiveRevisionId] = useState<string | undefined>();
|
|
1068
1074
|
const [suppressedSuggestionRevisionId, setSuppressedSuggestionRevisionId] = useState<string | null>(null);
|
|
1069
1075
|
const [selectionToolbarAnchor, setSelectionToolbarAnchor] = useState<SelectionToolbarAnchor | null>(null);
|
|
1070
1076
|
const [selectionToolbarDismissedKey, setSelectionToolbarDismissedKey] = useState<string | null>(null);
|
|
1071
|
-
const [
|
|
1077
|
+
const [, setSelectionToolbarFocusWithin] = useState(false);
|
|
1072
1078
|
const [hostAnnotationOverlayState, setHostAnnotationOverlayState] =
|
|
1073
1079
|
useState<HostAnnotationOverlay | null>(null);
|
|
1074
1080
|
const [activeReviewQueueItemId, setActiveReviewQueueItemId] = useState<string | null>(null);
|
|
@@ -2536,6 +2542,17 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2536
2542
|
!capabilities.canAddComment && !snapshot.selection.isCollapsed
|
|
2537
2543
|
? "Select text within one paragraph to add a DOCX comment."
|
|
2538
2544
|
: undefined;
|
|
2545
|
+
const selectionWorkflowPosture = resolveSelectionWorkflowPosture(
|
|
2546
|
+
snapshot,
|
|
2547
|
+
viewState,
|
|
2548
|
+
workflowScopeSnapshot,
|
|
2549
|
+
interactionGuardSnapshot,
|
|
2550
|
+
);
|
|
2551
|
+
const effectiveInteractionGuardSnapshot = createSelectionScopedInteractionGuardSnapshot(
|
|
2552
|
+
interactionGuardSnapshot,
|
|
2553
|
+
selectionWorkflowPosture,
|
|
2554
|
+
workflowScopeSnapshot,
|
|
2555
|
+
);
|
|
2539
2556
|
const activeImageContext = useMemo(
|
|
2540
2557
|
() =>
|
|
2541
2558
|
buildActiveImageContext({
|
|
@@ -2584,16 +2601,117 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2584
2601
|
),
|
|
2585
2602
|
[canonicalDocument, snapshot],
|
|
2586
2603
|
);
|
|
2604
|
+
const navigateInlineFindResult = useCallback(
|
|
2605
|
+
(results: readonly SearchResultSnapshot[], index: number) => {
|
|
2606
|
+
const result = results[index];
|
|
2607
|
+
if (!result) return;
|
|
2608
|
+
applyRuntimeSelection(
|
|
2609
|
+
activeRuntime,
|
|
2610
|
+
createSelectionFromAnchor(result.anchor, result.storyTarget),
|
|
2611
|
+
);
|
|
2612
|
+
},
|
|
2613
|
+
[activeRuntime],
|
|
2614
|
+
);
|
|
2615
|
+
const refreshInlineFindResults = useCallback(
|
|
2616
|
+
(query: string, requestedIndex = 0) => {
|
|
2617
|
+
const normalizedQuery = query.trim();
|
|
2618
|
+
if (!normalizedQuery) {
|
|
2619
|
+
surfaceRef.current?.clearSearch();
|
|
2620
|
+
setInlineFindResults([]);
|
|
2621
|
+
setInlineFindActiveIndex(0);
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
const results = searchRuntimeDocument(
|
|
2625
|
+
activeRuntime,
|
|
2626
|
+
surfaceRef.current,
|
|
2627
|
+
normalizedQuery,
|
|
2628
|
+
{ limit: 500 },
|
|
2629
|
+
);
|
|
2630
|
+
const nextIndex =
|
|
2631
|
+
results.length > 0 ? wrapSearchResultIndex(requestedIndex, results.length) : 0;
|
|
2632
|
+
setInlineFindResults(results);
|
|
2633
|
+
setInlineFindActiveIndex(nextIndex);
|
|
2634
|
+
navigateInlineFindResult(results, nextIndex);
|
|
2635
|
+
},
|
|
2636
|
+
[activeRuntime, navigateInlineFindResult],
|
|
2637
|
+
);
|
|
2638
|
+
const handleOpenInlineFind = useCallback(() => {
|
|
2639
|
+
setInlineFindOpen(true);
|
|
2640
|
+
if (inlineFindQuery.trim()) {
|
|
2641
|
+
refreshInlineFindResults(inlineFindQuery, inlineFindActiveIndex);
|
|
2642
|
+
}
|
|
2643
|
+
}, [inlineFindActiveIndex, inlineFindQuery, refreshInlineFindResults]);
|
|
2644
|
+
const handleCloseInlineFind = useCallback(() => {
|
|
2645
|
+
setInlineFindOpen(false);
|
|
2646
|
+
surfaceRef.current?.clearSearch();
|
|
2647
|
+
}, []);
|
|
2648
|
+
const handleInlineFindQueryChange = useCallback(
|
|
2649
|
+
(query: string) => {
|
|
2650
|
+
setInlineFindQuery(query);
|
|
2651
|
+
refreshInlineFindResults(query, 0);
|
|
2652
|
+
},
|
|
2653
|
+
[refreshInlineFindResults],
|
|
2654
|
+
);
|
|
2655
|
+
const handleInlineFindNext = useCallback(() => {
|
|
2656
|
+
if (inlineFindResults.length === 0) {
|
|
2657
|
+
refreshInlineFindResults(inlineFindQuery, 0);
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
const nextIndex = wrapSearchResultIndex(
|
|
2661
|
+
inlineFindActiveIndex + 1,
|
|
2662
|
+
inlineFindResults.length,
|
|
2663
|
+
);
|
|
2664
|
+
setInlineFindActiveIndex(nextIndex);
|
|
2665
|
+
navigateInlineFindResult(inlineFindResults, nextIndex);
|
|
2666
|
+
}, [
|
|
2667
|
+
inlineFindActiveIndex,
|
|
2668
|
+
inlineFindQuery,
|
|
2669
|
+
inlineFindResults,
|
|
2670
|
+
navigateInlineFindResult,
|
|
2671
|
+
refreshInlineFindResults,
|
|
2672
|
+
]);
|
|
2673
|
+
const handleInlineFindPrevious = useCallback(() => {
|
|
2674
|
+
if (inlineFindResults.length === 0) {
|
|
2675
|
+
refreshInlineFindResults(inlineFindQuery, 0);
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
const nextIndex = wrapSearchResultIndex(
|
|
2679
|
+
inlineFindActiveIndex - 1,
|
|
2680
|
+
inlineFindResults.length,
|
|
2681
|
+
);
|
|
2682
|
+
setInlineFindActiveIndex(nextIndex);
|
|
2683
|
+
navigateInlineFindResult(inlineFindResults, nextIndex);
|
|
2684
|
+
}, [
|
|
2685
|
+
inlineFindActiveIndex,
|
|
2686
|
+
inlineFindQuery,
|
|
2687
|
+
inlineFindResults,
|
|
2688
|
+
navigateInlineFindResult,
|
|
2689
|
+
refreshInlineFindResults,
|
|
2690
|
+
]);
|
|
2587
2691
|
const suggestionsSnapshot = activeRuntime.getSuggestionsSnapshot();
|
|
2692
|
+
const caretCommentPoint =
|
|
2693
|
+
snapshot.selection.isCollapsed && snapshot.selection.activeRange.kind === "range"
|
|
2694
|
+
? snapshot.selection.activeRange.from
|
|
2695
|
+
: null;
|
|
2696
|
+
const caretCommentThread =
|
|
2697
|
+
caretCommentPoint !== null
|
|
2698
|
+
? snapshot.comments.threads.find((thread) => {
|
|
2699
|
+
if (thread.status !== "open" || thread.anchor.kind !== "range") {
|
|
2700
|
+
return false;
|
|
2701
|
+
}
|
|
2702
|
+
return caretCommentPoint >= thread.anchor.from && caretCommentPoint <= thread.anchor.to;
|
|
2703
|
+
})
|
|
2704
|
+
: undefined;
|
|
2588
2705
|
const activeCommentThread =
|
|
2589
|
-
snapshot.comments.activeCommentId
|
|
2706
|
+
(snapshot.comments.activeCommentId
|
|
2590
2707
|
? snapshot.comments.threads.find((thread) => thread.commentId === snapshot.comments.activeCommentId)
|
|
2591
|
-
: undefined;
|
|
2708
|
+
: undefined) ?? caretCommentThread;
|
|
2709
|
+
const effectiveActiveCommentId = snapshot.comments.activeCommentId ?? activeCommentThread?.commentId;
|
|
2592
2710
|
const scopedChromePolicy = resolveScopedChromePolicy({
|
|
2593
2711
|
preset: effectiveChromePreset,
|
|
2594
2712
|
compactMode: false,
|
|
2595
2713
|
capabilities,
|
|
2596
|
-
interactionGuardSnapshot,
|
|
2714
|
+
interactionGuardSnapshot: effectiveInteractionGuardSnapshot,
|
|
2597
2715
|
workflowScopeSnapshot,
|
|
2598
2716
|
activeListContext: viewState.activeListContext,
|
|
2599
2717
|
});
|
|
@@ -2605,7 +2723,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2605
2723
|
styleCatalog,
|
|
2606
2724
|
formattingState,
|
|
2607
2725
|
workflowScopeSnapshot,
|
|
2608
|
-
interactionGuardSnapshot,
|
|
2726
|
+
interactionGuardSnapshot: effectiveInteractionGuardSnapshot,
|
|
2609
2727
|
addCommentDisabledReason,
|
|
2610
2728
|
});
|
|
2611
2729
|
const suggestionCard = buildSuggestionCardModel({
|
|
@@ -2614,7 +2732,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2614
2732
|
viewState,
|
|
2615
2733
|
capabilities,
|
|
2616
2734
|
workflowScopeSnapshot,
|
|
2617
|
-
interactionGuardSnapshot,
|
|
2735
|
+
interactionGuardSnapshot: effectiveInteractionGuardSnapshot,
|
|
2618
2736
|
activeRevisionId,
|
|
2619
2737
|
suppressedSuggestionRevisionId,
|
|
2620
2738
|
addCommentDisabledReason,
|
|
@@ -2659,11 +2777,11 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2659
2777
|
styleCatalog,
|
|
2660
2778
|
formattingState,
|
|
2661
2779
|
workflowScopeSnapshot,
|
|
2662
|
-
interactionGuardSnapshot,
|
|
2780
|
+
interactionGuardSnapshot: effectiveInteractionGuardSnapshot,
|
|
2663
2781
|
workflowMarkupSnapshot: workflowMarkupSnapshot ?? undefined,
|
|
2664
2782
|
suggestionsSnapshot,
|
|
2665
2783
|
activeRevisionId,
|
|
2666
|
-
activeCommentId:
|
|
2784
|
+
activeCommentId: effectiveActiveCommentId,
|
|
2667
2785
|
activeCommentThread,
|
|
2668
2786
|
activeTableContext,
|
|
2669
2787
|
activeImageContext,
|
|
@@ -2682,19 +2800,14 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2682
2800
|
snapshot.selection,
|
|
2683
2801
|
viewState.activeStory,
|
|
2684
2802
|
activeRevisionId,
|
|
2685
|
-
|
|
2803
|
+
effectiveActiveCommentId,
|
|
2686
2804
|
),
|
|
2687
|
-
[activeRevisionId,
|
|
2805
|
+
[activeRevisionId, effectiveActiveCommentId, snapshot.selection, viewState.activeStory],
|
|
2688
2806
|
);
|
|
2689
2807
|
const shouldRenderSelectionChrome = Boolean(
|
|
2690
2808
|
activeSelectionTool &&
|
|
2691
2809
|
selectionToolbarSelectionKey &&
|
|
2692
|
-
selectionToolbarDismissedKey !== selectionToolbarSelectionKey
|
|
2693
|
-
(
|
|
2694
|
-
viewState.isFocused ||
|
|
2695
|
-
selectionToolbarFocusWithin ||
|
|
2696
|
-
activeSelectionTool.kind === "structure-context"
|
|
2697
|
-
),
|
|
2810
|
+
selectionToolbarDismissedKey !== selectionToolbarSelectionKey,
|
|
2698
2811
|
);
|
|
2699
2812
|
const accessibilityInstructionsId = `${documentId}-accessibility-instructions`;
|
|
2700
2813
|
const accessibilityStatusId = `${documentId}-accessibility-status`;
|
|
@@ -2978,16 +3091,20 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2978
3091
|
// not, the event falls through to the browser (Ctrl+F opens
|
|
2979
3092
|
// Find, Ctrl+Plus zooms, etc.) — matching the legacy behavior.
|
|
2980
3093
|
let handled = false;
|
|
2981
|
-
if (shortcut.shortcut === "find"
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
3094
|
+
if (shortcut.shortcut === "find") {
|
|
3095
|
+
if (onFindRequested) {
|
|
3096
|
+
// selectionText is intentionally empty — hosts that need the
|
|
3097
|
+
// selected text already receive it via the selection_changed
|
|
3098
|
+
// event + canonicalDocument they have via onEvent listeners.
|
|
3099
|
+
// The range is the load-bearing field so host Find panels can
|
|
3100
|
+
// scope their search to the selection or pre-populate from it.
|
|
3101
|
+
onFindRequested({
|
|
3102
|
+
selectionText: "",
|
|
3103
|
+
selectionRange: snapshot.selection,
|
|
3104
|
+
});
|
|
3105
|
+
} else {
|
|
3106
|
+
handleOpenInlineFind();
|
|
3107
|
+
}
|
|
2991
3108
|
handled = true;
|
|
2992
3109
|
} else if (shortcut.shortcut === "print" && onPrintRequested) {
|
|
2993
3110
|
onPrintRequested();
|
|
@@ -3373,6 +3490,54 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3373
3490
|
type: "set-cell-background",
|
|
3374
3491
|
color,
|
|
3375
3492
|
}),
|
|
3493
|
+
onToggleRowHeader: () => {
|
|
3494
|
+
if (!activeTableContext?.operations.setRowIsHeader.enabled) return;
|
|
3495
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
3496
|
+
type: "set-row-is-header",
|
|
3497
|
+
rowIndex: activeTableContext.currentCell.rowIndex,
|
|
3498
|
+
value: !activeTableContext.currentCell.isHeader,
|
|
3499
|
+
});
|
|
3500
|
+
},
|
|
3501
|
+
onToggleRowCantSplit: () => {
|
|
3502
|
+
if (!activeTableContext?.operations.setRowCantSplit.enabled) return;
|
|
3503
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
3504
|
+
type: "set-row-cant-split",
|
|
3505
|
+
rowIndex: activeTableContext.currentCell.rowIndex,
|
|
3506
|
+
value: !readTableRowCantSplit(
|
|
3507
|
+
canonicalDocument,
|
|
3508
|
+
activeTableContext.tableBlockIndex,
|
|
3509
|
+
activeTableContext.currentCell.rowIndex,
|
|
3510
|
+
),
|
|
3511
|
+
});
|
|
3512
|
+
},
|
|
3513
|
+
onDistributeColumnsEvenly: () =>
|
|
3514
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
3515
|
+
type: "distribute-columns-evenly",
|
|
3516
|
+
}),
|
|
3517
|
+
onSetTableAlignment: (alignment) =>
|
|
3518
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
3519
|
+
type: "set-table-alignment",
|
|
3520
|
+
alignment,
|
|
3521
|
+
}),
|
|
3522
|
+
onSetCellVerticalAlign: (align) =>
|
|
3523
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
3524
|
+
type: "set-cell-vertical-align",
|
|
3525
|
+
locator: { kind: "anchor" },
|
|
3526
|
+
align,
|
|
3527
|
+
}),
|
|
3528
|
+
onSetColumnWidth: (columnIndex, twips) =>
|
|
3529
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
3530
|
+
type: "set-column-width",
|
|
3531
|
+
columnIndex,
|
|
3532
|
+
twips,
|
|
3533
|
+
}),
|
|
3534
|
+
onSetRowHeight: (rowIndex, twips, rule) =>
|
|
3535
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
3536
|
+
type: "set-row-height",
|
|
3537
|
+
rowIndex,
|
|
3538
|
+
twips,
|
|
3539
|
+
rule,
|
|
3540
|
+
}),
|
|
3376
3541
|
onSetImageLayout: (mediaId, dimensions) =>
|
|
3377
3542
|
applyRuntimeImageResize(activeRuntime, mediaId, dimensions),
|
|
3378
3543
|
onSetImageFrame: (mediaId, offsets) =>
|
|
@@ -3508,7 +3673,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3508
3673
|
onAddComment: commands.onAddComment,
|
|
3509
3674
|
onFindRequested: onFindRequested
|
|
3510
3675
|
? () => onFindRequested({ selectionText: "", selectionRange: snapshot.selection })
|
|
3511
|
-
:
|
|
3676
|
+
: handleOpenInlineFind,
|
|
3512
3677
|
onReplaceRequested: onReplaceRequested
|
|
3513
3678
|
? () => onReplaceRequested({ selectionText: "", selectionRange: snapshot.selection })
|
|
3514
3679
|
: undefined,
|
|
@@ -3534,6 +3699,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3534
3699
|
activeRuntime,
|
|
3535
3700
|
commands,
|
|
3536
3701
|
editorActionHost,
|
|
3702
|
+
handleOpenInlineFind,
|
|
3537
3703
|
onFindRequested,
|
|
3538
3704
|
onGoToRequested,
|
|
3539
3705
|
onPrintRequested,
|
|
@@ -3662,7 +3828,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3662
3828
|
layoutFacet={activeRuntime.layout}
|
|
3663
3829
|
geometryFacet={activeRuntime.geometry}
|
|
3664
3830
|
workflowFacet={activeRuntime.workflow}
|
|
3665
|
-
interactionGuardSnapshot={
|
|
3831
|
+
interactionGuardSnapshot={effectiveInteractionGuardSnapshot}
|
|
3666
3832
|
chromePreset={effectiveChromePreset}
|
|
3667
3833
|
chromeOptions={chromeOptions}
|
|
3668
3834
|
density={density}
|
|
@@ -3688,6 +3854,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3688
3854
|
currentScopeContextAnalytics={currentScopeContextAnalytics}
|
|
3689
3855
|
activeSelectionTool={shouldRenderSelectionChrome ? activeSelectionTool : null}
|
|
3690
3856
|
selectionToolAnchor={shouldRenderSelectionChrome ? selectionToolbarAnchor : null}
|
|
3857
|
+
tableContext={activeTableContext}
|
|
3691
3858
|
onAddCommentFromSelection={addSelectionToolbarComment}
|
|
3692
3859
|
onAddCommentFromSuggestion={addSelectionToolbarComment}
|
|
3693
3860
|
onAcceptSuggestion={activeSelectionTool?.kind === "suggestion-review"
|
|
@@ -3703,6 +3870,20 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3703
3870
|
onSelectionToolbarFocusCapture={handleSelectionToolbarFocusCapture}
|
|
3704
3871
|
onSelectionToolbarBlurCapture={handleSelectionToolbarBlurCapture}
|
|
3705
3872
|
selectionToolbarRef={selectionToolbarElementRef}
|
|
3873
|
+
onOpenInlineFind={handleOpenInlineFind}
|
|
3874
|
+
inlineFindBar={
|
|
3875
|
+
inlineFindOpen ? (
|
|
3876
|
+
<TwInlineFindBar
|
|
3877
|
+
query={inlineFindQuery}
|
|
3878
|
+
activeIndex={inlineFindActiveIndex}
|
|
3879
|
+
resultCount={inlineFindResults.length}
|
|
3880
|
+
onQueryChange={handleInlineFindQueryChange}
|
|
3881
|
+
onPrevious={handleInlineFindPrevious}
|
|
3882
|
+
onNext={handleInlineFindNext}
|
|
3883
|
+
onClose={handleCloseInlineFind}
|
|
3884
|
+
/>
|
|
3885
|
+
) : null
|
|
3886
|
+
}
|
|
3706
3887
|
commands={commands}
|
|
3707
3888
|
document={documentElement}
|
|
3708
3889
|
onReviewSidebarTrackedChanges={onReviewSidebarTrackedChanges}
|
|
@@ -4070,6 +4251,23 @@ function openStoryForPage(
|
|
|
4070
4251
|
runtime.openStory(target);
|
|
4071
4252
|
}
|
|
4072
4253
|
|
|
4254
|
+
function wrapSearchResultIndex(index: number, resultCount: number): number {
|
|
4255
|
+
if (resultCount <= 0) return 0;
|
|
4256
|
+
return ((index % resultCount) + resultCount) % resultCount;
|
|
4257
|
+
}
|
|
4258
|
+
|
|
4259
|
+
function readTableRowCantSplit(
|
|
4260
|
+
document: CanonicalDocumentEnvelope,
|
|
4261
|
+
tableBlockIndex: number,
|
|
4262
|
+
rowIndex: number,
|
|
4263
|
+
): boolean {
|
|
4264
|
+
const root = document.content;
|
|
4265
|
+
if (!root || root.type !== "doc") return false;
|
|
4266
|
+
const block = root.children[tableBlockIndex];
|
|
4267
|
+
if (!block || block.type !== "table") return false;
|
|
4268
|
+
return block.rows[rowIndex]?.cantSplit === true;
|
|
4269
|
+
}
|
|
4270
|
+
|
|
4073
4271
|
function applyRegionAttributes(shell: HTMLElement): void {
|
|
4074
4272
|
const toolbar = shell.querySelector<HTMLElement>("header");
|
|
4075
4273
|
if (toolbar) {
|
|
@@ -5013,6 +5211,46 @@ function createSelectionToolbarWorkflowBadge(
|
|
|
5013
5211
|
}
|
|
5014
5212
|
}
|
|
5015
5213
|
|
|
5214
|
+
function createSelectionScopedInteractionGuardSnapshot(
|
|
5215
|
+
base: InteractionGuardSnapshot,
|
|
5216
|
+
posture: {
|
|
5217
|
+
mode: "edit" | "suggest" | "comment" | "view" | "blocked";
|
|
5218
|
+
disabledReason?: string;
|
|
5219
|
+
},
|
|
5220
|
+
workflowScopeSnapshot?: WorkflowScopeSnapshot | null,
|
|
5221
|
+
): InteractionGuardSnapshot {
|
|
5222
|
+
if (posture.mode === "edit") {
|
|
5223
|
+
return base;
|
|
5224
|
+
}
|
|
5225
|
+
|
|
5226
|
+
const targetAccess =
|
|
5227
|
+
posture.mode === "suggest"
|
|
5228
|
+
? "suggest"
|
|
5229
|
+
: posture.mode === "comment"
|
|
5230
|
+
? "comment-only"
|
|
5231
|
+
: posture.mode === "view"
|
|
5232
|
+
? "view-only"
|
|
5233
|
+
: "blocked";
|
|
5234
|
+
const blockedReasons =
|
|
5235
|
+
posture.mode === "blocked" && posture.disabledReason
|
|
5236
|
+
? [{
|
|
5237
|
+
code: "outside_workflow_scope" as const,
|
|
5238
|
+
message: posture.disabledReason,
|
|
5239
|
+
...(workflowScopeSnapshot?.activeWorkItemId
|
|
5240
|
+
? { workItemId: workflowScopeSnapshot.activeWorkItemId }
|
|
5241
|
+
: {}),
|
|
5242
|
+
}]
|
|
5243
|
+
: base.blockedReasons;
|
|
5244
|
+
|
|
5245
|
+
return {
|
|
5246
|
+
...base,
|
|
5247
|
+
effectiveMode: posture.mode,
|
|
5248
|
+
targetAccess,
|
|
5249
|
+
...(posture.disabledReason ? { disabledReason: posture.disabledReason } : {}),
|
|
5250
|
+
blockedReasons,
|
|
5251
|
+
};
|
|
5252
|
+
}
|
|
5253
|
+
|
|
5016
5254
|
function resolveSelectionWorkflowPosture(
|
|
5017
5255
|
snapshot: RuntimeRenderSnapshot,
|
|
5018
5256
|
viewState: ReturnType<WordReviewEditorRuntime["getViewState"]>,
|
|
@@ -5093,6 +5331,17 @@ function resolveSelectionWorkflowPosture(
|
|
|
5093
5331
|
return activeRange.from >= scopeFrom && activeRange.to <= scopeTo;
|
|
5094
5332
|
})
|
|
5095
5333
|
: null;
|
|
5334
|
+
const activeWorkItem = workflowScopeSnapshot?.activeWorkItem;
|
|
5335
|
+
const activeWorkItemHasScopes = Boolean(
|
|
5336
|
+
activeWorkItem &&
|
|
5337
|
+
workflowScopeSnapshot?.scopes.some((scope) => scope.workItemId === activeWorkItem.workItemId),
|
|
5338
|
+
);
|
|
5339
|
+
if (activeRange && activeWorkItem && activeWorkItemHasScopes && !matchingScope) {
|
|
5340
|
+
return {
|
|
5341
|
+
mode: "blocked",
|
|
5342
|
+
disabledReason: `Unavailable here. "${activeWorkItem.title}" only applies inside its assigned scope.`,
|
|
5343
|
+
};
|
|
5344
|
+
}
|
|
5096
5345
|
|
|
5097
5346
|
if (matchingScope?.mode === "suggest") {
|
|
5098
5347
|
return {
|
|
@@ -63,6 +63,17 @@ export interface EditorCommandBag {
|
|
|
63
63
|
onMergeCells?(): void;
|
|
64
64
|
onSplitCell?(): void;
|
|
65
65
|
onSetCellBackground?(color: string): void;
|
|
66
|
+
onToggleRowHeader?(): void;
|
|
67
|
+
onToggleRowCantSplit?(): void;
|
|
68
|
+
onDistributeColumnsEvenly?(): void;
|
|
69
|
+
onSetTableAlignment?(alignment: "left" | "center" | "right"): void;
|
|
70
|
+
onSetCellVerticalAlign?(align: "top" | "center" | "bottom"): void;
|
|
71
|
+
onSetColumnWidth?(columnIndex: number, twips: number): void;
|
|
72
|
+
onSetRowHeight?(
|
|
73
|
+
rowIndex: number,
|
|
74
|
+
twips: number,
|
|
75
|
+
rule: "auto" | "atLeast" | "exact",
|
|
76
|
+
): void;
|
|
66
77
|
onSetImageLayout?(
|
|
67
78
|
mediaId: string,
|
|
68
79
|
dimensions: { widthEmu: number; heightEmu: number },
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
RuntimeContextAnalyticsSnapshot,
|
|
12
12
|
RuntimeRenderSnapshot,
|
|
13
13
|
StyleCatalogSnapshot,
|
|
14
|
+
TableStructureContextSnapshot,
|
|
14
15
|
WordReviewEditorChromeOptions,
|
|
15
16
|
WordReviewEditorChromePreset,
|
|
16
17
|
WordReviewEditorChromeVisibility,
|
|
@@ -90,6 +91,8 @@ export interface EditorShellViewProps {
|
|
|
90
91
|
import("../ui-tailwind/chrome/tw-workspace-chrome-host.tsx").TwWorkspaceChromeHostController
|
|
91
92
|
>;
|
|
92
93
|
commandPaletteDisabled?: boolean;
|
|
94
|
+
inlineFindBar?: ReactNode;
|
|
95
|
+
onOpenInlineFind?: () => void;
|
|
93
96
|
/** P9g — live collab session for the `"collab"` chrome preset's top nav. */
|
|
94
97
|
collabSession?: import("../runtime/collab-session.ts").CollabSession;
|
|
95
98
|
collabTransportStatus?: import("../api/awareness-identity-types.ts").TransportStatus;
|
|
@@ -106,6 +109,7 @@ export interface EditorShellViewProps {
|
|
|
106
109
|
currentScopeContextAnalytics?: RuntimeContextAnalyticsSnapshot | null;
|
|
107
110
|
activeSelectionTool?: ActiveSelectionToolModel | null;
|
|
108
111
|
selectionToolAnchor?: SelectionToolAnchor | null;
|
|
112
|
+
tableContext?: TableStructureContextSnapshot | null;
|
|
109
113
|
documentNavigation?: DocumentNavigationSnapshot;
|
|
110
114
|
commands: EditorCommandBag;
|
|
111
115
|
shellHeader?: ReactNode;
|
|
@@ -185,6 +189,7 @@ export function EditorShellView(props: EditorShellViewProps) {
|
|
|
185
189
|
onShellKeyDownCapture,
|
|
186
190
|
document,
|
|
187
191
|
commands,
|
|
192
|
+
inlineFindBar,
|
|
188
193
|
...workspaceProps
|
|
189
194
|
} = props;
|
|
190
195
|
|
|
@@ -230,6 +235,11 @@ export function EditorShellView(props: EditorShellViewProps) {
|
|
|
230
235
|
document={document}
|
|
231
236
|
{...workspaceProps}
|
|
232
237
|
/>
|
|
238
|
+
{inlineFindBar ? (
|
|
239
|
+
<div className="pointer-events-none absolute right-6 top-16 z-50">
|
|
240
|
+
{inlineFindBar}
|
|
241
|
+
</div>
|
|
242
|
+
) : null}
|
|
233
243
|
</div>
|
|
234
244
|
);
|
|
235
245
|
}
|
|
@@ -170,7 +170,7 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
|
|
|
170
170
|
id: "text-style-selectors",
|
|
171
171
|
surfaces: ["top-toolbar"],
|
|
172
172
|
group: "text",
|
|
173
|
-
presets: ["advanced", "workflow"],
|
|
173
|
+
presets: ["advanced", "review", "workflow"],
|
|
174
174
|
roles: ALL_ROLES,
|
|
175
175
|
fullPlacement: "inline",
|
|
176
176
|
compactPlacement: "overflow",
|
|
@@ -200,7 +200,7 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
|
|
|
200
200
|
id: "text-colors",
|
|
201
201
|
surfaces: ["top-toolbar"],
|
|
202
202
|
group: "text",
|
|
203
|
-
presets: ["simple", "advanced"],
|
|
203
|
+
presets: ["simple", "advanced", "review"],
|
|
204
204
|
roles: EDITOR_AND_REVIEW,
|
|
205
205
|
fullPlacement: "inline",
|
|
206
206
|
compactPlacement: "inline",
|
|
@@ -214,7 +214,7 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
|
|
|
214
214
|
id: "paragraph-alignment",
|
|
215
215
|
surfaces: ["top-toolbar"],
|
|
216
216
|
group: "paragraph",
|
|
217
|
-
presets: ["simple", "advanced"],
|
|
217
|
+
presets: ["simple", "advanced", "review"],
|
|
218
218
|
roles: EDITOR_AND_REVIEW,
|
|
219
219
|
fullPlacement: "inline",
|
|
220
220
|
compactPlacement: "inline",
|
|
@@ -266,7 +266,7 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
|
|
|
266
266
|
id: "insert-actions",
|
|
267
267
|
surfaces: ["top-toolbar"],
|
|
268
268
|
group: "document",
|
|
269
|
-
presets: ["simple", "advanced"],
|
|
269
|
+
presets: ["simple", "advanced", "review"],
|
|
270
270
|
roles: EDITOR_ONLY,
|
|
271
271
|
fullPlacement: "inline",
|
|
272
272
|
compactPlacement: "overflow",
|
|
@@ -333,8 +333,8 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
|
|
|
333
333
|
surfaces: ["top-toolbar"],
|
|
334
334
|
group: "review",
|
|
335
335
|
presets: ["simple", "advanced", "review", "workflow"],
|
|
336
|
-
//
|
|
337
|
-
roles:
|
|
336
|
+
// Recording changes is review-authoring, not workflow/edit chrome.
|
|
337
|
+
roles: REVIEW_ONLY,
|
|
338
338
|
fullPlacement: "inline",
|
|
339
339
|
compactPlacement: "inline",
|
|
340
340
|
runtimeBehavior: "always",
|
|
@@ -18,26 +18,20 @@ import type { ToolbarChromeItemId } from "./chrome-registry";
|
|
|
18
18
|
/**
|
|
19
19
|
* Ordered role-action ids. Each array covers the *role-primary* actions
|
|
20
20
|
* only — the general left cluster (history, formatting, style selectors)
|
|
21
|
-
* and right cluster (
|
|
22
|
-
*
|
|
21
|
+
* and right cluster (workspace mode, zoom, health, export) stay in the
|
|
22
|
+
* base `TwToolbar` layout regardless of role.
|
|
23
23
|
*/
|
|
24
24
|
export const ROLE_ACTION_SETS: Record<
|
|
25
25
|
EditorRole,
|
|
26
26
|
ReadonlyArray<ToolbarChromeItemId>
|
|
27
27
|
> = {
|
|
28
|
-
editor: [
|
|
29
|
-
// Comment + inline tracked-changes toggle are the two review-layer
|
|
30
|
-
// actions relevant to authoring. They live in the role region rather
|
|
31
|
-
// than the right cluster so the right cluster stays view-focused.
|
|
32
|
-
"comment",
|
|
33
|
-
"tracked-changes-toggle",
|
|
34
|
-
],
|
|
28
|
+
editor: [],
|
|
35
29
|
review: [
|
|
36
30
|
// Optional sidebar panel shortcuts — visible only when the host provides
|
|
37
31
|
// hasSidebarPanelAccess (e.g. the harness). Hidden in base runtime.
|
|
38
32
|
"review-sidebar-tracked-changes",
|
|
39
33
|
"review-sidebar-comments",
|
|
40
|
-
// Inline review actions
|
|
34
|
+
// Inline review actions.
|
|
41
35
|
"comment",
|
|
42
36
|
"tracked-changes-toggle",
|
|
43
37
|
// Queue navigation + counts, collapsed from the old TwReviewQueueBar.
|
|
@@ -705,6 +705,17 @@ export function resolveSelectionWorkflowPosture(
|
|
|
705
705
|
return activeRange.from >= scopeFrom && activeRange.to <= scopeTo;
|
|
706
706
|
})
|
|
707
707
|
: null;
|
|
708
|
+
const activeWorkItem = workflowScopeSnapshot?.activeWorkItem;
|
|
709
|
+
const activeWorkItemHasScopes = Boolean(
|
|
710
|
+
activeWorkItem &&
|
|
711
|
+
workflowScopeSnapshot?.scopes.some((scope) => scope.workItemId === activeWorkItem.workItemId),
|
|
712
|
+
);
|
|
713
|
+
if (activeRange && activeWorkItem && activeWorkItemHasScopes && !matchingScope) {
|
|
714
|
+
return {
|
|
715
|
+
mode: "blocked",
|
|
716
|
+
disabledReason: `Unavailable here. "${activeWorkItem.title}" only applies inside its assigned scope.`,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
708
719
|
|
|
709
720
|
if (matchingScope?.mode === "suggest") {
|
|
710
721
|
return {
|
|
@@ -599,7 +599,7 @@ export const EDITOR_ACTION_REGISTRY: readonly EditorAction[] = [
|
|
|
599
599
|
mkImportant({
|
|
600
600
|
id: "find",
|
|
601
601
|
label: "Find…",
|
|
602
|
-
description: "
|
|
602
|
+
description: "Find text in this document.",
|
|
603
603
|
shortcut: ["Mod", "F"],
|
|
604
604
|
group: "misc",
|
|
605
605
|
targetKinds: [],
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
* children are whatever the mode owner decides to render inside:
|
|
15
15
|
* - review mode → review prev/next + accept/reject + markup selector
|
|
16
16
|
* - workflow mode → scope prev/next + claim/skip/complete
|
|
17
|
-
* - edit mode →
|
|
18
|
-
* formatting
|
|
17
|
+
* - edit mode → no band unless a future pass adds real contextual
|
|
18
|
+
* authoring content; toolbar owns direct formatting
|
|
19
19
|
* - more mode → diagnostics links + command search affordance
|
|
20
20
|
*
|
|
21
|
-
* Phase B.3 moves the band OUT of the toolbar so
|
|
22
|
-
*
|
|
21
|
+
* Phase B.3 moves the band OUT of the toolbar so it sits below the shell
|
|
22
|
+
* header and above the toolbar when it has meaningful content.
|
|
23
23
|
*
|
|
24
24
|
* Perf discipline (CLAUDE.md §Performance Invariants):
|
|
25
25
|
* - Pure presentational component; no DOM reads, no runtime calls.
|
|
@@ -66,9 +66,9 @@ const DEFAULT_EYEBROW: Record<EditorChromeMode, string> = {
|
|
|
66
66
|
/**
|
|
67
67
|
* Accent tint classes per mode. Review and workflow get a quiet
|
|
68
68
|
* `color.accent.soft` tint so the band visually differs from the
|
|
69
|
-
* neutral toolbar clusters flanking it; edit mode stays neutral
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
* neutral toolbar clusters flanking it; edit mode stays neutral for
|
|
70
|
+
* compatibility with the composition model even though the workspace does
|
|
71
|
+
* not mount an empty editor band.
|
|
72
72
|
*
|
|
73
73
|
* "more" mode stays neutral (`bg-chrome`) because the More posture
|
|
74
74
|
* opens diagnostics / compatibility drawers — the emphasis belongs on
|