@beyondwork/docx-react-component 1.0.57 → 1.0.59
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/README.md +1 -1
- package/package.json +2 -1
- package/src/api/awareness-identity-types.ts +4 -2
- package/src/api/comment-negotiation-types.ts +4 -1
- package/src/api/external-custody-types.ts +16 -0
- package/src/api/internal/build-ref-projections.ts +108 -0
- package/src/api/package-version.ts +1 -1
- package/src/api/participants-types.ts +11 -1
- package/src/api/public-types.ts +1149 -8
- package/src/api/scope-metadata-resolver-types.ts +6 -0
- package/src/compare/diff-engine.ts +3 -0
- package/src/core/commands/formatting-commands.ts +1 -0
- package/src/core/commands/index.ts +225 -16
- package/src/core/commands/legacy-form-field-commands.ts +181 -0
- package/src/core/commands/table-structure-commands.ts +149 -31
- package/src/core/selection/mapping.ts +20 -0
- package/src/core/state/editor-state.ts +2 -1
- package/src/index.ts +28 -0
- package/src/io/docx-session.ts +22 -3
- package/src/io/export/export-session.ts +11 -7
- package/src/io/export/ooxml-namespaces.ts +47 -0
- package/src/io/export/reattach-preserved-parts.ts +4 -16
- package/src/io/export/serialize-comments.ts +3 -131
- package/src/io/export/serialize-ffdata.ts +89 -0
- package/src/io/export/serialize-headers-footers.ts +5 -0
- package/src/io/export/serialize-main-document.ts +224 -34
- package/src/io/export/serialize-numbering.ts +22 -2
- package/src/io/export/serialize-revisions.ts +99 -0
- package/src/io/export/serialize-tables.ts +9 -0
- package/src/io/export/split-review-boundaries.ts +1 -0
- package/src/io/export/table-properties-xml.ts +14 -0
- package/src/io/load-scheduler.ts +70 -28
- package/src/io/normalize/normalize-text.ts +13 -0
- package/src/io/ooxml/_mini-xml.ts +198 -0
- package/src/io/ooxml/canonicalize-payload.ts +1 -4
- package/src/io/ooxml/chart/chart-style-table.ts +4 -3
- package/src/io/ooxml/chart/parse-chart-space.ts +2 -4
- package/src/io/ooxml/chart/parse-series.ts +2 -1
- package/src/io/ooxml/chart/resolve-color.ts +2 -2
- package/src/io/ooxml/chart/types.ts +6 -434
- package/src/io/ooxml/comment-presentation-payload.ts +6 -5
- package/src/io/ooxml/highlight-colors.ts +8 -5
- package/src/io/ooxml/parse-anchor.ts +68 -53
- package/src/io/ooxml/parse-comments.ts +14 -142
- package/src/io/ooxml/parse-complex-content.ts +3 -106
- package/src/io/ooxml/parse-drawing.ts +100 -195
- package/src/io/ooxml/parse-ffdata.ts +93 -0
- package/src/io/ooxml/parse-fields.ts +7 -146
- package/src/io/ooxml/parse-fill.ts +88 -8
- package/src/io/ooxml/parse-font-table.ts +5 -105
- package/src/io/ooxml/parse-footnotes.ts +28 -152
- package/src/io/ooxml/parse-headers-footers.ts +106 -212
- package/src/io/ooxml/parse-inline-media.ts +3 -200
- package/src/io/ooxml/parse-main-document.ts +180 -217
- package/src/io/ooxml/parse-numbering.ts +154 -335
- package/src/io/ooxml/parse-object.ts +147 -0
- package/src/io/ooxml/parse-ole-relationship.ts +82 -0
- package/src/io/ooxml/parse-paragraph-formatting.ts +7 -10
- package/src/io/ooxml/parse-picture-sdt.ts +85 -0
- package/src/io/ooxml/parse-picture.ts +120 -39
- package/src/io/ooxml/parse-revisions.ts +285 -51
- package/src/io/ooxml/parse-settings.ts +6 -99
- package/src/io/ooxml/parse-shapes.ts +25 -140
- package/src/io/ooxml/parse-styles.ts +3 -218
- package/src/io/ooxml/parse-tables.ts +76 -256
- package/src/io/ooxml/parse-theme.ts +1 -4
- package/src/io/ooxml/property-grab-bag.ts +5 -47
- package/src/io/ooxml/xml-element-serialize.ts +32 -0
- package/src/io/ooxml/xml-parser.ts +183 -0
- package/src/legal/bookmarks.ts +1 -1
- package/src/legal/cross-references.ts +1 -1
- package/src/legal/defined-terms.ts +1 -1
- package/src/legal/{_document-root.ts → document-root.ts} +8 -0
- package/src/legal/signature-blocks.ts +1 -1
- package/src/model/canonical-document.ts +165 -6
- package/src/model/chart-types.ts +439 -0
- package/src/model/snapshot.ts +3 -1
- package/src/review/store/comment-remapping.ts +24 -11
- package/src/review/store/revision-actions.ts +482 -2
- package/src/review/store/revision-store.ts +15 -0
- package/src/review/store/revision-types.ts +76 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +24 -0
- package/src/runtime/collab/runtime-collab-sync.ts +33 -0
- package/src/runtime/diagnostics/build-diagnostic.ts +151 -0
- package/src/runtime/diagnostics/code-metadata-table.ts +221 -0
- package/src/runtime/document-runtime.ts +544 -35
- package/src/runtime/document-search.ts +176 -0
- package/src/runtime/edit-ops/index.ts +18 -2
- package/src/runtime/footnote-resolver.ts +130 -0
- package/src/runtime/layout/layout-engine-instance.ts +31 -4
- package/src/runtime/layout/layout-engine-version.ts +37 -1
- package/src/runtime/layout/page-graph.ts +14 -1
- package/src/runtime/layout/resolved-formatting-state.ts +21 -0
- package/src/runtime/numbering-prefix.ts +17 -0
- package/src/runtime/query-scopes.ts +183 -0
- package/src/runtime/resolved-numbering-geometry.ts +37 -6
- package/src/runtime/revision-runtime.ts +27 -1
- package/src/runtime/scope-resolver.ts +60 -0
- package/src/runtime/selection/post-edit-validator.ts +60 -6
- package/src/runtime/structure-ops/index.ts +20 -4
- package/src/runtime/surface-projection.ts +293 -18
- package/src/runtime/table-schema.ts +6 -0
- package/src/runtime/theme-color-resolver.ts +2 -2
- package/src/runtime/units.ts +9 -0
- package/src/runtime/workflow-rail-segments.ts +4 -0
- package/src/ui/WordReviewEditor.tsx +258 -44
- package/src/ui/editor-runtime-boundary.ts +13 -0
- package/src/ui/editor-shell-view.tsx +4 -1
- package/src/ui/headless/chrome-registry.ts +53 -0
- package/src/ui/headless/selection-tool-resolver.ts +11 -1
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +13 -0
- package/src/ui-tailwind/chrome/tw-command-palette-mount.tsx +96 -0
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +2 -1
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +5 -4
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +6 -2
- package/src/ui-tailwind/chrome/use-container-breakpoint.ts +111 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +23 -9
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +158 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +6 -7
- package/src/ui-tailwind/editor-surface/pm-schema.ts +105 -17
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +13 -0
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +76 -14
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +18 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +2 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +18 -2
- package/src/ui-tailwind/index.ts +9 -0
- package/src/ui-tailwind/page-chrome-model.ts +77 -5
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +56 -1
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +2 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +116 -113
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +2 -2
- package/src/ui-tailwind/theme/tokens.ts +14 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +5 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +52 -87
- package/src/validation/diagnostics.ts +1 -0
|
@@ -44,6 +44,7 @@ import type {
|
|
|
44
44
|
SectionLayoutPatch,
|
|
45
45
|
SectionPageNumberingPatch,
|
|
46
46
|
SearchOptions,
|
|
47
|
+
SetSelectionOptions,
|
|
47
48
|
SearchResultSnapshot,
|
|
48
49
|
SelectionSnapshot as PublicSelectionSnapshot,
|
|
49
50
|
SuggestionEntrySnapshot,
|
|
@@ -76,6 +77,7 @@ import type {
|
|
|
76
77
|
ZoomLevel,
|
|
77
78
|
} from "../api/public-types";
|
|
78
79
|
import { MetadataResolverMissingError } from "../api/public-types";
|
|
80
|
+
import { buildRefProjections } from "../api/internal/build-ref-projections.ts";
|
|
79
81
|
import { readHarnessDebugPortsFlag } from "../internal/harness-debug-ports.ts";
|
|
80
82
|
import type { ScopeMetadataResolver } from "../api/scope-metadata-resolver-types.ts";
|
|
81
83
|
import {
|
|
@@ -151,7 +153,7 @@ import {
|
|
|
151
153
|
} from "../io/source-package-provenance.ts";
|
|
152
154
|
import { readOpcPackage } from "../io/opc/package-reader.ts";
|
|
153
155
|
import { deriveCapabilities } from "../runtime/session-capabilities";
|
|
154
|
-
import { searchDocument } from "../runtime/document-search.ts";
|
|
156
|
+
import { findTextMatches, searchDocument } from "../runtime/document-search.ts";
|
|
155
157
|
import {
|
|
156
158
|
resolveCurrentContextAnalyticsQuery,
|
|
157
159
|
runtimeContextAnalyticsSnapshotsEqual,
|
|
@@ -189,6 +191,7 @@ import type {
|
|
|
189
191
|
SuggestionCardModel,
|
|
190
192
|
} from "./headless/selection-toolbar-model";
|
|
191
193
|
import { resolveActiveSelectionTool } from "./headless/selection-tool-resolver";
|
|
194
|
+
import { resolveSelectionToolRegistry } from "./headless/chrome-registry";
|
|
192
195
|
import { type EditorCommandBag, useCommandBag } from "./editor-command-bag.ts";
|
|
193
196
|
import {
|
|
194
197
|
resolveHeadingShortcutStyleId,
|
|
@@ -566,7 +569,16 @@ export function __createWordReviewEditorRefBridge(
|
|
|
566
569
|
// Pending-conflict queue for this bridge instance. Keyed by conflictKey(...).
|
|
567
570
|
const pendingConflicts = new Map<string, PendingConflict>();
|
|
568
571
|
|
|
569
|
-
|
|
572
|
+
// v2.0.0 Track D — purpose-grouped projections dispatch via this holder
|
|
573
|
+
// so they stay in lockstep with the ref even if a rebuild is triggered.
|
|
574
|
+
const refHolder: { current: WordReviewEditorRef | null } = { current: null };
|
|
575
|
+
const projections = buildRefProjections(() => {
|
|
576
|
+
const r = refHolder.current;
|
|
577
|
+
if (!r) throw new Error("ref projection used before ref initialization");
|
|
578
|
+
return r;
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
const refValue: WordReviewEditorRef = {
|
|
570
582
|
focus: () => runtime.focus(),
|
|
571
583
|
blur: () => runtime.blur(),
|
|
572
584
|
undo: () => runtime.undo(),
|
|
@@ -595,6 +607,12 @@ export function __createWordReviewEditorRefBridge(
|
|
|
595
607
|
addScope: (params) => runtime.addScope(params),
|
|
596
608
|
getScope: (scopeId) => runtime.getScope(scopeId),
|
|
597
609
|
removeScope: (scopeId) => runtime.removeScope(scopeId),
|
|
610
|
+
addInvisibleScope: (params) => runtime.addInvisibleScope(params),
|
|
611
|
+
setScopeVisibility: (scopeId, visibility) => runtime.setScopeVisibility(scopeId, visibility),
|
|
612
|
+
getScopeVisibility: (scopeId) => runtime.getScopeVisibility(scopeId),
|
|
613
|
+
setScopeChromeVisibility: (state) => runtime.setScopeChromeVisibility(state),
|
|
614
|
+
getScopeChromeVisibility: () => runtime.getScopeChromeVisibility(),
|
|
615
|
+
subscribeToScopeQuery: (filter, callback) => runtime.subscribeToScopeQuery(filter, callback),
|
|
598
616
|
acceptChange: (changeId) => runtime.acceptChange(changeId),
|
|
599
617
|
rejectChange: (changeId) => runtime.rejectChange(changeId),
|
|
600
618
|
acceptAllChanges: () => runtime.acceptAllChanges(),
|
|
@@ -760,7 +778,7 @@ export function __createWordReviewEditorRefBridge(
|
|
|
760
778
|
clearSearch: () => {
|
|
761
779
|
mountedSurface?.clearSearch();
|
|
762
780
|
},
|
|
763
|
-
setSelection: (selection) => {
|
|
781
|
+
setSelection: (selection, _options?: SetSelectionOptions) => {
|
|
764
782
|
applyRuntimeSelection(
|
|
765
783
|
runtime,
|
|
766
784
|
normalizeRequestedSelection(runtime.getRenderSnapshot(), selection),
|
|
@@ -909,6 +927,36 @@ export function __createWordReviewEditorRefBridge(
|
|
|
909
927
|
getWorkflowMetadataSnapshot: () => {
|
|
910
928
|
return clonePublicValue(runtime.getWorkflowMetadataSnapshot());
|
|
911
929
|
},
|
|
930
|
+
queryScopes: (filter) => {
|
|
931
|
+
return clonePublicValue(runtime.queryScopes(filter));
|
|
932
|
+
},
|
|
933
|
+
findScopesAt: (position, options) => {
|
|
934
|
+
return clonePublicValue(runtime.findScopesAt(position, options));
|
|
935
|
+
},
|
|
936
|
+
findScopesIntersecting: (range, options) => {
|
|
937
|
+
return clonePublicValue(runtime.findScopesIntersecting(range, options));
|
|
938
|
+
},
|
|
939
|
+
findFirstText: (query, opts) => {
|
|
940
|
+
const hits = findTextMatchesForRuntime(runtime, query, opts);
|
|
941
|
+
return hits.length > 0 ? (hits[0] ?? null) : null;
|
|
942
|
+
},
|
|
943
|
+
findAllText: (query, opts) => {
|
|
944
|
+
return findTextMatchesForRuntime(runtime, query, opts);
|
|
945
|
+
},
|
|
946
|
+
selectFirstText: (query, opts) => {
|
|
947
|
+
const hits = findTextMatchesForRuntime(runtime, query, opts);
|
|
948
|
+
if (hits.length === 0) return false;
|
|
949
|
+
applyRuntimeSelection(runtime, createSelectionFromAnchor(hits[0]!));
|
|
950
|
+
return true;
|
|
951
|
+
},
|
|
952
|
+
selectAllText: (query, opts) => {
|
|
953
|
+
const hits = findTextMatchesForRuntime(runtime, query, opts);
|
|
954
|
+
if (hits.length === 0) return 0;
|
|
955
|
+
applyRuntimeSelection(runtime, createSelectionFromAnchor(hits[0]!));
|
|
956
|
+
return hits.length;
|
|
957
|
+
},
|
|
958
|
+
findTextWithStyle: (query, filter, opts) => runtime.findTextWithStyle(query, filter, opts),
|
|
959
|
+
selectTextWithStyle: (query, filter, opts) => runtime.selectTextWithStyle(query, filter, opts),
|
|
912
960
|
// P17 — metadata persistence toggle + convert methods.
|
|
913
961
|
setMetadataPersistenceMode: (mode) => {
|
|
914
962
|
if (mode === "external" && !(options?.resolverRef?.current ?? null)) {
|
|
@@ -1112,7 +1160,10 @@ export function __createWordReviewEditorRefBridge(
|
|
|
1112
1160
|
replaceWorkflowMarkupText: (markupId, text) => {
|
|
1113
1161
|
runtime.replaceWorkflowMarkupText(markupId, text);
|
|
1114
1162
|
},
|
|
1163
|
+
...projections,
|
|
1115
1164
|
};
|
|
1165
|
+
refHolder.current = refValue;
|
|
1166
|
+
return refValue;
|
|
1116
1167
|
}
|
|
1117
1168
|
|
|
1118
1169
|
export function __applyRuntimeTextCommand(
|
|
@@ -1126,6 +1177,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1126
1177
|
function WordReviewEditor(props, ref) {
|
|
1127
1178
|
const {
|
|
1128
1179
|
currentUser,
|
|
1180
|
+
shellHeader,
|
|
1129
1181
|
ydoc,
|
|
1130
1182
|
awareness,
|
|
1131
1183
|
hostAdapter,
|
|
@@ -1163,6 +1215,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1163
1215
|
suggestionsEnabled = false,
|
|
1164
1216
|
showReviewPanel = true,
|
|
1165
1217
|
chromeVisibility,
|
|
1218
|
+
density,
|
|
1219
|
+
customSelectionTools,
|
|
1166
1220
|
} = props;
|
|
1167
1221
|
|
|
1168
1222
|
const [activeRailTab, setActiveRailTab] = useState<ReviewRailTab>("comments");
|
|
@@ -1190,6 +1244,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1190
1244
|
new Map<string, { choice: string; mergedValue?: Record<string, unknown> }>(),
|
|
1191
1245
|
);
|
|
1192
1246
|
const metadataConflictPendingRef = useRef(new Map<string, PendingConflict>());
|
|
1247
|
+
const suppressNextAwarenessPublishRef = useRef(false);
|
|
1193
1248
|
const {
|
|
1194
1249
|
runtime,
|
|
1195
1250
|
loadError,
|
|
@@ -1444,6 +1499,21 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1444
1499
|
activeRuntime.setDocumentMode(suggestionsEnabled ? "suggesting" : "editing");
|
|
1445
1500
|
}, [activeRuntime, suggestionsEnabled]);
|
|
1446
1501
|
|
|
1502
|
+
// design-close-chrome Phase 2 — density contract (designsystem §4.2).
|
|
1503
|
+
// When the `density` prop is supplied, drive the root `data-density`
|
|
1504
|
+
// attribute that powers the `--space-density-multiplier` cascade in
|
|
1505
|
+
// `tokens.css`. When the prop is omitted, leave the attribute alone
|
|
1506
|
+
// so the host (or `useDensity()`) retains control — the prop is
|
|
1507
|
+
// strictly an opt-in override, mirroring React's controlled vs.
|
|
1508
|
+
// uncontrolled input pattern. On unmount the attribute is left in
|
|
1509
|
+
// place so a subsequent host-managed setter (e.g. the density picker
|
|
1510
|
+
// in the shell view menu) can persist without a flicker.
|
|
1511
|
+
useEffect(() => {
|
|
1512
|
+
if (density === undefined) return;
|
|
1513
|
+
if (typeof document === "undefined") return;
|
|
1514
|
+
document.documentElement.dataset.density = density;
|
|
1515
|
+
}, [density]);
|
|
1516
|
+
|
|
1447
1517
|
const markCurrentSectionForReview = useCallback((input?: {
|
|
1448
1518
|
sectionIndex?: number;
|
|
1449
1519
|
label?: string;
|
|
@@ -1540,14 +1610,18 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1540
1610
|
return;
|
|
1541
1611
|
}
|
|
1542
1612
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1613
|
+
if (suppressNextAwarenessPublishRef.current) {
|
|
1614
|
+
suppressNextAwarenessPublishRef.current = false;
|
|
1615
|
+
} else {
|
|
1616
|
+
setLocalCursorState(awareness, {
|
|
1617
|
+
userId: currentUser.userId,
|
|
1618
|
+
displayName: currentUser.displayName,
|
|
1619
|
+
color: getCursorColorForUser(currentUser.userId),
|
|
1620
|
+
anchor: snapshot.selection.anchor,
|
|
1621
|
+
head: snapshot.selection.head,
|
|
1622
|
+
storyTarget: snapshot.activeStory,
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1551
1625
|
}, [
|
|
1552
1626
|
awareness,
|
|
1553
1627
|
currentUser.displayName,
|
|
@@ -1572,7 +1646,17 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1572
1646
|
|
|
1573
1647
|
useImperativeHandle(
|
|
1574
1648
|
ref,
|
|
1575
|
-
() =>
|
|
1649
|
+
() => {
|
|
1650
|
+
// v2.0.0 Track D — projections dispatch through a holder so they
|
|
1651
|
+
// always point at the freshly-built ref on every
|
|
1652
|
+
// useImperativeHandle re-run.
|
|
1653
|
+
const refHolder: { current: WordReviewEditorRef | null } = { current: null };
|
|
1654
|
+
const projections = buildRefProjections(() => {
|
|
1655
|
+
const r = refHolder.current;
|
|
1656
|
+
if (!r) throw new Error("ref projection used before ref initialization");
|
|
1657
|
+
return r;
|
|
1658
|
+
});
|
|
1659
|
+
const refValue: WordReviewEditorRef = ({
|
|
1576
1660
|
focus: () => activeRuntime.focus(),
|
|
1577
1661
|
blur: () => activeRuntime.blur(),
|
|
1578
1662
|
undo: () => activeRuntime.undo(),
|
|
@@ -1607,6 +1691,12 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1607
1691
|
addScope: (params) => activeRuntime.addScope(params),
|
|
1608
1692
|
getScope: (scopeId) => activeRuntime.getScope(scopeId),
|
|
1609
1693
|
removeScope: (scopeId) => activeRuntime.removeScope(scopeId),
|
|
1694
|
+
addInvisibleScope: (params) => activeRuntime.addInvisibleScope(params),
|
|
1695
|
+
setScopeVisibility: (scopeId, visibility) => activeRuntime.setScopeVisibility(scopeId, visibility),
|
|
1696
|
+
getScopeVisibility: (scopeId) => activeRuntime.getScopeVisibility(scopeId),
|
|
1697
|
+
setScopeChromeVisibility: (state) => activeRuntime.setScopeChromeVisibility(state),
|
|
1698
|
+
getScopeChromeVisibility: () => activeRuntime.getScopeChromeVisibility(),
|
|
1699
|
+
subscribeToScopeQuery: (filter, callback) => activeRuntime.subscribeToScopeQuery(filter, callback),
|
|
1610
1700
|
acceptChange: (changeId) => activeRuntime.acceptChange(changeId),
|
|
1611
1701
|
rejectChange: (changeId) => activeRuntime.rejectChange(changeId),
|
|
1612
1702
|
acceptAllChanges: () => activeRuntime.acceptAllChanges(),
|
|
@@ -1821,11 +1911,22 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1821
1911
|
clearSearch: () => {
|
|
1822
1912
|
surfaceRef.current?.clearSearch();
|
|
1823
1913
|
},
|
|
1824
|
-
setSelection: (selection) => {
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1914
|
+
setSelection: (selection, options?: SetSelectionOptions) => {
|
|
1915
|
+
const snap = activeRuntime.getRenderSnapshot();
|
|
1916
|
+
const normalized = normalizeRequestedSelection(snap, selection);
|
|
1917
|
+
if (options?.silent) {
|
|
1918
|
+
// Only suppress awareness publish if the selection will actually change.
|
|
1919
|
+
// If positions are identical the cursor-state useEffect won't re-run,
|
|
1920
|
+
// which would leave the flag set and silently suppress the next real
|
|
1921
|
+
// selection change (stale-flag bug).
|
|
1922
|
+
if (
|
|
1923
|
+
normalized.anchor !== snap.selection.anchor ||
|
|
1924
|
+
normalized.head !== snap.selection.head
|
|
1925
|
+
) {
|
|
1926
|
+
suppressNextAwarenessPublishRef.current = true;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
applyRuntimeSelection(activeRuntime, normalized);
|
|
1829
1930
|
},
|
|
1830
1931
|
scrollToRevision: (revisionId: string) => {
|
|
1831
1932
|
const revision = activeRuntime.getRenderSnapshot().trackedChanges.revisions.find(
|
|
@@ -1976,6 +2077,36 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1976
2077
|
getWorkflowMetadataSnapshot: () => {
|
|
1977
2078
|
return clonePublicValue(activeRuntime.getWorkflowMetadataSnapshot());
|
|
1978
2079
|
},
|
|
2080
|
+
queryScopes: (filter) => {
|
|
2081
|
+
return clonePublicValue(activeRuntime.queryScopes(filter));
|
|
2082
|
+
},
|
|
2083
|
+
findScopesAt: (position, options) => {
|
|
2084
|
+
return clonePublicValue(activeRuntime.findScopesAt(position, options));
|
|
2085
|
+
},
|
|
2086
|
+
findScopesIntersecting: (range, options) => {
|
|
2087
|
+
return clonePublicValue(activeRuntime.findScopesIntersecting(range, options));
|
|
2088
|
+
},
|
|
2089
|
+
findFirstText: (query, opts) => {
|
|
2090
|
+
const hits = findTextMatchesForRuntime(activeRuntime, query, opts);
|
|
2091
|
+
return hits.length > 0 ? (hits[0] ?? null) : null;
|
|
2092
|
+
},
|
|
2093
|
+
findAllText: (query, opts) => {
|
|
2094
|
+
return findTextMatchesForRuntime(activeRuntime, query, opts);
|
|
2095
|
+
},
|
|
2096
|
+
selectFirstText: (query, opts) => {
|
|
2097
|
+
const hits = findTextMatchesForRuntime(activeRuntime, query, opts);
|
|
2098
|
+
if (hits.length === 0) return false;
|
|
2099
|
+
applyRuntimeSelection(activeRuntime, createSelectionFromAnchor(hits[0]!));
|
|
2100
|
+
return true;
|
|
2101
|
+
},
|
|
2102
|
+
selectAllText: (query, opts) => {
|
|
2103
|
+
const hits = findTextMatchesForRuntime(activeRuntime, query, opts);
|
|
2104
|
+
if (hits.length === 0) return 0;
|
|
2105
|
+
applyRuntimeSelection(activeRuntime, createSelectionFromAnchor(hits[0]!));
|
|
2106
|
+
return hits.length;
|
|
2107
|
+
},
|
|
2108
|
+
findTextWithStyle: (query, filter, opts) => activeRuntime.findTextWithStyle(query, filter, opts),
|
|
2109
|
+
selectTextWithStyle: (query, filter, opts) => activeRuntime.selectTextWithStyle(query, filter, opts),
|
|
1979
2110
|
// P17 — metadata persistence toggle + convert methods.
|
|
1980
2111
|
setMetadataPersistenceMode: (mode) => {
|
|
1981
2112
|
if (mode === "external" && scopeMetadataResolverRef.current === null) {
|
|
@@ -2170,7 +2301,11 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2170
2301
|
replaceWorkflowMarkupText: (markupId, text) => {
|
|
2171
2302
|
activeRuntime.replaceWorkflowMarkupText(markupId, text);
|
|
2172
2303
|
},
|
|
2173
|
-
|
|
2304
|
+
...projections,
|
|
2305
|
+
}) as WordReviewEditorRef;
|
|
2306
|
+
refHolder.current = refValue;
|
|
2307
|
+
return refValue;
|
|
2308
|
+
},
|
|
2174
2309
|
[
|
|
2175
2310
|
activeRuntime,
|
|
2176
2311
|
clearReviewSectionMarkById,
|
|
@@ -2484,29 +2619,40 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2484
2619
|
suggestionsSnapshot,
|
|
2485
2620
|
viewState.activeStory,
|
|
2486
2621
|
]);
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2622
|
+
// R9 — host may supply custom selection-tool registry entries; merge
|
|
2623
|
+
// with the default before resolving. The merger is a pure function
|
|
2624
|
+
// returning the canonical default array by reference when `custom`
|
|
2625
|
+
// is empty, so the typical no-custom case is a single identity call.
|
|
2626
|
+
const selectionToolRegistry = useMemo(
|
|
2627
|
+
() => resolveSelectionToolRegistry(customSelectionTools),
|
|
2628
|
+
[customSelectionTools],
|
|
2629
|
+
);
|
|
2630
|
+
const activeSelectionTool = resolveActiveSelectionTool(
|
|
2631
|
+
{
|
|
2632
|
+
snapshot,
|
|
2633
|
+
viewState,
|
|
2634
|
+
capabilities,
|
|
2635
|
+
documentNavigation,
|
|
2636
|
+
styleCatalog,
|
|
2637
|
+
formattingState,
|
|
2638
|
+
workflowScopeSnapshot,
|
|
2639
|
+
interactionGuardSnapshot,
|
|
2640
|
+
workflowMarkupSnapshot: workflowMarkupSnapshot ?? undefined,
|
|
2641
|
+
suggestionsSnapshot,
|
|
2642
|
+
activeRevisionId,
|
|
2643
|
+
activeCommentId: snapshot.comments.activeCommentId,
|
|
2644
|
+
activeCommentThread,
|
|
2645
|
+
activeTableContext,
|
|
2646
|
+
activeImageContext,
|
|
2647
|
+
activeObjectContext,
|
|
2648
|
+
activeListContext: viewState.activeListContext,
|
|
2649
|
+
preferListStructureContext: viewState.workspaceMode === "page",
|
|
2650
|
+
addCommentDisabledReason,
|
|
2651
|
+
suppressedSuggestionRevisionId,
|
|
2652
|
+
scopedChromePolicy,
|
|
2653
|
+
},
|
|
2654
|
+
selectionToolRegistry,
|
|
2655
|
+
);
|
|
2510
2656
|
const selectionToolbarSelectionKey = useMemo(
|
|
2511
2657
|
() =>
|
|
2512
2658
|
createSelectionToolbarSelectionKey(
|
|
@@ -3237,6 +3383,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3237
3383
|
)}
|
|
3238
3384
|
visuallyHiddenStyles={VISUALLY_HIDDEN_STYLES}
|
|
3239
3385
|
onShellKeyDownCapture={handleShellKeyDownCapture}
|
|
3386
|
+
shellHeader={shellHeader}
|
|
3240
3387
|
viewState={viewState}
|
|
3241
3388
|
markupDisplay={liveMarkupDisplay}
|
|
3242
3389
|
currentUserId={currentUser.userId}
|
|
@@ -3339,6 +3486,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3339
3486
|
onScopeRejectSuggestionGroup={(payload) => {
|
|
3340
3487
|
applySuggestionGroupAction(activeRuntime, payload.groupId, "reject");
|
|
3341
3488
|
}}
|
|
3489
|
+
onDeselectObject={() => activeRuntime.deselectObject()}
|
|
3342
3490
|
onScopeAskAgent={(payload) => {
|
|
3343
3491
|
// Resolve the scope's anchor + story from the facet's card
|
|
3344
3492
|
// model so the agent request carries the canonical range.
|
|
@@ -3393,13 +3541,28 @@ function applySuggestionGroupAction(
|
|
|
3393
3541
|
): void {
|
|
3394
3542
|
const snapshot = runtime.getSuggestionsSnapshot();
|
|
3395
3543
|
const group = snapshot.groups?.find((entry) => entry.groupId === groupId);
|
|
3396
|
-
|
|
3544
|
+
const op = action === "accept" ? "acceptSuggestionGroup" : "rejectSuggestionGroup";
|
|
3545
|
+
if (!group) {
|
|
3546
|
+
runtime.emitTransientWarning({
|
|
3547
|
+
warningId: `suggestion-group-unknown-${groupId}-${Date.now()}`,
|
|
3548
|
+
code: "review_target_not_found",
|
|
3549
|
+
severity: "info",
|
|
3550
|
+
message: `${op}("${groupId}") skipped: unknown groupId.`,
|
|
3551
|
+
source: "review",
|
|
3552
|
+
details: { op, targetId: groupId, reason: "group_unknown" },
|
|
3553
|
+
});
|
|
3554
|
+
return;
|
|
3555
|
+
}
|
|
3397
3556
|
const byId = new Map(
|
|
3398
3557
|
snapshot.suggestions.map((entry) => [entry.suggestionId, entry]),
|
|
3399
3558
|
);
|
|
3559
|
+
const skippedSuggestions: string[] = [];
|
|
3400
3560
|
for (const suggestionId of group.suggestionIds) {
|
|
3401
3561
|
const suggestion = byId.get(suggestionId);
|
|
3402
|
-
if (!suggestion)
|
|
3562
|
+
if (!suggestion) {
|
|
3563
|
+
skippedSuggestions.push(suggestionId);
|
|
3564
|
+
continue;
|
|
3565
|
+
}
|
|
3403
3566
|
for (const changeId of suggestion.changeIds) {
|
|
3404
3567
|
if (action === "accept") {
|
|
3405
3568
|
runtime.acceptChange(changeId);
|
|
@@ -3408,6 +3571,21 @@ function applySuggestionGroupAction(
|
|
|
3408
3571
|
}
|
|
3409
3572
|
}
|
|
3410
3573
|
}
|
|
3574
|
+
if (skippedSuggestions.length > 0) {
|
|
3575
|
+
runtime.emitTransientWarning({
|
|
3576
|
+
warningId: `suggestion-group-stale-${groupId}-${Date.now()}`,
|
|
3577
|
+
code: "review_target_not_found",
|
|
3578
|
+
severity: "info",
|
|
3579
|
+
message: `${op}("${groupId}") partially skipped: ${skippedSuggestions.length} suggestion(s) no longer in snapshot.`,
|
|
3580
|
+
source: "review",
|
|
3581
|
+
details: {
|
|
3582
|
+
op,
|
|
3583
|
+
targetId: groupId,
|
|
3584
|
+
reason: "suggestion_stale",
|
|
3585
|
+
skippedSuggestionIds: skippedSuggestions,
|
|
3586
|
+
},
|
|
3587
|
+
});
|
|
3588
|
+
}
|
|
3411
3589
|
}
|
|
3412
3590
|
|
|
3413
3591
|
function applyRuntimeFormattingOperation(
|
|
@@ -4980,12 +5158,34 @@ function applyRuntimeDeleteComment(
|
|
|
4980
5158
|
commentId: string,
|
|
4981
5159
|
): void {
|
|
4982
5160
|
const snapshot = runtime.getRenderSnapshot();
|
|
4983
|
-
|
|
5161
|
+
// Pre-ready / fatal states stay silent: the host called too early, and
|
|
5162
|
+
// there is no meaningful document yet to signal against. Emitting a
|
|
5163
|
+
// warning here would only add noise to load-time error handling.
|
|
5164
|
+
if (!snapshot.isReady || snapshot.fatalError) {
|
|
5165
|
+
return;
|
|
5166
|
+
}
|
|
5167
|
+
if (snapshot.readOnly) {
|
|
5168
|
+
runtime.emitTransientWarning({
|
|
5169
|
+
warningId: `delete-comment-readonly-${commentId}-${Date.now()}`,
|
|
5170
|
+
code: "review_target_not_found",
|
|
5171
|
+
severity: "info",
|
|
5172
|
+
message: `deleteComment("${commentId}") skipped: editor is read-only.`,
|
|
5173
|
+
source: "review",
|
|
5174
|
+
details: { op: "deleteComment", targetId: commentId, reason: "read_only" },
|
|
5175
|
+
});
|
|
4984
5176
|
return;
|
|
4985
5177
|
}
|
|
4986
5178
|
|
|
4987
5179
|
const sessionState = runtime.getSessionState();
|
|
4988
5180
|
if (!sessionState.canonicalDocument.review.comments[commentId]) {
|
|
5181
|
+
runtime.emitTransientWarning({
|
|
5182
|
+
warningId: `delete-comment-unknown-${commentId}-${Date.now()}`,
|
|
5183
|
+
code: "review_target_not_found",
|
|
5184
|
+
severity: "info",
|
|
5185
|
+
message: `deleteComment("${commentId}") skipped: unknown commentId.`,
|
|
5186
|
+
source: "review",
|
|
5187
|
+
details: { op: "deleteComment", targetId: commentId, reason: "comment_unknown" },
|
|
5188
|
+
});
|
|
4989
5189
|
return;
|
|
4990
5190
|
}
|
|
4991
5191
|
|
|
@@ -5056,6 +5256,20 @@ function clonePublicValue<T>(value: T): T {
|
|
|
5056
5256
|
return structuredClone(value);
|
|
5057
5257
|
}
|
|
5058
5258
|
|
|
5259
|
+
function findTextMatchesForRuntime(
|
|
5260
|
+
runtime: WordReviewEditorRuntime,
|
|
5261
|
+
query: string,
|
|
5262
|
+
options: SearchOptions | undefined,
|
|
5263
|
+
): EditorAnchorProjection[] {
|
|
5264
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
5265
|
+
return findTextMatches(
|
|
5266
|
+
runtime.getSessionState().canonicalDocument,
|
|
5267
|
+
snapshot.selection,
|
|
5268
|
+
query,
|
|
5269
|
+
options ?? {},
|
|
5270
|
+
);
|
|
5271
|
+
}
|
|
5272
|
+
|
|
5059
5273
|
/**
|
|
5060
5274
|
* Open the correct header/footer story for a specific page. The page's
|
|
5061
5275
|
* resolved `stories.header` / `stories.footer` already carries the
|
|
@@ -1015,6 +1015,7 @@ function createLoadingRuntimeBridge(input: {
|
|
|
1015
1015
|
subscribe: () => () => undefined,
|
|
1016
1016
|
subscribeToEvents: () => () => undefined,
|
|
1017
1017
|
emitBlockedCommand: () => undefined,
|
|
1018
|
+
emitTransientWarning: () => undefined,
|
|
1018
1019
|
getRenderSnapshot: () => {
|
|
1019
1020
|
const progressive = input.progressiveSurfaceRef?.current;
|
|
1020
1021
|
if (progressive == null) return input.snapshot;
|
|
@@ -1154,6 +1155,9 @@ function createLoadingRuntimeBridge(input: {
|
|
|
1154
1155
|
definitions: [],
|
|
1155
1156
|
entries: [],
|
|
1156
1157
|
}),
|
|
1158
|
+
queryScopes: () => [],
|
|
1159
|
+
findScopesAt: () => [],
|
|
1160
|
+
findScopesIntersecting: () => [],
|
|
1157
1161
|
setHostAnnotationOverlay: () => undefined,
|
|
1158
1162
|
clearHostAnnotationOverlay: () => undefined,
|
|
1159
1163
|
getHostAnnotationSnapshot: () => ({
|
|
@@ -1183,6 +1187,15 @@ function createLoadingRuntimeBridge(input: {
|
|
|
1183
1187
|
resetPerfCounters: () => undefined,
|
|
1184
1188
|
setVisibleBlockRange: () => undefined,
|
|
1185
1189
|
requestViewportRefresh: () => undefined,
|
|
1190
|
+
addInvisibleScope: () => ({ scopeId: "", anchor: { kind: "range", from: 0, to: 0, assoc: { start: -1, end: 1 } } }),
|
|
1191
|
+
setScopeVisibility: () => undefined,
|
|
1192
|
+
getScopeVisibility: () => "visible" as const,
|
|
1193
|
+
setScopeChromeVisibility: () => undefined,
|
|
1194
|
+
getScopeChromeVisibility: () => ({ mode: "all" as const }),
|
|
1195
|
+
subscribeToScopeQuery: (_filter, _callback) => () => undefined,
|
|
1196
|
+
findAllText: () => [],
|
|
1197
|
+
findTextWithStyle: () => [],
|
|
1198
|
+
selectTextWithStyle: () => 0,
|
|
1186
1199
|
};
|
|
1187
1200
|
}
|
|
1188
1201
|
|
|
@@ -82,6 +82,7 @@ export interface EditorShellViewProps {
|
|
|
82
82
|
selectionToolAnchor?: SelectionToolAnchor | null;
|
|
83
83
|
documentNavigation?: DocumentNavigationSnapshot;
|
|
84
84
|
commands: EditorCommandBag;
|
|
85
|
+
shellHeader?: ReactNode;
|
|
85
86
|
document: ReactNode;
|
|
86
87
|
onAddCommentFromSelection?: () => void;
|
|
87
88
|
onAddCommentFromSuggestion?: () => void;
|
|
@@ -126,6 +127,8 @@ export interface EditorShellViewProps {
|
|
|
126
127
|
}) => void;
|
|
127
128
|
/** K2 — forwarded from workspace to WordReviewEditor. */
|
|
128
129
|
onScopeAskAgent?: (payload: { scopeId: string }) => void;
|
|
130
|
+
/** N6 — deselects the currently grabbed object; wired to runtime.deselectObject(). */
|
|
131
|
+
onDeselectObject?: () => void;
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
export function EditorShellView(props: EditorShellViewProps) {
|
|
@@ -154,7 +157,7 @@ export function EditorShellView(props: EditorShellViewProps) {
|
|
|
154
157
|
aria-describedby={`${accessibilityInstructionsId} ${accessibilityStatusId}${
|
|
155
158
|
diagnosticsModeMessage ? ` ${accessibilityAlertId}` : ""
|
|
156
159
|
}`}
|
|
157
|
-
className="wre-editor relative h-full"
|
|
160
|
+
className="wre-editor wre-editor-root relative h-full"
|
|
158
161
|
onKeyDownCapture={onShellKeyDownCapture}
|
|
159
162
|
>
|
|
160
163
|
<p id={accessibilityInstructionsId} style={visuallyHiddenStyles}>
|
|
@@ -102,6 +102,59 @@ export const SELECTION_TOOL_REGISTRY: ReadonlyArray<SelectionToolRegistryEntry>
|
|
|
102
102
|
{ id: "blocked-explainer", surfaces: ["selection-tool"], group: "blocked", precedence: 60 },
|
|
103
103
|
];
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* R10 — options for `resolveSelectionToolRegistry`.
|
|
107
|
+
*
|
|
108
|
+
* `remove` lets a host suppress a default kind entirely (the dedicated
|
|
109
|
+
* builder for that kind is skipped at resolve time). Used together
|
|
110
|
+
* with `custom` when the host wants to both add a new precedence and
|
|
111
|
+
* silence an existing one.
|
|
112
|
+
*/
|
|
113
|
+
export interface ResolveSelectionToolRegistryOptions {
|
|
114
|
+
remove?: ReadonlyArray<SelectionToolRegistryEntry["id"]>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Merge host-supplied selection-tool entries with the default registry.
|
|
119
|
+
*
|
|
120
|
+
* design-close-chrome Phase 4 / designsystem §6 extensibility. Pure
|
|
121
|
+
* function — callers supply `custom` as a stable array (memoize in
|
|
122
|
+
* React), this returns a fresh, precedence-sorted copy that the
|
|
123
|
+
* resolver can iterate. Dedupe rule: host entries win on id collision
|
|
124
|
+
* (they override default precedence for that id). When `options.remove`
|
|
125
|
+
* lists ids, those are filtered out of the default set before merging.
|
|
126
|
+
*
|
|
127
|
+
* Note: as of this Phase, the resolver builder matrix (see
|
|
128
|
+
* `buildSelectionToolFromRegistryEntry` in `selection-tool-resolver.ts`)
|
|
129
|
+
* still only understands the built-in `SelectionToolKind` union.
|
|
130
|
+
* Widening that union to accept host render functions is a downstream
|
|
131
|
+
* change coordinated with Lane 8 API ergonomics; the merger is shipped
|
|
132
|
+
* first so callers can depend on the final shape while the renderer
|
|
133
|
+
* catches up.
|
|
134
|
+
*/
|
|
135
|
+
export function resolveSelectionToolRegistry(
|
|
136
|
+
custom?: ReadonlyArray<SelectionToolRegistryEntry>,
|
|
137
|
+
options?: ResolveSelectionToolRegistryOptions,
|
|
138
|
+
): ReadonlyArray<SelectionToolRegistryEntry> {
|
|
139
|
+
const remove = options?.remove;
|
|
140
|
+
const hasCustom = custom && custom.length > 0;
|
|
141
|
+
const hasRemove = remove && remove.length > 0;
|
|
142
|
+
if (!hasCustom && !hasRemove) return SELECTION_TOOL_REGISTRY;
|
|
143
|
+
const removed = new Set(remove ?? []);
|
|
144
|
+
const byId = new Map<SelectionToolRegistryEntry["id"], SelectionToolRegistryEntry>();
|
|
145
|
+
for (const entry of SELECTION_TOOL_REGISTRY) {
|
|
146
|
+
if (removed.has(entry.id)) continue;
|
|
147
|
+
byId.set(entry.id, entry);
|
|
148
|
+
}
|
|
149
|
+
for (const entry of custom ?? []) {
|
|
150
|
+
// Host entries are NOT filtered by `remove` — callers can always
|
|
151
|
+
// install an entry they meant to keep even when a matching default
|
|
152
|
+
// would otherwise have been suppressed.
|
|
153
|
+
byId.set(entry.id, entry);
|
|
154
|
+
}
|
|
155
|
+
return [...byId.values()].sort((a, b) => a.precedence - b.precedence);
|
|
156
|
+
}
|
|
157
|
+
|
|
105
158
|
export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry> = [
|
|
106
159
|
{
|
|
107
160
|
id: "history",
|
|
@@ -23,10 +23,20 @@ import {
|
|
|
23
23
|
} from "./chrome-registry";
|
|
24
24
|
import { shouldRenderSelectionToolKind } from "./scoped-chrome-policy";
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the active selection tool for `input`.
|
|
28
|
+
*
|
|
29
|
+
* `registry` defaults to the canonical `SELECTION_TOOL_REGISTRY`. Hosts
|
|
30
|
+
* that want to reorder or shadow default precedences pass in the result
|
|
31
|
+
* of `resolveSelectionToolRegistry(props.customSelectionTools, options)`.
|
|
32
|
+
* Entries iterate in the order provided — callers are responsible for
|
|
33
|
+
* supplying a precedence-sorted array.
|
|
34
|
+
*/
|
|
26
35
|
export function resolveActiveSelectionTool(
|
|
27
36
|
input: SelectionToolResolverInput,
|
|
37
|
+
registry: ReadonlyArray<SelectionToolRegistryEntry> = SELECTION_TOOL_REGISTRY,
|
|
28
38
|
): ActiveSelectionToolModel | null {
|
|
29
|
-
for (const entry of
|
|
39
|
+
for (const entry of registry) {
|
|
30
40
|
if (!shouldRenderSelectionToolKind(input.scopedChromePolicy, entry.id)) {
|
|
31
41
|
continue;
|
|
32
42
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
EditorRole,
|
|
2
3
|
WordReviewEditorChromeOptions,
|
|
3
4
|
WordReviewEditorChromePreset,
|
|
4
5
|
WordReviewEditorChromeVisibility,
|
|
@@ -21,6 +22,7 @@ export function resolveChromePreset(
|
|
|
21
22
|
export function resolveChromePresetOptions(
|
|
22
23
|
chromePreset: WordReviewEditorChromePreset,
|
|
23
24
|
overrides?: Partial<WordReviewEditorChromeOptions>,
|
|
25
|
+
editorRole?: EditorRole,
|
|
24
26
|
): WordReviewEditorChromeOptions {
|
|
25
27
|
const defaults: Record<WordReviewEditorChromePreset, WordReviewEditorChromeOptions> = {
|
|
26
28
|
selection: {
|
|
@@ -73,10 +75,21 @@ export function resolveChromePresetOptions(
|
|
|
73
75
|
|
|
74
76
|
return {
|
|
75
77
|
...defaults[chromePreset],
|
|
78
|
+
...resolveRoleAdjustments(editorRole),
|
|
76
79
|
...overrides,
|
|
77
80
|
};
|
|
78
81
|
}
|
|
79
82
|
|
|
83
|
+
function resolveRoleAdjustments(role: EditorRole | undefined): Partial<WordReviewEditorChromeOptions> {
|
|
84
|
+
if (role === "editor") {
|
|
85
|
+
return { showReviewRail: false, showReviewQueueBar: false };
|
|
86
|
+
}
|
|
87
|
+
if (role === "workflow") {
|
|
88
|
+
return { showReviewQueueBar: false };
|
|
89
|
+
}
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
|
|
80
93
|
export function resolveChromeVisibilityForPreset(input: {
|
|
81
94
|
chromePreset: WordReviewEditorChromePreset;
|
|
82
95
|
chromeOptions?: Partial<WordReviewEditorChromeOptions>;
|