@beyondwork/docx-react-component 1.0.88 → 1.0.90
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 +51 -0
- 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 +30 -5
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +2 -1
- 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 +20 -37
- package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +15 -27
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +24 -15
- package/src/ui-tailwind/tw-review-workspace.tsx +32 -18
- package/src/ui-tailwind/workflow-scope-layers.ts +70 -0
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
import React from "react";
|
|
12
12
|
import type { ScopeRailSegment, ScopeRailPosture } from "../../api/public-types";
|
|
13
|
+
import {
|
|
14
|
+
WORKFLOW_SCOPE_LAYER_FILTERS,
|
|
15
|
+
createDefaultWorkflowScopeLayerKeys,
|
|
16
|
+
isWorkflowScopePostureVisible,
|
|
17
|
+
toggleWorkflowScopeLayerKey,
|
|
18
|
+
type WorkflowScopeLayerKey,
|
|
19
|
+
} from "../workflow-scope-layers";
|
|
13
20
|
|
|
14
21
|
export interface TwWorkflowTabProps {
|
|
15
22
|
segments: readonly ScopeRailSegment[];
|
|
@@ -21,6 +28,8 @@ export interface TwWorkflowTabProps {
|
|
|
21
28
|
* matching overlay card. If omitted, focus sync is not wired.
|
|
22
29
|
*/
|
|
23
30
|
onActiveScopeChange?: (scopeId: string) => void;
|
|
31
|
+
enabledLayerFilters?: ReadonlySet<WorkflowScopeLayerKey>;
|
|
32
|
+
onEnabledLayerFiltersChange?: (filters: ReadonlySet<WorkflowScopeLayerKey>) => void;
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
const POSTURE_META: Record<
|
|
@@ -39,26 +48,13 @@ const POSTURE_META: Record<
|
|
|
39
48
|
const focusRingClass =
|
|
40
49
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
|
|
41
50
|
|
|
42
|
-
type ScopeFilterKey = "edit" | "suggest" | "comment" | "view" | "candidate" | "blocked";
|
|
43
|
-
|
|
44
|
-
const SCOPE_FILTERS: ReadonlyArray<{
|
|
45
|
-
key: ScopeFilterKey;
|
|
46
|
-
label: string;
|
|
47
|
-
postures: readonly ScopeRailPosture[];
|
|
48
|
-
}> = [
|
|
49
|
-
{ key: "edit", label: "Edit", postures: ["edit"] },
|
|
50
|
-
{ key: "suggest", label: "Suggest", postures: ["suggest"] },
|
|
51
|
-
{ key: "comment", label: "Comment", postures: ["comment"] },
|
|
52
|
-
{ key: "view", label: "Review", postures: ["view"] },
|
|
53
|
-
{ key: "candidate", label: "Scheduled", postures: ["candidate"] },
|
|
54
|
-
{ key: "blocked", label: "Blocked", postures: ["preserve-only", "blocked-import"] },
|
|
55
|
-
];
|
|
56
|
-
|
|
57
51
|
export const TwWorkflowTab: React.FC<TwWorkflowTabProps> = ({
|
|
58
52
|
segments,
|
|
59
53
|
activeScopeId,
|
|
60
54
|
onOpenScope,
|
|
61
55
|
onActiveScopeChange,
|
|
56
|
+
enabledLayerFilters,
|
|
57
|
+
onEnabledLayerFiltersChange,
|
|
62
58
|
}) => {
|
|
63
59
|
// Dedupe by scopeId so a scope spanning multiple pages shows once.
|
|
64
60
|
const uniqueSegments = React.useMemo(() => {
|
|
@@ -71,20 +67,31 @@ export const TwWorkflowTab: React.FC<TwWorkflowTabProps> = ({
|
|
|
71
67
|
return Array.from(byScopeId.values()).sort(compareWorkflowSegments(activeScopeId ?? null));
|
|
72
68
|
}, [activeScopeId, segments]);
|
|
73
69
|
const [query, setQuery] = React.useState("");
|
|
74
|
-
const [
|
|
75
|
-
|
|
70
|
+
const [uncontrolledEnabledFilters, setUncontrolledEnabledFilters] = React.useState<
|
|
71
|
+
ReadonlySet<WorkflowScopeLayerKey>
|
|
72
|
+
>(
|
|
73
|
+
createDefaultWorkflowScopeLayerKeys,
|
|
74
|
+
);
|
|
75
|
+
const activeEnabledFilters = enabledLayerFilters ?? uncontrolledEnabledFilters;
|
|
76
|
+
const setEnabledFilters = React.useCallback(
|
|
77
|
+
(next: ReadonlySet<WorkflowScopeLayerKey>) => {
|
|
78
|
+
if (!enabledLayerFilters) {
|
|
79
|
+
setUncontrolledEnabledFilters(next);
|
|
80
|
+
}
|
|
81
|
+
onEnabledLayerFiltersChange?.(next);
|
|
82
|
+
},
|
|
83
|
+
[enabledLayerFilters, onEnabledLayerFiltersChange],
|
|
76
84
|
);
|
|
77
85
|
const availableFilters = React.useMemo(() => {
|
|
78
86
|
const presentPostures = new Set(uniqueSegments.map((segment) => segment.posture));
|
|
79
|
-
return
|
|
87
|
+
return WORKFLOW_SCOPE_LAYER_FILTERS.filter((filter) =>
|
|
80
88
|
filter.postures.some((posture) => presentPostures.has(posture)),
|
|
81
89
|
);
|
|
82
90
|
}, [uniqueSegments]);
|
|
83
91
|
const visibleSegments = React.useMemo(() => {
|
|
84
92
|
const normalizedQuery = normalizeScopeQuery(query);
|
|
85
93
|
return uniqueSegments.filter((segment) => {
|
|
86
|
-
|
|
87
|
-
if (!enabledFilters.has(filterKey)) {
|
|
94
|
+
if (!isWorkflowScopePostureVisible(segment.posture, activeEnabledFilters)) {
|
|
88
95
|
return false;
|
|
89
96
|
}
|
|
90
97
|
if (!normalizedQuery) {
|
|
@@ -92,7 +99,7 @@ export const TwWorkflowTab: React.FC<TwWorkflowTabProps> = ({
|
|
|
92
99
|
}
|
|
93
100
|
return scopeSearchText(segment).includes(normalizedQuery);
|
|
94
101
|
});
|
|
95
|
-
}, [
|
|
102
|
+
}, [activeEnabledFilters, query, uniqueSegments]);
|
|
96
103
|
|
|
97
104
|
if (uniqueSegments.length === 0) {
|
|
98
105
|
return (
|
|
@@ -146,7 +153,7 @@ export const TwWorkflowTab: React.FC<TwWorkflowTabProps> = ({
|
|
|
146
153
|
role="group"
|
|
147
154
|
>
|
|
148
155
|
{availableFilters.map((filter) => {
|
|
149
|
-
const isEnabled =
|
|
156
|
+
const isEnabled = activeEnabledFilters.has(filter.key);
|
|
150
157
|
return (
|
|
151
158
|
<button
|
|
152
159
|
key={filter.key}
|
|
@@ -160,15 +167,9 @@ export const TwWorkflowTab: React.FC<TwWorkflowTabProps> = ({
|
|
|
160
167
|
].join(" ")}
|
|
161
168
|
data-testid={`workflow-scope-filter-${filter.key}`}
|
|
162
169
|
onClick={() => {
|
|
163
|
-
setEnabledFilters(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
next.delete(filter.key);
|
|
167
|
-
} else {
|
|
168
|
-
next.add(filter.key);
|
|
169
|
-
}
|
|
170
|
-
return next;
|
|
171
|
-
});
|
|
170
|
+
setEnabledFilters(
|
|
171
|
+
toggleWorkflowScopeLayerKey(activeEnabledFilters, filter.key),
|
|
172
|
+
);
|
|
172
173
|
}}
|
|
173
174
|
>
|
|
174
175
|
{filter.label}
|
|
@@ -245,13 +246,6 @@ function compareWorkflowSegments(activeScopeId: string | null) {
|
|
|
245
246
|
};
|
|
246
247
|
}
|
|
247
248
|
|
|
248
|
-
function filterKeyForPosture(posture: ScopeRailPosture): ScopeFilterKey {
|
|
249
|
-
if (posture === "preserve-only" || posture === "blocked-import") {
|
|
250
|
-
return "blocked";
|
|
251
|
-
}
|
|
252
|
-
return posture;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
249
|
function normalizeScopeQuery(value: string): string {
|
|
256
250
|
return value.trim().toLocaleLowerCase();
|
|
257
251
|
}
|
|
@@ -136,6 +136,8 @@ export interface TwReviewWorkspaceProps {
|
|
|
136
136
|
searchLabel?: string;
|
|
137
137
|
helpLabel?: string;
|
|
138
138
|
};
|
|
139
|
+
/** Opens the built-in inline find surface from More/Search and command palette. */
|
|
140
|
+
onOpenInlineFind?: () => void;
|
|
139
141
|
document: ReactNode;
|
|
140
142
|
workspaceMode: WorkspaceMode;
|
|
141
143
|
zoomLevel?: ZoomLevel;
|
|
@@ -475,19 +475,19 @@
|
|
|
475
475
|
}
|
|
476
476
|
|
|
477
477
|
.wre-scope-rail-tint-accent {
|
|
478
|
-
background: color-mix(in srgb, var(--color-accent)
|
|
478
|
+
background: color-mix(in srgb, var(--color-accent) 20%, transparent);
|
|
479
479
|
}
|
|
480
480
|
.wre-scope-rail-tint-warning {
|
|
481
|
-
background: color-mix(in srgb, var(--color-warning)
|
|
481
|
+
background: color-mix(in srgb, var(--color-warning) 23%, transparent);
|
|
482
482
|
}
|
|
483
483
|
.wre-scope-rail-tint-insert {
|
|
484
|
-
background: color-mix(in srgb, var(--color-insert)
|
|
484
|
+
background: color-mix(in srgb, var(--color-insert) 20%, transparent);
|
|
485
485
|
}
|
|
486
486
|
.wre-scope-rail-tint-secondary {
|
|
487
|
-
background: color-mix(in srgb, var(--color-secondary)
|
|
487
|
+
background: color-mix(in srgb, var(--color-secondary) 16%, transparent);
|
|
488
488
|
}
|
|
489
489
|
.wre-scope-rail-tint-danger {
|
|
490
|
-
background: color-mix(in srgb, var(--color-danger)
|
|
490
|
+
background: color-mix(in srgb, var(--color-danger) 24%, transparent);
|
|
491
491
|
}
|
|
492
492
|
|
|
493
493
|
/* §3.7 canonical scope families */
|
|
@@ -543,22 +543,22 @@
|
|
|
543
543
|
/*
|
|
544
544
|
* ─── Scope rail stripe ───
|
|
545
545
|
*
|
|
546
|
-
* The rail stripe is the rest-state representation of a scope: a
|
|
546
|
+
* The rail stripe is the rest-state representation of a scope: a 6px
|
|
547
547
|
* color stripe in the gutter lane. Posture color comes from the
|
|
548
548
|
* accent/warning/insert/secondary/danger tokens. Hover widens the
|
|
549
549
|
* stripe via transform (zero layout cost) and reveals the label pill.
|
|
550
550
|
*/
|
|
551
551
|
.wre-scope-rail-stripe {
|
|
552
552
|
position: absolute;
|
|
553
|
-
width:
|
|
554
|
-
border-radius:
|
|
553
|
+
width: 6px;
|
|
554
|
+
border-radius: 999px;
|
|
555
555
|
background: currentColor;
|
|
556
556
|
pointer-events: auto;
|
|
557
557
|
cursor: pointer;
|
|
558
558
|
z-index: 1;
|
|
559
559
|
transform-origin: left center;
|
|
560
560
|
transition: transform 120ms ease-out, opacity 120ms ease-out;
|
|
561
|
-
opacity: 0.
|
|
561
|
+
opacity: 0.9;
|
|
562
562
|
/* Reset button defaults. */
|
|
563
563
|
border: none;
|
|
564
564
|
padding: 0;
|
|
@@ -568,16 +568,22 @@
|
|
|
568
568
|
background-clip: padding-box;
|
|
569
569
|
}
|
|
570
570
|
|
|
571
|
+
.wre-scope-rail-stripe::before {
|
|
572
|
+
content: "";
|
|
573
|
+
position: absolute;
|
|
574
|
+
inset: -5px -10px;
|
|
575
|
+
}
|
|
576
|
+
|
|
571
577
|
.wre-scope-rail-stripe:hover,
|
|
572
578
|
.wre-scope-rail-stripe:focus-visible {
|
|
573
|
-
transform: scaleX(1.
|
|
579
|
+
transform: scaleX(1.45);
|
|
574
580
|
opacity: 1;
|
|
575
581
|
outline: none;
|
|
576
582
|
}
|
|
577
583
|
|
|
578
584
|
.wre-scope-rail-stripe-active {
|
|
579
585
|
opacity: 1;
|
|
580
|
-
transform: scaleX(1.
|
|
586
|
+
transform: scaleX(1.6);
|
|
581
587
|
}
|
|
582
588
|
|
|
583
589
|
.wre-scope-rail-stripe.wre-scope-rail-label-accent { color: var(--color-accent); }
|
|
@@ -623,6 +629,8 @@
|
|
|
623
629
|
pointer-events: none;
|
|
624
630
|
transition: opacity 140ms ease-out, transform 140ms ease-out;
|
|
625
631
|
transform: translateX(-4px);
|
|
632
|
+
margin: 0;
|
|
633
|
+
font-family: inherit;
|
|
626
634
|
}
|
|
627
635
|
|
|
628
636
|
.wre-scope-rail-stripe:hover + .wre-scope-rail-label,
|
|
@@ -692,7 +700,12 @@
|
|
|
692
700
|
}
|
|
693
701
|
|
|
694
702
|
.wre-scope-rail-label-active {
|
|
695
|
-
|
|
703
|
+
opacity: 1;
|
|
704
|
+
pointer-events: auto;
|
|
705
|
+
transform: translateX(0);
|
|
706
|
+
box-shadow:
|
|
707
|
+
0 0 0 1px color-mix(in srgb, currentColor 42%, transparent),
|
|
708
|
+
0 8px 22px color-mix(in srgb, currentColor 14%, transparent);
|
|
696
709
|
}
|
|
697
710
|
|
|
698
711
|
.wre-scope-rail-icon {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* Review-role actions here collapse what used to live in
|
|
10
10
|
* `TwReviewQueueBar` as a second strip — the review prev/next, counts,
|
|
11
11
|
* active-item label, accept/reject, markup-mode, and batch operations.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Workflow-role actions surface the scope posture menu plus work-item
|
|
13
|
+
* traversal + claim/skip/complete.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import React, { useState } from "react";
|
|
@@ -18,19 +18,17 @@ import * as Popover from "@radix-ui/react-popover";
|
|
|
18
18
|
import * as Toggle from "@radix-ui/react-toggle";
|
|
19
19
|
import * as Tooltip from "@radix-ui/react-tooltip";
|
|
20
20
|
import {
|
|
21
|
-
BookmarkCheck,
|
|
22
21
|
Check,
|
|
23
22
|
CheckCheck,
|
|
24
|
-
ChevronDown,
|
|
25
23
|
ChevronLeft,
|
|
26
24
|
ChevronRight,
|
|
27
25
|
CircleOff,
|
|
28
26
|
FileDiff,
|
|
29
|
-
Flag,
|
|
30
27
|
Hand,
|
|
31
28
|
MessageSquare,
|
|
32
29
|
MessageSquareDot,
|
|
33
30
|
MessageSquareText,
|
|
31
|
+
MoreHorizontal,
|
|
34
32
|
Rows3,
|
|
35
33
|
SkipForward,
|
|
36
34
|
Target,
|
|
@@ -48,6 +46,7 @@ import type { ScopedChromePolicy } from "../../ui/headless/scoped-chrome-policy"
|
|
|
48
46
|
import type { ToolbarChromeItemId } from "../../ui/headless/chrome-registry";
|
|
49
47
|
import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
|
|
50
48
|
import { ROLE_ACTION_SETS } from "../../ui/headless/role-action-sets";
|
|
49
|
+
import { TwDisplayModeSelector } from "../chrome/tw-display-mode-selector";
|
|
51
50
|
import { TwScopePostureMenu } from "./tw-scope-posture-menu";
|
|
52
51
|
|
|
53
52
|
/**
|
|
@@ -103,7 +102,7 @@ export interface TwRoleActionRegionProps {
|
|
|
103
102
|
onReviewSidebarTrackedChanges?: () => void;
|
|
104
103
|
onReviewSidebarComments?: () => void;
|
|
105
104
|
|
|
106
|
-
// Workflow
|
|
105
|
+
// Workflow role: assign authorable scope posture.
|
|
107
106
|
onMarkScopePosture?: (posture: ScopeRailPosture) => void;
|
|
108
107
|
|
|
109
108
|
// Review role
|
|
@@ -185,6 +184,15 @@ function isRoleActionRenderable(
|
|
|
185
184
|
case "review-accept-all":
|
|
186
185
|
case "review-reject-all":
|
|
187
186
|
return reviewQueueTotal > 0;
|
|
187
|
+
case "workflow-prev":
|
|
188
|
+
case "workflow-next":
|
|
189
|
+
case "workflow-mark-complete":
|
|
190
|
+
case "workflow-claim":
|
|
191
|
+
case "workflow-skip":
|
|
192
|
+
case "workflow-mark-blocked":
|
|
193
|
+
return props.workflowItem !== undefined;
|
|
194
|
+
case "workflow-jump-to-scope":
|
|
195
|
+
return props.workflowItem !== undefined || props.onWorkflowJumpToScope !== undefined;
|
|
188
196
|
default:
|
|
189
197
|
return true;
|
|
190
198
|
}
|
|
@@ -357,10 +365,11 @@ function RoleActionButton(arg: RoleActionButtonProps): React.JSX.Element | null
|
|
|
357
365
|
);
|
|
358
366
|
case "review-markup-mode":
|
|
359
367
|
return (
|
|
360
|
-
<
|
|
361
|
-
|
|
368
|
+
<TwDisplayModeSelector
|
|
369
|
+
value={props.markupDisplay ?? "simple"}
|
|
362
370
|
onChange={(mode) => props.onReviewMarkupMode?.(mode)}
|
|
363
371
|
disabled={!props.onReviewMarkupMode}
|
|
372
|
+
data-testid="role-review-markup-mode"
|
|
364
373
|
/>
|
|
365
374
|
);
|
|
366
375
|
case "workflow-prev":
|
|
@@ -433,11 +442,11 @@ function RoleActionOverflow({
|
|
|
433
442
|
aria-label="More role actions"
|
|
434
443
|
aria-expanded={open}
|
|
435
444
|
onMouseDown={preserveEditorSelectionMouseDown}
|
|
436
|
-
|
|
445
|
+
title="More role actions"
|
|
446
|
+
className="inline-flex h-6 w-6 items-center justify-center rounded-md border border-border bg-canvas text-primary transition-colors hover:bg-surface outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas"
|
|
437
447
|
data-testid="role-action-overflow-trigger"
|
|
438
448
|
>
|
|
439
|
-
|
|
440
|
-
<ChevronDown className="h-3.5 w-3.5 text-tertiary" />
|
|
449
|
+
<MoreHorizontal className="h-3.5 w-3.5 text-tertiary" aria-hidden="true" />
|
|
441
450
|
</button>
|
|
442
451
|
</Popover.Trigger>
|
|
443
452
|
<Popover.Portal>
|
|
@@ -684,30 +693,4 @@ function ReviewActiveLabel({
|
|
|
684
693
|
);
|
|
685
694
|
}
|
|
686
695
|
|
|
687
|
-
function MarkupModeSelect(arg: {
|
|
688
|
-
mode: MarkupDisplayMode;
|
|
689
|
-
onChange: (mode: MarkupDisplayMode) => void;
|
|
690
|
-
disabled?: boolean;
|
|
691
|
-
}): React.JSX.Element {
|
|
692
|
-
const Icon = arg.mode === "clean" ? BookmarkCheck : arg.mode === "all" ? Flag : Rows3;
|
|
693
|
-
return (
|
|
694
|
-
<button
|
|
695
|
-
type="button"
|
|
696
|
-
aria-label={`Markup display: ${arg.mode}`}
|
|
697
|
-
disabled={arg.disabled}
|
|
698
|
-
onMouseDown={preserveEditorSelectionMouseDown}
|
|
699
|
-
onClick={() => {
|
|
700
|
-
const next: MarkupDisplayMode =
|
|
701
|
-
arg.mode === "all" ? "clean" : arg.mode === "clean" ? "simple" : "all";
|
|
702
|
-
arg.onChange(next);
|
|
703
|
-
}}
|
|
704
|
-
data-testid="role-review-markup-mode"
|
|
705
|
-
className="inline-flex h-6 items-center gap-1 rounded-md border border-border bg-canvas px-2 text-[11px] font-medium text-secondary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
|
|
706
|
-
>
|
|
707
|
-
<Icon className="h-3.5 w-3.5" />
|
|
708
|
-
<span className="capitalize">{arg.mode}</span>
|
|
709
|
-
</button>
|
|
710
|
-
);
|
|
711
|
-
}
|
|
712
|
-
|
|
713
696
|
export default TwRoleActionRegion;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Scope posture menu — replaces the old "Mark section" button with a
|
|
3
|
-
* topnav dropdown listing the
|
|
4
|
-
*
|
|
5
|
-
* single "marked" flag.
|
|
3
|
+
* topnav dropdown listing the authorable `ScopeRailPosture` values so
|
|
4
|
+
* workflow operators can mark regions with an explicit workflow mode
|
|
5
|
+
* instead of a single "marked" flag.
|
|
6
6
|
*
|
|
7
7
|
* Per runtime-rendering-and-chrome-phase.md §6.4, the menu lives inline
|
|
8
|
-
* in the
|
|
8
|
+
* in the workflow role's primary action region (not in the review queue
|
|
9
9
|
* strip). Postures align 1:1 with the rail vocabulary so the rail
|
|
10
|
-
* updates visually as soon as the user picks one.
|
|
10
|
+
* updates visually as soon as the user picks one. Runtime-only postures
|
|
11
|
+
* like preserve-only and blocked-import still render in overlays, but
|
|
12
|
+
* they are not choices users can assign from this product menu.
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
import React, { useState } from "react";
|
|
@@ -17,7 +19,6 @@ import {
|
|
|
17
19
|
ChevronDown,
|
|
18
20
|
Eye,
|
|
19
21
|
Flag,
|
|
20
|
-
Lock,
|
|
21
22
|
MessageCircle,
|
|
22
23
|
Pencil,
|
|
23
24
|
Sparkles,
|
|
@@ -35,12 +36,12 @@ export interface TwScopePostureMenuProps {
|
|
|
35
36
|
"data-testid"?: string;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
interface
|
|
39
|
+
export interface ScopePostureMenuEntry {
|
|
39
40
|
posture: ScopeRailPosture;
|
|
40
41
|
label: string;
|
|
41
42
|
hint: string;
|
|
42
43
|
icon: React.ComponentType<{ className?: string }>;
|
|
43
|
-
tone: "accent" | "warning" | "comment" | "secondary"
|
|
44
|
+
tone: "accent" | "warning" | "comment" | "secondary";
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/**
|
|
@@ -49,7 +50,7 @@ interface PostureEntry {
|
|
|
49
50
|
* glyphs via the `data-icon` attribute). Extract both into a single
|
|
50
51
|
* source of truth in a follow-up.
|
|
51
52
|
*/
|
|
52
|
-
const
|
|
53
|
+
export const SCOPE_POSTURE_MENU_ENTRIES: readonly ScopePostureMenuEntry[] = [
|
|
53
54
|
{
|
|
54
55
|
posture: "edit",
|
|
55
56
|
label: "Edit scope",
|
|
@@ -85,22 +86,11 @@ const POSTURE_ENTRIES: readonly PostureEntry[] = [
|
|
|
85
86
|
icon: Flag,
|
|
86
87
|
tone: "warning",
|
|
87
88
|
},
|
|
88
|
-
{
|
|
89
|
-
posture: "preserve-only",
|
|
90
|
-
label: "Preserve only",
|
|
91
|
-
hint: "Blocked — export-preserving only",
|
|
92
|
-
icon: Lock,
|
|
93
|
-
tone: "danger",
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
posture: "blocked-import",
|
|
97
|
-
label: "Blocked import",
|
|
98
|
-
hint: "Blocked — imported region is locked",
|
|
99
|
-
icon: Lock,
|
|
100
|
-
tone: "danger",
|
|
101
|
-
},
|
|
102
89
|
];
|
|
103
90
|
|
|
91
|
+
export const SCOPE_POSTURE_MENU_POSTURES: readonly ScopeRailPosture[] =
|
|
92
|
+
SCOPE_POSTURE_MENU_ENTRIES.map((entry) => entry.posture);
|
|
93
|
+
|
|
104
94
|
export function TwScopePostureMenu(props: TwScopePostureMenuProps): React.JSX.Element {
|
|
105
95
|
const [open, setOpen] = useState(false);
|
|
106
96
|
|
|
@@ -131,7 +121,7 @@ export function TwScopePostureMenu(props: TwScopePostureMenuProps): React.JSX.El
|
|
|
131
121
|
<div className="mb-1 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
132
122
|
Mark section with posture
|
|
133
123
|
</div>
|
|
134
|
-
{
|
|
124
|
+
{SCOPE_POSTURE_MENU_ENTRIES.map((entry) => (
|
|
135
125
|
<Popover.Close key={entry.posture} asChild>
|
|
136
126
|
<button
|
|
137
127
|
type="button"
|
|
@@ -163,7 +153,7 @@ export function TwScopePostureMenu(props: TwScopePostureMenuProps): React.JSX.El
|
|
|
163
153
|
);
|
|
164
154
|
}
|
|
165
155
|
|
|
166
|
-
function toneClass(tone:
|
|
156
|
+
function toneClass(tone: ScopePostureMenuEntry["tone"]): string {
|
|
167
157
|
switch (tone) {
|
|
168
158
|
case "accent":
|
|
169
159
|
return "text-accent";
|
|
@@ -171,8 +161,6 @@ function toneClass(tone: PostureEntry["tone"]): string {
|
|
|
171
161
|
return "text-warning";
|
|
172
162
|
case "comment":
|
|
173
163
|
return "text-comment";
|
|
174
|
-
case "danger":
|
|
175
|
-
return "text-danger";
|
|
176
164
|
case "secondary":
|
|
177
165
|
default:
|
|
178
166
|
return "text-secondary";
|
|
@@ -69,8 +69,8 @@ import {
|
|
|
69
69
|
type ScopedChromePolicy,
|
|
70
70
|
} from "../../ui/headless/scoped-chrome-policy";
|
|
71
71
|
import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
|
|
72
|
+
import { TwDisplayModeSelector } from "../chrome/tw-display-mode-selector";
|
|
72
73
|
import { type MarkupDisplayMode } from "./tw-role-action-region";
|
|
73
|
-
import { TwDetachHandle } from "../chrome/tw-detach-handle";
|
|
74
74
|
import { TwToolbarIconButton } from "./tw-toolbar-icon-button";
|
|
75
75
|
|
|
76
76
|
export interface TwToolbarProps {
|
|
@@ -124,6 +124,8 @@ export interface TwToolbarProps {
|
|
|
124
124
|
onToggleSidebar?: () => void;
|
|
125
125
|
onZoomChange?: (level: ZoomLevel) => void;
|
|
126
126
|
onShowTrackedChangesChange: (show: boolean) => void;
|
|
127
|
+
/** Top-toolbar fallback for changing redline/comment display when review context band is not active. */
|
|
128
|
+
onMarkupDisplayChange?: (mode: MarkupDisplayMode) => void;
|
|
127
129
|
onRestartNumbering?: () => void;
|
|
128
130
|
onContinueNumbering?: () => void;
|
|
129
131
|
onUpdateFields?: () => void;
|
|
@@ -155,9 +157,9 @@ export interface TwToolbarProps {
|
|
|
155
157
|
onReviewNext?: () => void;
|
|
156
158
|
onReviewAccept?: () => void;
|
|
157
159
|
onReviewReject?: () => void;
|
|
158
|
-
/** Current chrome pin state
|
|
160
|
+
/** Current chrome pin state, retained for host compatibility. */
|
|
159
161
|
chromePins?: ChromePinsState;
|
|
160
|
-
/** Called when
|
|
162
|
+
/** Called when a host-supported chrome surface changes placement. */
|
|
161
163
|
onChromePinChange?: (surface: ChromePinSurface, pin: PinState | null) => void;
|
|
162
164
|
|
|
163
165
|
/**
|
|
@@ -255,13 +257,18 @@ export function TwToolbar(props: TwToolbarProps) {
|
|
|
255
257
|
});
|
|
256
258
|
const showStyleSelectors = isToolbarChromeItemVisible(scopedChromePolicy, "text-style-selectors");
|
|
257
259
|
const showInlineFormatting = isToolbarChromeItemVisible(scopedChromePolicy, "inline-formatting");
|
|
258
|
-
const showAdvancedFormatting =
|
|
260
|
+
const showAdvancedFormatting =
|
|
261
|
+
showInlineFormatting && (preset === "advanced" || props.role === "editor");
|
|
259
262
|
const showTextColors = isToolbarChromeItemVisible(scopedChromePolicy, "text-colors");
|
|
260
263
|
const showParagraphAlignment = isToolbarChromeItemVisible(scopedChromePolicy, "paragraph-alignment");
|
|
261
264
|
const showInsertMenu = isToolbarChromeItemVisible(scopedChromePolicy, "insert-actions");
|
|
262
265
|
const showTrackedChangesToggle =
|
|
263
266
|
isToolbarChromeItemVisible(scopedChromePolicy, "tracked-changes-toggle") &&
|
|
264
267
|
!isChromeItemOwnedByRoleRegion("tracked-changes-toggle", props.role);
|
|
268
|
+
const showMarkupDisplaySelector =
|
|
269
|
+
props.markupDisplay !== undefined &&
|
|
270
|
+
props.onMarkupDisplayChange !== undefined &&
|
|
271
|
+
!isChromeItemOwnedByRoleRegion("review-markup-mode", props.role);
|
|
265
272
|
const showRightClusterComment =
|
|
266
273
|
isToolbarChromeItemVisible(scopedChromePolicy, "comment") &&
|
|
267
274
|
!isChromeItemOwnedByRoleRegion("comment", props.role);
|
|
@@ -695,6 +702,17 @@ export function TwToolbar(props: TwToolbarProps) {
|
|
|
695
702
|
</>
|
|
696
703
|
) : null}
|
|
697
704
|
|
|
705
|
+
{showMarkupDisplaySelector ? (
|
|
706
|
+
<>
|
|
707
|
+
<TwDisplayModeSelector
|
|
708
|
+
value={props.markupDisplay!}
|
|
709
|
+
onChange={(mode) => props.onMarkupDisplayChange?.(mode)}
|
|
710
|
+
data-testid="toolbar-display-mode-selector"
|
|
711
|
+
/>
|
|
712
|
+
<div className="mx-1 h-4 w-px bg-border" />
|
|
713
|
+
</>
|
|
714
|
+
) : null}
|
|
715
|
+
|
|
698
716
|
{/* View mode toggle group: Canvas (clean, flowing) / Page (layout-sensitive) */}
|
|
699
717
|
{isToolbarChromeItemVisible(scopedChromePolicy, "workspace-mode") ? (
|
|
700
718
|
<ToggleGroup.Root
|
|
@@ -891,14 +909,6 @@ export function TwToolbar(props: TwToolbarProps) {
|
|
|
891
909
|
/>
|
|
892
910
|
) : null}
|
|
893
911
|
|
|
894
|
-
{props.onChromePinChange ? (
|
|
895
|
-
<TwDetachHandle
|
|
896
|
-
surface="topnav"
|
|
897
|
-
pin={props.chromePins?.topnav}
|
|
898
|
-
onChange={props.onChromePinChange}
|
|
899
|
-
label="Detach toolbar"
|
|
900
|
-
/>
|
|
901
|
-
) : null}
|
|
902
912
|
</div>
|
|
903
913
|
</header>
|
|
904
914
|
);
|
|
@@ -1158,10 +1168,9 @@ function ToolbarCompactOverflow(props: {
|
|
|
1158
1168
|
aria-expanded={open}
|
|
1159
1169
|
onMouseDown={preserveEditorSelectionMouseDown}
|
|
1160
1170
|
onClick={() => setOpen((value) => !value)}
|
|
1161
|
-
className={`inline-flex h-6 items-center
|
|
1171
|
+
className={`inline-flex h-6 w-6 items-center justify-center rounded-md border border-border bg-canvas text-primary transition-colors hover:bg-surface outline-none ${focusRingClass}`}
|
|
1162
1172
|
>
|
|
1163
|
-
|
|
1164
|
-
<ChevronDown className="h-3.5 w-3.5 text-tertiary" />
|
|
1173
|
+
<MoreHorizontal className="h-3.5 w-3.5" />
|
|
1165
1174
|
</button>
|
|
1166
1175
|
</Tooltip.Trigger>
|
|
1167
1176
|
<Tooltip.Portal>
|