@beyondwork/docx-react-component 1.0.71 → 1.0.73
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 +964 -75
- package/package.json +1 -1
- package/src/api/public-types.ts +280 -1
- package/src/api/v3/_create.ts +16 -1
- package/src/api/v3/_runtime-handle.ts +2 -0
- package/src/api/v3/ai/evaluate.ts +113 -0
- package/src/api/v3/ai/outline.ts +140 -0
- package/src/api/v3/ai/policy.ts +31 -0
- package/src/api/v3/ai/replacement.ts +8 -0
- package/src/api/v3/ai/review.ts +342 -0
- package/src/api/v3/ai/stats.ts +62 -0
- package/src/api/v3/runtime/viewport.ts +181 -0
- package/src/api/v3/runtime/workflow.ts +114 -1
- package/src/api/v3/ui/_types.ts +35 -0
- package/src/api/v3/ui/chrome-preset-model.ts +6 -0
- package/src/api/v3/ui/index.ts +1 -0
- package/src/api/v3/ui/viewport.ts +112 -0
- package/src/compare/diff-engine.ts +2 -0
- package/src/core/commands/formatting-commands.ts +1 -0
- package/src/core/commands/table-structure-commands.ts +1 -0
- package/src/core/state/editor-state.ts +49 -6
- package/src/io/export/serialize-footnotes.ts +6 -0
- package/src/io/export/serialize-headers-footers.ts +7 -0
- package/src/io/export/serialize-main-document.ts +20 -0
- package/src/io/export/serialize-paragraph-formatting.ts +34 -0
- package/src/io/export/split-review-boundaries.ts +1 -0
- package/src/io/normalize/normalize-text.ts +49 -2
- package/src/io/ooxml/parse-headers-footers.ts +31 -0
- package/src/io/ooxml/parse-main-document.ts +148 -7
- package/src/io/ooxml/parse-paragraph-formatting.ts +105 -0
- package/src/model/canonical-document.ts +401 -1
- package/src/runtime/formatting/formatting-context.ts +2 -1
- package/src/runtime/geometry/overlay-rects.ts +7 -10
- package/src/runtime/layout/layout-engine-version.ts +278 -1
- package/src/runtime/layout/paginated-layout-engine.ts +181 -8
- package/src/runtime/layout/resolved-formatting-state.ts +108 -13
- package/src/runtime/markdown-sanitizer.ts +21 -4
- package/src/runtime/render/render-kernel.ts +21 -1
- package/src/runtime/scopes/action-validation.ts +30 -4
- package/src/runtime/scopes/audit-bundle.ts +8 -0
- package/src/runtime/scopes/compiler-service.ts +1 -0
- package/src/runtime/scopes/enumerate-scopes.ts +61 -3
- package/src/runtime/scopes/replacement/apply.ts +50 -3
- package/src/runtime/scopes/scope-kinds/paragraph.ts +170 -7
- package/src/runtime/scopes/semantic-scope-types.ts +27 -0
- package/src/runtime/surface-projection.ts +77 -0
- package/src/runtime/workflow/coordinator.ts +3 -0
- package/src/runtime/workflow/scope-writer.ts +34 -0
- package/src/session/export/embedded-reconstitute.ts +37 -3
- package/src/session/import/embedded-offload.ts +26 -1
- package/src/session/import/loader-types.ts +18 -0
- package/src/session/import/loader.ts +2 -0
- package/src/shell/media-previews.ts +8 -6
- package/src/ui/WordReviewEditor.tsx +1 -0
- package/src/ui/editor-surface-controller.tsx +11 -0
- package/src/ui/headless/selection-helpers.ts +2 -2
- package/src/ui/runtime-shortcut-dispatch.ts +4 -4
- package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +22 -4
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +11 -11
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +5 -0
- package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +18 -1
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +22 -6
- package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +18 -1
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +98 -3
- package/src/ui-tailwind/editor-surface/pm-schema.ts +50 -4
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +6 -0
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +8 -1
- package/src/ui-tailwind/editor-surface/search-plugin.ts +2 -4
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +114 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +12 -4
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +29 -4
- package/src/ui-tailwind/index.ts +4 -2
- package/src/ui-tailwind/page-chrome-model.ts +5 -7
- package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +54 -34
- package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +4 -1
- package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +4 -1
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +10 -1
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +8 -1
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +11 -1
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +7 -1
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +139 -10
- package/src/ui-tailwind/review/comment-markdown-renderer.tsx +1 -1
- package/src/ui-tailwind/review-workspace/page-chrome.ts +4 -4
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +1 -1
- package/src/ui-tailwind/theme/editor-theme.css +15 -16
- package/src/ui-tailwind/tw-review-workspace.tsx +22 -14
|
@@ -4,15 +4,19 @@ import type {
|
|
|
4
4
|
SurfaceBlockSnapshot,
|
|
5
5
|
SurfaceInlineSegment,
|
|
6
6
|
} from "../../api/public-types.ts";
|
|
7
|
+
import type { MediaPreviewDescriptor } from "../editor-surface/pm-state-from-snapshot.ts";
|
|
7
8
|
import {
|
|
8
9
|
buildMarkerStyle,
|
|
9
10
|
buildParagraphStyle,
|
|
10
11
|
buildSegmentStyle,
|
|
12
|
+
computeTabWidthsInPoints,
|
|
11
13
|
hasStyleEntries,
|
|
12
14
|
headingClassList,
|
|
13
15
|
resolveHeadingLevel,
|
|
14
16
|
} from "../editor-surface/tw-page-block-view.helpers.ts";
|
|
15
17
|
|
|
18
|
+
const EMU_PER_PX = 9525;
|
|
19
|
+
|
|
16
20
|
// ---------------------------------------------------------------------------
|
|
17
21
|
// TwRegionBlockRenderer (P8.4)
|
|
18
22
|
//
|
|
@@ -37,7 +41,27 @@ import {
|
|
|
37
41
|
// Inline segment renderer — mirrors `tw-page-block-view`'s `renderSegment`.
|
|
38
42
|
// ---------------------------------------------------------------------------
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Fallback visual for image segments that cannot resolve real bytes
|
|
46
|
+
* (no preview in `mediaPreviews`, or `seg.state === "missing"`).
|
|
47
|
+
*
|
|
48
|
+
* - `"chip"` (default) — 48×32 gray placeholder. Valuable dev-mode signal;
|
|
49
|
+
* correct for the body renderer.
|
|
50
|
+
* - `"hidden"` — zero-size, `aria-hidden` span. Correct inside header/
|
|
51
|
+
* footer bands, where the chip geometry visibly disrupts layout.
|
|
52
|
+
*
|
|
53
|
+
* The DOM node (with `data-node-type="image"` + `data-state`) is still
|
|
54
|
+
* emitted under `"hidden"` so diagnostics + tests can still see that an
|
|
55
|
+
* image segment was present.
|
|
56
|
+
*/
|
|
57
|
+
export type RegionImageFallbackDisplay = "chip" | "hidden";
|
|
58
|
+
|
|
59
|
+
function renderSegment(
|
|
60
|
+
seg: SurfaceInlineSegment,
|
|
61
|
+
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
62
|
+
fallbackDisplay: RegionImageFallbackDisplay,
|
|
63
|
+
tabWidthsPt: Map<string, number>,
|
|
64
|
+
): React.ReactNode {
|
|
41
65
|
switch (seg.kind) {
|
|
42
66
|
case "text": {
|
|
43
67
|
const style = buildSegmentStyle(seg.marks, seg.markAttrs);
|
|
@@ -51,23 +75,83 @@ function renderSegment(seg: SurfaceInlineSegment): React.ReactNode {
|
|
|
51
75
|
</span>
|
|
52
76
|
);
|
|
53
77
|
}
|
|
54
|
-
case "tab":
|
|
78
|
+
case "tab": {
|
|
79
|
+
const widthPt = tabWidthsPt.get(seg.segmentId);
|
|
80
|
+
const tabStyle: React.CSSProperties =
|
|
81
|
+
typeof widthPt === "number"
|
|
82
|
+
? { display: "inline-block", width: `${widthPt}pt`, minWidth: "8px" }
|
|
83
|
+
: { display: "inline-block", width: "32px", minWidth: "8px" };
|
|
55
84
|
return (
|
|
56
85
|
<span
|
|
57
86
|
key={seg.segmentId}
|
|
58
87
|
data-node-type="tab"
|
|
59
|
-
style={
|
|
88
|
+
style={tabStyle}
|
|
60
89
|
>
|
|
61
90
|
{"\u00A0"}
|
|
62
91
|
</span>
|
|
63
92
|
);
|
|
93
|
+
}
|
|
64
94
|
case "hard_break":
|
|
65
95
|
return <br key={seg.segmentId} />;
|
|
66
|
-
case "image":
|
|
96
|
+
case "image": {
|
|
97
|
+
// §5.1 gap 3 — floating-anchor images are owned by the absolute
|
|
98
|
+
// floating-image overlay (`TwFloatingImageLayer`). Emitting them
|
|
99
|
+
// inline here would double-paint the CCEP header logo on every
|
|
100
|
+
// page. Skip entirely so only the overlay renders them.
|
|
101
|
+
if (seg.anchor?.display === "floating") {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
// Mirror body-renderer behavior (`pm-state-from-snapshot.ts` :500+):
|
|
105
|
+
// look up the preview via `seg.mediaId` and render a real <img> when
|
|
106
|
+
// available. Without a preview (or `state === "missing"`), fall back
|
|
107
|
+
// to the placeholder chip — a deliberate signal that the media part
|
|
108
|
+
// exists canonical-side but no bytes have been resolved yet.
|
|
109
|
+
const preview = mediaPreviews[seg.mediaId];
|
|
110
|
+
const widthEmu = preview?.widthEmu ?? seg.anchor?.extent.widthEmu;
|
|
111
|
+
const heightEmu = preview?.heightEmu ?? seg.anchor?.extent.heightEmu;
|
|
112
|
+
if (preview?.src && seg.state !== "missing") {
|
|
113
|
+
const widthPx = widthEmu
|
|
114
|
+
? Math.max(8, Math.round(widthEmu / EMU_PER_PX))
|
|
115
|
+
: undefined;
|
|
116
|
+
const heightPx = heightEmu
|
|
117
|
+
? Math.max(8, Math.round(heightEmu / EMU_PER_PX))
|
|
118
|
+
: undefined;
|
|
119
|
+
return (
|
|
120
|
+
<img
|
|
121
|
+
key={seg.segmentId}
|
|
122
|
+
src={preview.src}
|
|
123
|
+
alt={seg.altText ?? ""}
|
|
124
|
+
data-node-type="image"
|
|
125
|
+
data-media-id={seg.mediaId}
|
|
126
|
+
style={{
|
|
127
|
+
display: "inline-block",
|
|
128
|
+
verticalAlign: "middle",
|
|
129
|
+
...(widthPx ? { width: `${widthPx}px` } : {}),
|
|
130
|
+
...(heightPx ? { height: `${heightPx}px` } : {}),
|
|
131
|
+
maxWidth: "100%",
|
|
132
|
+
objectFit: "contain",
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (fallbackDisplay === "hidden") {
|
|
138
|
+
return (
|
|
139
|
+
<span
|
|
140
|
+
key={seg.segmentId}
|
|
141
|
+
data-node-type="image"
|
|
142
|
+
data-media-id={seg.mediaId}
|
|
143
|
+
data-state={seg.state}
|
|
144
|
+
aria-hidden="true"
|
|
145
|
+
style={{ display: "inline-block", width: 0, height: 0 }}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
67
149
|
return (
|
|
68
150
|
<span
|
|
69
151
|
key={seg.segmentId}
|
|
70
152
|
data-node-type="image"
|
|
153
|
+
data-media-id={seg.mediaId}
|
|
154
|
+
data-state={seg.state}
|
|
71
155
|
style={{
|
|
72
156
|
display: "inline-block",
|
|
73
157
|
width: "48px",
|
|
@@ -80,6 +164,7 @@ function renderSegment(seg: SurfaceInlineSegment): React.ReactNode {
|
|
|
80
164
|
title={seg.altText ?? "Image"}
|
|
81
165
|
/>
|
|
82
166
|
);
|
|
167
|
+
}
|
|
83
168
|
case "field_ref":
|
|
84
169
|
return (
|
|
85
170
|
<span
|
|
@@ -121,8 +206,12 @@ function renderSegment(seg: SurfaceInlineSegment): React.ReactNode {
|
|
|
121
206
|
|
|
122
207
|
function RegionParagraph({
|
|
123
208
|
block,
|
|
209
|
+
mediaPreviews,
|
|
210
|
+
fallbackDisplay,
|
|
124
211
|
}: {
|
|
125
212
|
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>;
|
|
213
|
+
mediaPreviews: Record<string, MediaPreviewDescriptor>;
|
|
214
|
+
fallbackDisplay: RegionImageFallbackDisplay;
|
|
126
215
|
}): React.ReactElement {
|
|
127
216
|
const headingLevel = resolveHeadingLevel(block.styleId, block.outlineLevel);
|
|
128
217
|
const classes: string[] = ["leading-relaxed"];
|
|
@@ -131,6 +220,7 @@ function RegionParagraph({
|
|
|
131
220
|
}
|
|
132
221
|
|
|
133
222
|
const pStyle = buildParagraphStyle(block);
|
|
223
|
+
const tabWidthsPt = computeTabWidthsInPoints(block);
|
|
134
224
|
|
|
135
225
|
// Numbering prefix span — matches tw-page-block-view so region content that
|
|
136
226
|
// happens to carry numbering (e.g. footnote bodies authored as lists) shows
|
|
@@ -195,7 +285,7 @@ function RegionParagraph({
|
|
|
195
285
|
<div {...attrs}>
|
|
196
286
|
{prefixSpan}
|
|
197
287
|
<span className="pm-paragraph-content">
|
|
198
|
-
{block.segments.map((seg) => renderSegment(seg))}
|
|
288
|
+
{block.segments.map((seg) => renderSegment(seg, mediaPreviews, fallbackDisplay, tabWidthsPt))}
|
|
199
289
|
</span>
|
|
200
290
|
</div>
|
|
201
291
|
);
|
|
@@ -203,8 +293,12 @@ function RegionParagraph({
|
|
|
203
293
|
|
|
204
294
|
function RegionTable({
|
|
205
295
|
block,
|
|
296
|
+
mediaPreviews,
|
|
297
|
+
fallbackDisplay,
|
|
206
298
|
}: {
|
|
207
299
|
block: Extract<SurfaceBlockSnapshot, { kind: "table" }>;
|
|
300
|
+
mediaPreviews: Record<string, MediaPreviewDescriptor>;
|
|
301
|
+
fallbackDisplay: RegionImageFallbackDisplay;
|
|
208
302
|
}): React.ReactElement {
|
|
209
303
|
const tableStyle: React.CSSProperties = {
|
|
210
304
|
borderCollapse: "collapse",
|
|
@@ -273,7 +367,12 @@ function RegionTable({
|
|
|
273
367
|
style={Object.keys(cellStyle).length > 0 ? cellStyle : undefined}
|
|
274
368
|
>
|
|
275
369
|
{cell.content.map((childBlock) => (
|
|
276
|
-
<RegionBlockItem
|
|
370
|
+
<RegionBlockItem
|
|
371
|
+
key={childBlock.blockId}
|
|
372
|
+
block={childBlock}
|
|
373
|
+
mediaPreviews={mediaPreviews}
|
|
374
|
+
fallbackDisplay={fallbackDisplay}
|
|
375
|
+
/>
|
|
277
376
|
))}
|
|
278
377
|
</td>
|
|
279
378
|
);
|
|
@@ -311,14 +410,18 @@ function RegionOpaque({
|
|
|
311
410
|
|
|
312
411
|
function RegionBlockItem({
|
|
313
412
|
block,
|
|
413
|
+
mediaPreviews,
|
|
414
|
+
fallbackDisplay,
|
|
314
415
|
}: {
|
|
315
416
|
block: SurfaceBlockSnapshot;
|
|
417
|
+
mediaPreviews: Record<string, MediaPreviewDescriptor>;
|
|
418
|
+
fallbackDisplay: RegionImageFallbackDisplay;
|
|
316
419
|
}): React.ReactElement | null {
|
|
317
420
|
switch (block.kind) {
|
|
318
421
|
case "paragraph":
|
|
319
|
-
return <RegionParagraph block={block} />;
|
|
422
|
+
return <RegionParagraph block={block} mediaPreviews={mediaPreviews} fallbackDisplay={fallbackDisplay} />;
|
|
320
423
|
case "table":
|
|
321
|
-
return <RegionTable block={block} />;
|
|
424
|
+
return <RegionTable block={block} mediaPreviews={mediaPreviews} fallbackDisplay={fallbackDisplay} />;
|
|
322
425
|
case "sdt_block":
|
|
323
426
|
return (
|
|
324
427
|
<section
|
|
@@ -328,7 +431,7 @@ function RegionBlockItem({
|
|
|
328
431
|
style={{ margin: "8px 0" }}
|
|
329
432
|
>
|
|
330
433
|
{block.children.map((child) => (
|
|
331
|
-
<RegionBlockItem key={child.blockId} block={child} />
|
|
434
|
+
<RegionBlockItem key={child.blockId} block={child} mediaPreviews={mediaPreviews} fallbackDisplay={fallbackDisplay} />
|
|
332
435
|
))}
|
|
333
436
|
</section>
|
|
334
437
|
);
|
|
@@ -348,8 +451,26 @@ export interface TwRegionBlockRendererProps {
|
|
|
348
451
|
blocks: readonly SurfaceBlockSnapshot[];
|
|
349
452
|
/** Optional class name applied to the root wrapper. */
|
|
350
453
|
className?: string;
|
|
454
|
+
/**
|
|
455
|
+
* Media preview catalog. Without it, any `kind: "image"` segment falls
|
|
456
|
+
* back to the 48×32 gray placeholder chip — the pre-existing behavior
|
|
457
|
+
* for the 7-of-8 CCEP docs whose headers carry a logo. Pass the same
|
|
458
|
+
* catalog the body renderer uses (`props.mediaPreviews` on the
|
|
459
|
+
* workspace root) to light up headers + footers + footnote bodies with
|
|
460
|
+
* real image bytes.
|
|
461
|
+
*/
|
|
462
|
+
mediaPreviews?: Record<string, MediaPreviewDescriptor>;
|
|
463
|
+
/**
|
|
464
|
+
* Behavior for image segments that can't resolve real bytes.
|
|
465
|
+
* Defaults to `"chip"` for back-compat with body-renderer parity;
|
|
466
|
+
* pass `"hidden"` inside header/footer bands where the 48×32 chip
|
|
467
|
+
* disrupts band geometry (§5.5 tuning-phase handover).
|
|
468
|
+
*/
|
|
469
|
+
fallbackDisplay?: RegionImageFallbackDisplay;
|
|
351
470
|
}
|
|
352
471
|
|
|
472
|
+
const EMPTY_MEDIA_PREVIEWS: Record<string, MediaPreviewDescriptor> = {};
|
|
473
|
+
|
|
353
474
|
/**
|
|
354
475
|
* TwRegionBlockRenderer — read-only React renderer for a region's
|
|
355
476
|
* `SurfaceBlockSnapshot[]`. Used by the header / footer / footnote /
|
|
@@ -363,9 +484,12 @@ export interface TwRegionBlockRendererProps {
|
|
|
363
484
|
export function TwRegionBlockRenderer({
|
|
364
485
|
blocks,
|
|
365
486
|
className,
|
|
487
|
+
mediaPreviews,
|
|
488
|
+
fallbackDisplay = "chip",
|
|
366
489
|
}: TwRegionBlockRendererProps): React.ReactElement {
|
|
367
490
|
const rootClasses = ["ProseMirror"];
|
|
368
491
|
if (className) rootClasses.push(className);
|
|
492
|
+
const previews = mediaPreviews ?? EMPTY_MEDIA_PREVIEWS;
|
|
369
493
|
return (
|
|
370
494
|
<div
|
|
371
495
|
className={rootClasses.join(" ")}
|
|
@@ -373,7 +497,12 @@ export function TwRegionBlockRenderer({
|
|
|
373
497
|
data-region-block-renderer=""
|
|
374
498
|
>
|
|
375
499
|
{blocks.map((block) => (
|
|
376
|
-
<RegionBlockItem
|
|
500
|
+
<RegionBlockItem
|
|
501
|
+
key={block.blockId}
|
|
502
|
+
block={block}
|
|
503
|
+
mediaPreviews={previews}
|
|
504
|
+
fallbackDisplay={fallbackDisplay}
|
|
505
|
+
/>
|
|
377
506
|
))}
|
|
378
507
|
</div>
|
|
379
508
|
);
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
CommentBody,
|
|
6
6
|
CommentMention,
|
|
7
7
|
} from "../../api/comment-presentation-types";
|
|
8
|
-
import { sanitizeMarkdown } from "../../
|
|
8
|
+
import { sanitizeMarkdown } from "../../api/public-types";
|
|
9
9
|
|
|
10
10
|
export interface CommentMarkdownRendererProps {
|
|
11
11
|
body: CommentBody;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { CSSProperties } from "react";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
DocumentNavigationSnapshot,
|
|
5
|
-
RuntimeRenderSnapshot,
|
|
3
|
+
import {
|
|
4
|
+
type DocumentNavigationSnapshot,
|
|
5
|
+
type RuntimeRenderSnapshot,
|
|
6
|
+
DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP,
|
|
6
7
|
} from "../../api/public-types";
|
|
7
|
-
import { DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP } from "../../runtime/page-layout-estimation.ts";
|
|
8
8
|
import { computeLineMarkersIfEnabled } from "../page-chrome-model.ts";
|
|
9
9
|
|
|
10
10
|
export interface PageChromeModel {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect } from "react";
|
|
2
2
|
import type { Dispatch, SetStateAction } from "react";
|
|
3
3
|
|
|
4
|
-
import { createCanvasBackend } from "../../
|
|
4
|
+
import { createCanvasBackend } from "../../api/public-types.ts";
|
|
5
5
|
import type { RuntimeRenderSnapshot } from "../../api/public-types.ts";
|
|
6
6
|
import {
|
|
7
7
|
incrementInvalidationCounter,
|
|
@@ -1058,30 +1058,29 @@
|
|
|
1058
1058
|
* `data-story-active="header|footer"` and the body PM surface dims to
|
|
1059
1059
|
* 0.65 so the active band reads as the focal surface.
|
|
1060
1060
|
*/
|
|
1061
|
+
/*
|
|
1062
|
+
* Designsystem §6.20 — inactive = low-contrast tint (color.bg.muted),
|
|
1063
|
+
* active = tint color.accent.soft + border color.border.accent.
|
|
1064
|
+
* The 3px top accent stripe was the prior signal; §6.20 rejects it in
|
|
1065
|
+
* favor of a border/tint-led active state that reads as "a focal frame"
|
|
1066
|
+
* rather than "a colored line draped across the top".
|
|
1067
|
+
*/
|
|
1061
1068
|
.wre-page-band {
|
|
1062
|
-
|
|
1063
|
-
|
|
1069
|
+
background-color: var(--color-bg-muted);
|
|
1070
|
+
border: 1px solid transparent;
|
|
1071
|
+
transition:
|
|
1072
|
+
background-color var(--motion-fast, 120ms) ease-out,
|
|
1073
|
+
border-color var(--motion-fast, 120ms) ease-out;
|
|
1064
1074
|
pointer-events: auto;
|
|
1065
1075
|
}
|
|
1066
1076
|
|
|
1067
1077
|
.wre-page-band:hover {
|
|
1068
|
-
|
|
1078
|
+
background-color: color-mix(in srgb, var(--color-bg-muted) 80%, var(--color-surface));
|
|
1069
1079
|
}
|
|
1070
1080
|
|
|
1071
1081
|
.wre-page-band[data-active="true"] {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
.wre-page-band[data-active="true"]::before {
|
|
1076
|
-
content: "";
|
|
1077
|
-
position: absolute;
|
|
1078
|
-
top: 0;
|
|
1079
|
-
left: 0;
|
|
1080
|
-
right: 0;
|
|
1081
|
-
height: 3px;
|
|
1082
|
-
background: var(--color-accent);
|
|
1083
|
-
border-radius: 0 0 var(--radius-pill) var(--radius-pill);
|
|
1084
|
-
pointer-events: none;
|
|
1082
|
+
background-color: var(--color-accent-soft);
|
|
1083
|
+
border: 1px solid var(--color-border-accent);
|
|
1085
1084
|
}
|
|
1086
1085
|
|
|
1087
1086
|
.wre-page-band__label {
|
|
@@ -351,21 +351,28 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
351
351
|
const defaultShellActiveMode: ShellHeaderMode = editorRoleToShellMode(
|
|
352
352
|
viewState.editorRole,
|
|
353
353
|
);
|
|
354
|
+
// coord-11 §21 — host-supplied shellHeader wins; otherwise the default
|
|
355
|
+
// TwShellHeader mounts only when `chromeVisibility.shellHeader === true`
|
|
356
|
+
// (default for every preset except `selection`). The `selection` preset
|
|
357
|
+
// is intended for minimal embeds (incl. visual-fidelity `chrome=none`)
|
|
358
|
+
// and must not paint a workspace mode-tab header.
|
|
354
359
|
const renderedShell =
|
|
355
|
-
props.shellHeader !== undefined
|
|
356
|
-
props.shellHeader
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
360
|
+
props.shellHeader !== undefined
|
|
361
|
+
? props.shellHeader
|
|
362
|
+
: chromeVisibility.shellHeader
|
|
363
|
+
? (
|
|
364
|
+
<TwShellHeader
|
|
365
|
+
modes={defaultShellModes}
|
|
366
|
+
activeMode={defaultShellActiveMode}
|
|
367
|
+
onModeChange={(mode) => {
|
|
368
|
+
const nextRole = shellModeToEditorRole(mode);
|
|
369
|
+
if (nextRole !== null && nextRole !== viewState.editorRole) {
|
|
370
|
+
props.onEditorRoleChange?.(nextRole);
|
|
371
|
+
}
|
|
372
|
+
}}
|
|
373
|
+
/>
|
|
374
|
+
)
|
|
375
|
+
: null;
|
|
369
376
|
|
|
370
377
|
// Audit §2.5 — context band mounts as a composition-level sibling of
|
|
371
378
|
// the toolbar so the workspace row becomes
|
|
@@ -1030,6 +1037,7 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
1030
1037
|
onOpenStory={props.onOpenStory}
|
|
1031
1038
|
pmSurfaceElement={pmSurfaceElement}
|
|
1032
1039
|
visiblePageIndexRange={visiblePageIndexRange}
|
|
1040
|
+
mediaPreviews={props.mediaPreviews}
|
|
1033
1041
|
/>
|
|
1034
1042
|
) : null}
|
|
1035
1043
|
</div>
|