@beyondwork/docx-react-component 1.0.39 → 1.0.40
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/public-types.ts +122 -0
- package/src/index.ts +9 -0
- package/src/runtime/document-runtime.ts +7 -0
- package/src/runtime/layout/docx-font-loader.ts +30 -11
- package/src/runtime/layout/inert-layout-facet.ts +1 -0
- package/src/runtime/layout/public-facet.ts +41 -0
- package/src/runtime/workflow-rail-segments.ts +149 -1
- package/src/ui/WordReviewEditor.tsx +17 -0
- package/src/ui/editor-shell-view.tsx +18 -0
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +80 -0
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +89 -20
- package/src/ui-tailwind/chrome-overlay/index.ts +2 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +55 -1
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +133 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +386 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +140 -69
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +7 -2
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +26 -1
- package/src/ui-tailwind/index.ts +5 -0
- package/src/ui-tailwind/theme/editor-theme.css +108 -15
- package/src/ui-tailwind/tw-review-workspace.tsx +75 -0
|
@@ -39,6 +39,12 @@ function getWorkflowInlineClass(
|
|
|
39
39
|
isActiveWorkItem: boolean,
|
|
40
40
|
isSelectionZone: boolean,
|
|
41
41
|
): string {
|
|
42
|
+
// Active-work-item emphasis lives on the ChromeOverlay rail stripe, not
|
|
43
|
+
// on the PM inline decoration. The per-run outline produced a halo
|
|
44
|
+
// around every text fragment (one box per wrapped span due to
|
|
45
|
+
// box-decoration-break: clone) — the `data-workflow-active` attribute
|
|
46
|
+
// remains the canonical hook for hit-testing and accessibility.
|
|
47
|
+
void isActiveWorkItem;
|
|
42
48
|
const base =
|
|
43
49
|
scope.mode === "edit"
|
|
44
50
|
? "wre-workflow-inline wre-workflow-inline-edit"
|
|
@@ -47,8 +53,7 @@ function getWorkflowInlineClass(
|
|
|
47
53
|
: scope.mode === "comment"
|
|
48
54
|
? "wre-workflow-inline wre-workflow-inline-comment"
|
|
49
55
|
: "wre-workflow-inline wre-workflow-inline-view";
|
|
50
|
-
|
|
51
|
-
return isActiveWorkItem ? `${withZone} wre-workflow-inline-active` : withZone;
|
|
56
|
+
return isSelectionZone ? `${base} wre-workflow-inline-zone` : base;
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
function getWorkflowRailClass(
|
|
@@ -578,6 +578,23 @@ function buildSdtBlock(
|
|
|
578
578
|
);
|
|
579
579
|
}
|
|
580
580
|
|
|
581
|
+
/**
|
|
582
|
+
* Labels surface-projection emits for preserve-only complex fragments
|
|
583
|
+
* (charts, SmartArt, drawing shapes, WordArt, legacy VML). These have
|
|
584
|
+
* no first-class rendering — when the debug preview toggle is off, they
|
|
585
|
+
* collapse to a zero-dimension quiet marker so the reviewer's document
|
|
586
|
+
* view stays clean. Toggling `showUnsupportedObjectPreviews` on swaps
|
|
587
|
+
* them to the richer atom node specs that ship the preserved detail.
|
|
588
|
+
*/
|
|
589
|
+
const UNSUPPORTED_COMPLEX_PREVIEW_LABELS = new Set<string>([
|
|
590
|
+
"Embedded chart",
|
|
591
|
+
"SmartArt diagram",
|
|
592
|
+
"Drawing shape",
|
|
593
|
+
"Text box",
|
|
594
|
+
"WordArt",
|
|
595
|
+
"Legacy VML drawing",
|
|
596
|
+
]);
|
|
597
|
+
|
|
581
598
|
/**
|
|
582
599
|
* Map an opaque_inline surface segment to a dedicated complex-rendering PM atom
|
|
583
600
|
* node when the label identifies a known complex content type, or fall back to
|
|
@@ -635,12 +652,20 @@ function buildOpaqueInlineOrComplexAtom(
|
|
|
635
652
|
});
|
|
636
653
|
}
|
|
637
654
|
|
|
655
|
+
// Preserve-only complex fragments without the debug toggle: collapse
|
|
656
|
+
// to a zero-dimension quiet marker so the document view matches the
|
|
657
|
+
// dev-drawer copy ("off by default"). The fragment stays in the
|
|
658
|
+
// canonical document, so export round-trips remain lossless.
|
|
659
|
+
const effectivePresentation =
|
|
660
|
+
segment.presentation ??
|
|
661
|
+
(UNSUPPORTED_COMPLEX_PREVIEW_LABELS.has(label) ? "quiet-marker" : "inline-chip");
|
|
662
|
+
|
|
638
663
|
return editorSchema.nodes.opaque_inline.create({
|
|
639
664
|
fragmentId: segment.fragmentId,
|
|
640
665
|
warningId: segment.warningId,
|
|
641
666
|
label,
|
|
642
667
|
detail,
|
|
643
|
-
presentation:
|
|
668
|
+
presentation: effectivePresentation,
|
|
644
669
|
displayText: segment.displayText ?? null,
|
|
645
670
|
});
|
|
646
671
|
}
|
package/src/ui-tailwind/index.ts
CHANGED
|
@@ -47,6 +47,11 @@ export { TwStatusBar } from "./status/tw-status-bar";
|
|
|
47
47
|
// Chrome
|
|
48
48
|
export { TwAlertBanner } from "./chrome/tw-alert-banner";
|
|
49
49
|
export { TwSelectionToolbar } from "./chrome/tw-selection-toolbar";
|
|
50
|
+
export {
|
|
51
|
+
TwModeDock,
|
|
52
|
+
type TwModeDockAction,
|
|
53
|
+
type TwModeDockProps,
|
|
54
|
+
} from "./chrome/tw-mode-dock";
|
|
50
55
|
|
|
51
56
|
// Chrome overlay plane (R3a — scope rail layer)
|
|
52
57
|
export {
|
|
@@ -408,8 +408,16 @@
|
|
|
408
408
|
box-shadow: inset -1px 0 0 color-mix(in srgb, var(--color-danger) 35%, transparent);
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
+
/*
|
|
412
|
+
* `wre-workflow-inline-active` no longer emits a visual outline. The
|
|
413
|
+
* per-run inset box-shadow produced a halo around every text fragment
|
|
414
|
+
* (one box per run, due to box-decoration-break: clone above), which
|
|
415
|
+
* fought with the overlay's flat tint. The class name is kept on the
|
|
416
|
+
* inline decoration as a data hook (no visual), and emphasis for the
|
|
417
|
+
* active scope now lives on the ChromeOverlay rail stripe + scope card.
|
|
418
|
+
*/
|
|
411
419
|
.prosemirror-surface .ProseMirror .wre-workflow-inline-active {
|
|
412
|
-
|
|
420
|
+
/* intentionally empty — visual emphasis handled by ChromeOverlay */
|
|
413
421
|
}
|
|
414
422
|
|
|
415
423
|
/*
|
|
@@ -423,9 +431,37 @@
|
|
|
423
431
|
pointer-events: none;
|
|
424
432
|
}
|
|
425
433
|
|
|
434
|
+
/*
|
|
435
|
+
* ─── Gutter lane tokens ───
|
|
436
|
+
*
|
|
437
|
+
* The scope rail and scope card chrome live in a reserved lane to the
|
|
438
|
+
* left of the page frame so they visibly read as chrome (not document
|
|
439
|
+
* content). Page surfaces use 64px, canvas surfaces 48px. Host apps
|
|
440
|
+
* can override via CSS custom property.
|
|
441
|
+
*/
|
|
442
|
+
:root {
|
|
443
|
+
--wre-gutter-lane-width: 64px;
|
|
444
|
+
--wre-gutter-lane-pad: 8px;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.wre-canvas-surface {
|
|
448
|
+
--wre-gutter-lane-width: 48px;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/*
|
|
452
|
+
* The page-with-gutter grid shell allocates the gutter as its own
|
|
453
|
+
* column so absolute-positioned chrome overlays (which fill inset 0 of
|
|
454
|
+
* this shell) land INSIDE the gutter, not over the shell background.
|
|
455
|
+
*/
|
|
456
|
+
.wre-page-with-gutter {
|
|
457
|
+
display: grid;
|
|
458
|
+
grid-template-columns: var(--wre-gutter-lane-width) 1fr;
|
|
459
|
+
position: relative;
|
|
460
|
+
}
|
|
461
|
+
|
|
426
462
|
.wre-scope-rail-tint {
|
|
427
463
|
position: absolute;
|
|
428
|
-
border-radius: 0.
|
|
464
|
+
border-radius: 0.2rem;
|
|
429
465
|
pointer-events: none;
|
|
430
466
|
z-index: 0;
|
|
431
467
|
transition: background 140ms ease-out;
|
|
@@ -452,32 +488,89 @@
|
|
|
452
488
|
outline-offset: -1px;
|
|
453
489
|
}
|
|
454
490
|
|
|
491
|
+
/*
|
|
492
|
+
* ─── Scope rail stripe ───
|
|
493
|
+
*
|
|
494
|
+
* The rail stripe is the rest-state representation of a scope: a 4px
|
|
495
|
+
* color stripe in the gutter lane. Posture color comes from the
|
|
496
|
+
* accent/warning/insert/secondary/danger tokens. Hover widens the
|
|
497
|
+
* stripe via transform (zero layout cost) and reveals the label pill.
|
|
498
|
+
*/
|
|
499
|
+
.wre-scope-rail-stripe {
|
|
500
|
+
position: absolute;
|
|
501
|
+
width: 4px;
|
|
502
|
+
border-radius: 2px;
|
|
503
|
+
background: currentColor;
|
|
504
|
+
pointer-events: auto;
|
|
505
|
+
cursor: pointer;
|
|
506
|
+
z-index: 1;
|
|
507
|
+
transform-origin: left center;
|
|
508
|
+
transition: transform 120ms ease-out, opacity 120ms ease-out;
|
|
509
|
+
opacity: 0.75;
|
|
510
|
+
/* Reset button defaults. */
|
|
511
|
+
border: none;
|
|
512
|
+
padding: 0;
|
|
513
|
+
margin: 0;
|
|
514
|
+
font: inherit;
|
|
515
|
+
color: inherit;
|
|
516
|
+
background-clip: padding-box;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.wre-scope-rail-stripe:hover,
|
|
520
|
+
.wre-scope-rail-stripe:focus-visible {
|
|
521
|
+
transform: scaleX(1.5);
|
|
522
|
+
opacity: 1;
|
|
523
|
+
outline: none;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.wre-scope-rail-stripe-active {
|
|
527
|
+
opacity: 1;
|
|
528
|
+
transform: scaleX(1.75);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.wre-scope-rail-stripe.wre-scope-rail-label-accent { color: var(--color-accent); }
|
|
532
|
+
.wre-scope-rail-stripe.wre-scope-rail-label-warning { color: var(--color-warning); }
|
|
533
|
+
.wre-scope-rail-stripe.wre-scope-rail-label-insert { color: var(--color-insert); }
|
|
534
|
+
.wre-scope-rail-stripe.wre-scope-rail-label-secondary { color: var(--color-secondary); }
|
|
535
|
+
.wre-scope-rail-stripe.wre-scope-rail-label-danger { color: var(--color-danger); }
|
|
536
|
+
|
|
537
|
+
/*
|
|
538
|
+
* ─── Scope rail label pill ───
|
|
539
|
+
*
|
|
540
|
+
* Shown only on stripe hover (CSS-driven). The pill overlays the
|
|
541
|
+
* stripe with icon + short posture label, anchored to the first line
|
|
542
|
+
* of the scope.
|
|
543
|
+
*/
|
|
455
544
|
.wre-scope-rail-label {
|
|
456
545
|
position: absolute;
|
|
457
546
|
display: flex;
|
|
458
|
-
flex-direction: column;
|
|
459
547
|
align-items: center;
|
|
460
548
|
justify-content: center;
|
|
461
|
-
gap: 0.
|
|
462
|
-
padding: 0.
|
|
463
|
-
border-radius:
|
|
549
|
+
gap: 0.2rem;
|
|
550
|
+
padding: 0.15rem 0.3rem;
|
|
551
|
+
border-radius: var(--radius-sm);
|
|
464
552
|
border: 1px solid transparent;
|
|
465
553
|
background: var(--color-canvas, #fff);
|
|
466
|
-
box-shadow:
|
|
467
|
-
font-size:
|
|
554
|
+
box-shadow: var(--shadow-sm);
|
|
555
|
+
font-size: 9.5px;
|
|
468
556
|
line-height: 1;
|
|
469
557
|
text-transform: uppercase;
|
|
470
|
-
letter-spacing: 0.
|
|
558
|
+
letter-spacing: 0.06em;
|
|
471
559
|
font-weight: 600;
|
|
472
560
|
cursor: pointer;
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
561
|
+
z-index: 2;
|
|
562
|
+
opacity: 0;
|
|
563
|
+
pointer-events: none;
|
|
564
|
+
transition: opacity 140ms ease-out, transform 140ms ease-out;
|
|
565
|
+
transform: translateX(-4px);
|
|
476
566
|
}
|
|
477
567
|
|
|
478
|
-
.wre-scope-rail-
|
|
479
|
-
|
|
480
|
-
|
|
568
|
+
.wre-scope-rail-stripe:hover + .wre-scope-rail-label,
|
|
569
|
+
.wre-scope-rail-label:hover,
|
|
570
|
+
.wre-scope-rail-stripe:focus-visible + .wre-scope-rail-label {
|
|
571
|
+
opacity: 1;
|
|
572
|
+
pointer-events: auto;
|
|
573
|
+
transform: translateX(0);
|
|
481
574
|
}
|
|
482
575
|
|
|
483
576
|
.wre-scope-rail-label-accent {
|
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
import type { EditorCommandBag } from "../ui/editor-command-bag.ts";
|
|
68
68
|
import { preserveEditorSelectionMouseDown } from "../ui/headless/preserve-editor-selection";
|
|
69
69
|
import { TwAlertBanner } from "./chrome/tw-alert-banner";
|
|
70
|
+
import { TwModeDock } from "./chrome/tw-mode-dock";
|
|
70
71
|
import { TwLayoutPanel } from "./chrome/tw-layout-panel";
|
|
71
72
|
import { TwPageRuler } from "./chrome/tw-page-ruler";
|
|
72
73
|
import {
|
|
@@ -129,6 +130,15 @@ export interface TwReviewWorkspaceProps {
|
|
|
129
130
|
searchLabel?: string;
|
|
130
131
|
helpLabel?: string;
|
|
131
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* Opt-in floating mode dock pinned to the bottom of the workspace.
|
|
135
|
+
* Hosts pass the derived label / icon / actions; defaults to hidden.
|
|
136
|
+
*/
|
|
137
|
+
modeDock?: {
|
|
138
|
+
label: string;
|
|
139
|
+
icon?: ReactNode;
|
|
140
|
+
actions?: readonly import("./chrome/tw-mode-dock").TwModeDockAction[];
|
|
141
|
+
};
|
|
132
142
|
document: ReactNode;
|
|
133
143
|
workspaceMode: WorkspaceMode;
|
|
134
144
|
zoomLevel?: ZoomLevel;
|
|
@@ -314,6 +324,25 @@ export interface TwReviewWorkspaceProps {
|
|
|
314
324
|
* re-renders with the new per-role action set.
|
|
315
325
|
*/
|
|
316
326
|
onEditorRoleChange?: (role: import("../api/public-types.ts").EditorRole) => void;
|
|
327
|
+
/**
|
|
328
|
+
* Scope card mode selector fired a mode change. Wire to the host's
|
|
329
|
+
* existing overlay-apply path (or an equivalent CCEP workflow
|
|
330
|
+
* endpoint). The card never mutates runtime state directly.
|
|
331
|
+
*/
|
|
332
|
+
onScopeModeChangeRequested?: (payload: {
|
|
333
|
+
scopeId: string;
|
|
334
|
+
mode: import("../api/public-types.ts").WorkflowScopeMode;
|
|
335
|
+
}) => void;
|
|
336
|
+
/**
|
|
337
|
+
* Scope card issue row fired an action (resolve/waive/escalate/
|
|
338
|
+
* acknowledge). Host updates the attached `IssueMetadataValue`
|
|
339
|
+
* state and re-pushes via `setWorkflowMetadataEntries`.
|
|
340
|
+
*/
|
|
341
|
+
onScopeIssueActionRequested?: (payload: {
|
|
342
|
+
scopeId: string;
|
|
343
|
+
issueId: string;
|
|
344
|
+
action: import("../api/public-types.ts").ScopeIssueAction;
|
|
345
|
+
}) => void;
|
|
317
346
|
}
|
|
318
347
|
|
|
319
348
|
export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
@@ -328,6 +357,40 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
328
357
|
const markupDisplay = props.markupDisplay;
|
|
329
358
|
const [navOpen, setNavOpen] = useState(false);
|
|
330
359
|
const [layoutToolsOpen, setLayoutToolsOpen] = useState(false);
|
|
360
|
+
|
|
361
|
+
// Scope card state — tracks which scope's card is currently open so
|
|
362
|
+
// the ChromeOverlay's card layer renders the right one. The card
|
|
363
|
+
// closes on click-outside, Escape, or a repeat click on its stripe.
|
|
364
|
+
const [activeScopeId, setActiveScopeId] = useState<string | null>(null);
|
|
365
|
+
const handleScopeStripeClick = useCallback(
|
|
366
|
+
(segment: { scopeId: string }) => {
|
|
367
|
+
setActiveScopeId((current) =>
|
|
368
|
+
current === segment.scopeId ? null : segment.scopeId,
|
|
369
|
+
);
|
|
370
|
+
},
|
|
371
|
+
[],
|
|
372
|
+
);
|
|
373
|
+
const handleScopeCardClose = useCallback(() => {
|
|
374
|
+
setActiveScopeId(null);
|
|
375
|
+
}, []);
|
|
376
|
+
const onScopeModeChangeRequested = props.onScopeModeChangeRequested;
|
|
377
|
+
const handleScopeCardModeChange = useCallback(
|
|
378
|
+
(scopeId: string, mode: import("../api/public-types.ts").WorkflowScopeMode) => {
|
|
379
|
+
onScopeModeChangeRequested?.({ scopeId, mode });
|
|
380
|
+
},
|
|
381
|
+
[onScopeModeChangeRequested],
|
|
382
|
+
);
|
|
383
|
+
const onScopeIssueActionRequested = props.onScopeIssueActionRequested;
|
|
384
|
+
const handleScopeCardIssueAction = useCallback(
|
|
385
|
+
(
|
|
386
|
+
scopeId: string,
|
|
387
|
+
issueId: string,
|
|
388
|
+
action: import("../api/public-types.ts").ScopeIssueAction,
|
|
389
|
+
) => {
|
|
390
|
+
onScopeIssueActionRequested?.({ scopeId, issueId, action });
|
|
391
|
+
},
|
|
392
|
+
[onScopeIssueActionRequested],
|
|
393
|
+
);
|
|
331
394
|
const zoomLevel = props.zoomLevel ?? 100;
|
|
332
395
|
const zoomScale = typeof zoomLevel === "number" ? zoomLevel / 100 : 1;
|
|
333
396
|
const pageZoomBucket =
|
|
@@ -1144,6 +1207,11 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
1144
1207
|
tableContext={props.tableContext}
|
|
1145
1208
|
onSetColumnWidth={props.onSetColumnWidth}
|
|
1146
1209
|
onSetRowHeight={props.onSetRowHeight}
|
|
1210
|
+
activeScopeId={activeScopeId}
|
|
1211
|
+
onScopeStripeClick={handleScopeStripeClick}
|
|
1212
|
+
onScopeCardClose={handleScopeCardClose}
|
|
1213
|
+
onScopeCardModeChange={handleScopeCardModeChange}
|
|
1214
|
+
onScopeCardIssueAction={handleScopeCardIssueAction}
|
|
1147
1215
|
/>
|
|
1148
1216
|
) : null}
|
|
1149
1217
|
</div>
|
|
@@ -1280,6 +1348,13 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
1280
1348
|
</div>
|
|
1281
1349
|
) : null}
|
|
1282
1350
|
</div>
|
|
1351
|
+
{props.modeDock ? (
|
|
1352
|
+
<TwModeDock
|
|
1353
|
+
label={props.modeDock.label}
|
|
1354
|
+
icon={props.modeDock.icon}
|
|
1355
|
+
actions={props.modeDock.actions}
|
|
1356
|
+
/>
|
|
1357
|
+
) : null}
|
|
1283
1358
|
</div>
|
|
1284
1359
|
</Tooltip.Provider>
|
|
1285
1360
|
);
|