@beyondwork/docx-react-component 1.0.96 → 1.0.97
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 +33 -19
- package/src/api/v3/ui/_types.ts +11 -21
- package/src/api/v3/ui/chrome.ts +8 -9
- package/src/api/v3/ui/debug.ts +15 -77
- package/src/api/v3/ui/overlays-visibility.ts +9 -10
- package/src/api/v3/ui/overlays.ts +8 -75
- package/src/io/ooxml/parse-main-document.ts +30 -0
- package/src/io/ooxml/parse-picture.ts +14 -0
- package/src/io/ooxml/parse-shapes.ts +41 -1
- package/src/model/canonical-document.ts +17 -0
- package/src/runtime/layout/layout-engine-version.ts +8 -1
- package/src/runtime/layout/page-story-resolver.ts +1 -0
- package/src/runtime/layout/paginated-layout-engine.ts +26 -10
- package/src/runtime/surface-projection.ts +114 -12
- package/src/ui/WordReviewEditor.tsx +6 -10
- package/src/ui/editor-command-bag.ts +2 -0
- package/src/ui/ui-controller-factory.ts +2 -2
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +11 -25
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +2 -2
- package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +22 -220
- package/src/ui-tailwind/debug/README.md +12 -50
- package/src/ui-tailwind/debug/tw-debug-overlay.tsx +6 -6
- package/src/ui-tailwind/debug/tw-debug-presentation.tsx +9 -20
- package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +5 -6
- package/src/ui-tailwind/editor-surface/chart-node-view.tsx +1 -4
- package/src/ui-tailwind/editor-surface/picture-effects.ts +96 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +89 -62
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +205 -0
- package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +190 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +53 -53
- package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +83 -20
- package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +114 -4
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +5 -0
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +3 -0
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +3 -0
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +8 -0
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +26 -0
- package/src/ui-tailwind/theme/editor-theme.css +18 -11
- package/src/ui-tailwind/tw-review-workspace.tsx +15 -0
|
@@ -18,8 +18,15 @@ import {
|
|
|
18
18
|
} from "../chrome-overlay/tw-page-stack-overlay-layer.tsx";
|
|
19
19
|
import {
|
|
20
20
|
collectFloatingImageOverlayItems,
|
|
21
|
+
type FloatingImageOverlayItem,
|
|
21
22
|
type FloatingImagePreviewDescriptor,
|
|
22
23
|
} from "./floating-image-overlay-model.ts";
|
|
24
|
+
import { buildSegmentStyle } from "../editor-surface/tw-page-block-view.helpers.ts";
|
|
25
|
+
import { buildPictureFilterCss } from "../editor-surface/picture-effects.ts";
|
|
26
|
+
|
|
27
|
+
const EMU_PER_PX = 9525;
|
|
28
|
+
type FloatingShapeBody = NonNullable<FloatingImageOverlayItem["shape"]>["body"];
|
|
29
|
+
type FloatingShapeBodyAnchor = NonNullable<FloatingShapeBody>["anchor"];
|
|
23
30
|
|
|
24
31
|
export interface TwFloatingImageLayerProps {
|
|
25
32
|
facet: WordReviewEditorLayoutFacet;
|
|
@@ -221,9 +228,9 @@ export const TwFloatingImageLayer: React.FC<TwFloatingImageLayerProps> = ({
|
|
|
221
228
|
data-floating-image-count={items.length}
|
|
222
229
|
>
|
|
223
230
|
{items.map((item) => {
|
|
224
|
-
const interactive =
|
|
231
|
+
const interactive =
|
|
232
|
+
!item.shape && plane === "front" && typeof onActivateFloatingImage === "function";
|
|
225
233
|
const commonProps = {
|
|
226
|
-
key: item.key,
|
|
227
234
|
className: interactive
|
|
228
235
|
? "pointer-events-auto absolute m-0 border-0 bg-transparent p-0"
|
|
229
236
|
: "pointer-events-none absolute m-0 border-0 bg-transparent p-0",
|
|
@@ -234,9 +241,21 @@ export const TwFloatingImageLayer: React.FC<TwFloatingImageLayerProps> = ({
|
|
|
234
241
|
left: `${item.leftPx}px`,
|
|
235
242
|
width: `${item.widthPx}px`,
|
|
236
243
|
height: `${item.heightPx}px`,
|
|
244
|
+
...(item.zIndex !== undefined ? { zIndex: item.zIndex } : {}),
|
|
237
245
|
},
|
|
238
246
|
} as const;
|
|
239
|
-
const
|
|
247
|
+
const imageFilter = buildPictureFilterCss(item.pictureEffects);
|
|
248
|
+
const content = item.shape ? (
|
|
249
|
+
<div
|
|
250
|
+
className="flex h-full w-full overflow-hidden"
|
|
251
|
+
title={item.detail ?? item.shape.label}
|
|
252
|
+
style={buildFloatingShapeBoxStyle(item)}
|
|
253
|
+
>
|
|
254
|
+
<span style={buildFloatingShapeTextStyle(item)}>
|
|
255
|
+
{item.shape.text}
|
|
256
|
+
</span>
|
|
257
|
+
</div>
|
|
258
|
+
) : item.src ? (
|
|
240
259
|
<img
|
|
241
260
|
src={item.src}
|
|
242
261
|
alt={interactive ? item.altText ?? "" : ""}
|
|
@@ -247,6 +266,8 @@ export const TwFloatingImageLayer: React.FC<TwFloatingImageLayerProps> = ({
|
|
|
247
266
|
width: "100%",
|
|
248
267
|
height: "100%",
|
|
249
268
|
objectFit: "fill",
|
|
269
|
+
...(imageFilter ? { filter: imageFilter } : {}),
|
|
270
|
+
...(item.pictureEffects?.srcRect ? { clipPath: srcRectToInset(item.pictureEffects.srcRect) } : {}),
|
|
250
271
|
}}
|
|
251
272
|
/>
|
|
252
273
|
) : (
|
|
@@ -259,13 +280,14 @@ export const TwFloatingImageLayer: React.FC<TwFloatingImageLayerProps> = ({
|
|
|
259
280
|
);
|
|
260
281
|
if (!interactive) {
|
|
261
282
|
return (
|
|
262
|
-
<div {...commonProps} aria-hidden="true">
|
|
283
|
+
<div key={item.key} {...commonProps} aria-hidden="true">
|
|
263
284
|
{content}
|
|
264
285
|
</div>
|
|
265
286
|
);
|
|
266
287
|
}
|
|
267
288
|
return (
|
|
268
289
|
<button
|
|
290
|
+
key={item.key}
|
|
269
291
|
{...commonProps}
|
|
270
292
|
type="button"
|
|
271
293
|
tabIndex={-1}
|
|
@@ -287,4 +309,92 @@ export const TwFloatingImageLayer: React.FC<TwFloatingImageLayerProps> = ({
|
|
|
287
309
|
);
|
|
288
310
|
};
|
|
289
311
|
|
|
312
|
+
function buildFloatingShapeBoxStyle(
|
|
313
|
+
item: FloatingImageOverlayItem,
|
|
314
|
+
): React.CSSProperties {
|
|
315
|
+
const fill = item.shape?.fill;
|
|
316
|
+
const line = item.shape?.line;
|
|
317
|
+
const style: React.CSSProperties = {
|
|
318
|
+
boxSizing: "border-box",
|
|
319
|
+
alignItems: bodyAnchorToAlignItems(item.shape?.body?.anchor),
|
|
320
|
+
justifyContent: inferTextBoxJustification(item.shape?.text),
|
|
321
|
+
padding: shapeBodyPadding(item.shape?.body),
|
|
322
|
+
textAlign: inferTextBoxTextAlign(item.shape?.text),
|
|
323
|
+
backgroundColor: "transparent",
|
|
324
|
+
border: "none",
|
|
325
|
+
};
|
|
326
|
+
if (fill?.kind === "solid" && fill.colorType === "srgbClr" && isSafeHexColor(fill.color)) {
|
|
327
|
+
style.backgroundColor = `#${fill.color}`;
|
|
328
|
+
}
|
|
329
|
+
if (line && !line.noLine && line.color && isSafeHexColor(line.color)) {
|
|
330
|
+
style.border = `${Math.max(1, Math.round((line.widthEmu ?? 9525) / EMU_PER_PX))}px solid #${line.color}`;
|
|
331
|
+
}
|
|
332
|
+
return style;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function bodyAnchorToAlignItems(
|
|
336
|
+
anchor: FloatingShapeBodyAnchor,
|
|
337
|
+
): React.CSSProperties["alignItems"] {
|
|
338
|
+
switch (anchor) {
|
|
339
|
+
case "t":
|
|
340
|
+
return "flex-start";
|
|
341
|
+
case "b":
|
|
342
|
+
return "flex-end";
|
|
343
|
+
case "ctr":
|
|
344
|
+
default:
|
|
345
|
+
return "center";
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function shapeBodyPadding(
|
|
350
|
+
body: FloatingShapeBody,
|
|
351
|
+
): string {
|
|
352
|
+
const top = emuInsetToPx(body?.insetTopEmu, 0);
|
|
353
|
+
const right = emuInsetToPx(body?.insetRightEmu, 4);
|
|
354
|
+
const bottom = emuInsetToPx(body?.insetBottomEmu, 0);
|
|
355
|
+
const left = emuInsetToPx(body?.insetLeftEmu, 4);
|
|
356
|
+
return `${top}px ${right}px ${bottom}px ${left}px`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function emuInsetToPx(value: number | undefined, fallbackPx: number): number {
|
|
360
|
+
return value === undefined ? fallbackPx : Math.max(0, Math.round(value / EMU_PER_PX));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function buildFloatingShapeTextStyle(
|
|
364
|
+
item: FloatingImageOverlayItem,
|
|
365
|
+
): React.CSSProperties {
|
|
366
|
+
const style = buildSegmentStyle(item.shape?.marks, item.shape?.markAttrs);
|
|
367
|
+
if (item.shape?.markAttrs?.fontSize) {
|
|
368
|
+
style.fontSize = `${item.shape.markAttrs.fontSize / 2}pt`;
|
|
369
|
+
}
|
|
370
|
+
const rawColor = item.shape?.markAttrs?.textColor;
|
|
371
|
+
if (rawColor && isSafeHexColor(rawColor)) {
|
|
372
|
+
style.color = `#${rawColor}`;
|
|
373
|
+
}
|
|
374
|
+
if (!style.fontSize) {
|
|
375
|
+
style.fontSize = `${Math.max(12, Math.min(56, Math.round(item.heightPx * 0.42)))}px`;
|
|
376
|
+
}
|
|
377
|
+
if (!style.lineHeight) {
|
|
378
|
+
style.lineHeight = 1.05;
|
|
379
|
+
}
|
|
380
|
+
style.whiteSpace = "pre-wrap";
|
|
381
|
+
return style;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function srcRectToInset(srcRect: { top: number; right: number; bottom: number; left: number }): string {
|
|
385
|
+
return `inset(${(srcRect.top / 1000).toFixed(3)}% ${(srcRect.right / 1000).toFixed(3)}% ${(srcRect.bottom / 1000).toFixed(3)}% ${(srcRect.left / 1000).toFixed(3)}%)`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function inferTextBoxJustification(text: string | undefined): React.CSSProperties["justifyContent"] {
|
|
389
|
+
return text?.startsWith("[") ? "flex-end" : "center";
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function inferTextBoxTextAlign(text: string | undefined): React.CSSProperties["textAlign"] {
|
|
393
|
+
return text?.startsWith("[") ? "right" : "center";
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function isSafeHexColor(value: string): boolean {
|
|
397
|
+
return /^[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/.test(value);
|
|
398
|
+
}
|
|
399
|
+
|
|
290
400
|
export default TwFloatingImageLayer;
|
|
@@ -38,6 +38,7 @@ export interface TwPageChromeEntryProps {
|
|
|
38
38
|
activeStoryPageIndex?: number | null;
|
|
39
39
|
onOpenStory?: (target: EditorStoryTarget, pageIndex: number) => void;
|
|
40
40
|
visiblePageIndexRange?: { start: number; end: number } | null;
|
|
41
|
+
plainPageBands?: boolean;
|
|
41
42
|
renderFrameRevision: number;
|
|
42
43
|
/** Preview catalog threaded into header/footer/footnote region renderers
|
|
43
44
|
* so images (CCEP logos on 7-of-8 CCEP docs) render as real <img>s. */
|
|
@@ -62,6 +63,7 @@ function TwPageChromeEntryInner({
|
|
|
62
63
|
activeStoryPageIndex,
|
|
63
64
|
onOpenStory,
|
|
64
65
|
visiblePageIndexRange,
|
|
66
|
+
plainPageBands = false,
|
|
65
67
|
renderFrameRevision,
|
|
66
68
|
mediaPreviews,
|
|
67
69
|
activeBandRibbonProps,
|
|
@@ -208,6 +210,7 @@ function TwPageChromeEntryInner({
|
|
|
208
210
|
widthPx={bandWidthPx}
|
|
209
211
|
bandHeightPx={px(headerRegion.heightTwips)}
|
|
210
212
|
isActiveSlot={Boolean(headerActive)}
|
|
213
|
+
chromeMode={plainPageBands ? "plain" : "normal"}
|
|
211
214
|
sectionLabel={headerActive ? headerSectionLabel : undefined}
|
|
212
215
|
onDoubleClick={handleHeaderDoubleClick}
|
|
213
216
|
mediaPreviews={mediaPreviews}
|
|
@@ -223,6 +226,7 @@ function TwPageChromeEntryInner({
|
|
|
223
226
|
widthPx={bandWidthPx}
|
|
224
227
|
bandHeightPx={px(footerRegion.heightTwips)}
|
|
225
228
|
isActiveSlot={Boolean(footerActive)}
|
|
229
|
+
chromeMode={plainPageBands ? "plain" : "normal"}
|
|
226
230
|
sectionLabel={footerActive ? footerSectionLabel : undefined}
|
|
227
231
|
onDoubleClick={handleFooterDoubleClick}
|
|
228
232
|
mediaPreviews={mediaPreviews}
|
|
@@ -267,6 +271,7 @@ function propsAreEqual(
|
|
|
267
271
|
prev.activeStoryPageIndex === next.activeStoryPageIndex &&
|
|
268
272
|
prev.onOpenStory === next.onOpenStory &&
|
|
269
273
|
prev.visiblePageIndexRange === next.visiblePageIndexRange &&
|
|
274
|
+
prev.plainPageBands === next.plainPageBands &&
|
|
270
275
|
prev.renderFrameRevision === next.renderFrameRevision &&
|
|
271
276
|
prev.rect.topPx === next.rect.topPx &&
|
|
272
277
|
prev.rect.bottomPx === next.rect.bottomPx &&
|
|
@@ -32,6 +32,7 @@ export interface TwPageFooterBandProps {
|
|
|
32
32
|
widthPx: number;
|
|
33
33
|
/** True when this band is the active-story slot. Renders portal target instead of read-only DOM. */
|
|
34
34
|
isActiveSlot: boolean;
|
|
35
|
+
chromeMode?: "normal" | "plain";
|
|
35
36
|
/**
|
|
36
37
|
* Lane 6d.U1 — section label for the active-band ribbon (e.g. "Footer — Section 1").
|
|
37
38
|
* Only rendered when `isActiveSlot` is true.
|
|
@@ -56,6 +57,7 @@ export const TwPageFooterBand: React.FC<TwPageFooterBandProps> = React.memo(({
|
|
|
56
57
|
leftPx,
|
|
57
58
|
widthPx,
|
|
58
59
|
isActiveSlot,
|
|
60
|
+
chromeMode = "normal",
|
|
59
61
|
sectionLabel,
|
|
60
62
|
onDoubleClick,
|
|
61
63
|
"data-testid": testId,
|
|
@@ -66,6 +68,7 @@ export const TwPageFooterBand: React.FC<TwPageFooterBandProps> = React.memo(({
|
|
|
66
68
|
<div
|
|
67
69
|
className="wre-page-band"
|
|
68
70
|
data-page-band="footer"
|
|
71
|
+
data-page-band-chrome={chromeMode === "plain" ? "plain" : undefined}
|
|
69
72
|
data-page-index={pageIndex}
|
|
70
73
|
data-active={isActiveSlot ? "true" : undefined}
|
|
71
74
|
data-testid={testId}
|
|
@@ -33,6 +33,7 @@ export interface TwPageHeaderBandProps {
|
|
|
33
33
|
widthPx: number;
|
|
34
34
|
/** True when this band is the active-story slot. Renders portal target instead of read-only DOM. */
|
|
35
35
|
isActiveSlot: boolean;
|
|
36
|
+
chromeMode?: "normal" | "plain";
|
|
36
37
|
/**
|
|
37
38
|
* Lane 6d.U1 — section label for the active-band ribbon (e.g. "Header — Section 1").
|
|
38
39
|
* Only rendered when `isActiveSlot` is true.
|
|
@@ -62,6 +63,7 @@ export const TwPageHeaderBand: React.FC<TwPageHeaderBandProps> = React.memo(({
|
|
|
62
63
|
leftPx,
|
|
63
64
|
widthPx,
|
|
64
65
|
isActiveSlot,
|
|
66
|
+
chromeMode = "normal",
|
|
65
67
|
sectionLabel,
|
|
66
68
|
onDoubleClick,
|
|
67
69
|
"data-testid": testId,
|
|
@@ -72,6 +74,7 @@ export const TwPageHeaderBand: React.FC<TwPageHeaderBandProps> = React.memo(({
|
|
|
72
74
|
<div
|
|
73
75
|
className="wre-page-band"
|
|
74
76
|
data-page-band="header"
|
|
77
|
+
data-page-band-chrome={chromeMode === "plain" ? "plain" : undefined}
|
|
75
78
|
data-page-index={pageIndex}
|
|
76
79
|
data-active={isActiveSlot ? "true" : undefined}
|
|
77
80
|
data-testid={testId}
|
|
@@ -148,6 +148,11 @@ export interface TwPageStackChromeLayerProps {
|
|
|
148
148
|
* reported.
|
|
149
149
|
*/
|
|
150
150
|
visiblePageIndexRange?: { start: number; end: number } | null;
|
|
151
|
+
/**
|
|
152
|
+
* Chrome-less visual fidelity mode: keep per-page story rendering active
|
|
153
|
+
* while suppressing editor-only inactive band tint/border.
|
|
154
|
+
*/
|
|
155
|
+
plainPageBands?: boolean;
|
|
151
156
|
/** Optional test id applied to the layer root. */
|
|
152
157
|
"data-testid"?: string;
|
|
153
158
|
/** Preview catalog threaded through to per-page chrome bands so images
|
|
@@ -173,6 +178,7 @@ const TwPageStackChromeLayerInner: React.FC<TwPageStackChromeLayerProps> = ({
|
|
|
173
178
|
pmSurfaceElement,
|
|
174
179
|
pmView,
|
|
175
180
|
visiblePageIndexRange,
|
|
181
|
+
plainPageBands = false,
|
|
176
182
|
"data-testid": testId,
|
|
177
183
|
mediaPreviews,
|
|
178
184
|
activeBandRibbonProps,
|
|
@@ -461,6 +467,7 @@ const TwPageStackChromeLayerInner: React.FC<TwPageStackChromeLayerProps> = ({
|
|
|
461
467
|
activeStoryPageIndex={activeStoryPageIndex}
|
|
462
468
|
onOpenStory={handleOpenStoryForPage}
|
|
463
469
|
visiblePageIndexRange={visiblePageIndexRange}
|
|
470
|
+
plainPageBands={plainPageBands}
|
|
464
471
|
renderFrameRevision={renderFrameRevision}
|
|
465
472
|
mediaPreviews={mediaPreviews}
|
|
466
473
|
activeBandRibbonProps={activeBandRibbonProps}
|
|
@@ -516,6 +523,7 @@ function propsAreEqual(
|
|
|
516
523
|
prev.pmSurfaceElement === next.pmSurfaceElement &&
|
|
517
524
|
prev.pmView === next.pmView &&
|
|
518
525
|
rangeEqual(prev.visiblePageIndexRange, next.visiblePageIndexRange) &&
|
|
526
|
+
prev.plainPageBands === next.plainPageBands &&
|
|
519
527
|
prev["data-testid"] === next["data-testid"]
|
|
520
528
|
);
|
|
521
529
|
}
|
|
@@ -167,6 +167,28 @@ function renderSegment(
|
|
|
167
167
|
/>
|
|
168
168
|
);
|
|
169
169
|
}
|
|
170
|
+
case "shape": {
|
|
171
|
+
if (shouldRenderAbsoluteFloatingImageInPageOverlay(seg.anchor)) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
if (!seg.isTextBox || !seg.txbxText) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const style = buildSegmentStyle(seg.txbxMarks, seg.txbxMarkAttrs);
|
|
178
|
+
if (seg.txbxMarkAttrs?.textColor && isSafeHexColor(seg.txbxMarkAttrs.textColor)) {
|
|
179
|
+
style.color = `#${seg.txbxMarkAttrs.textColor}`;
|
|
180
|
+
}
|
|
181
|
+
return (
|
|
182
|
+
<span
|
|
183
|
+
key={seg.segmentId}
|
|
184
|
+
data-node-type="shape"
|
|
185
|
+
style={style}
|
|
186
|
+
title={seg.detail}
|
|
187
|
+
>
|
|
188
|
+
{seg.txbxText ?? seg.label}
|
|
189
|
+
</span>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
170
192
|
case "field_ref":
|
|
171
193
|
return (
|
|
172
194
|
<span
|
|
@@ -202,6 +224,10 @@ function renderSegment(
|
|
|
202
224
|
}
|
|
203
225
|
}
|
|
204
226
|
|
|
227
|
+
function isSafeHexColor(value: string): boolean {
|
|
228
|
+
return /^[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/.test(value);
|
|
229
|
+
}
|
|
230
|
+
|
|
205
231
|
// ---------------------------------------------------------------------------
|
|
206
232
|
// Block renderers
|
|
207
233
|
// ---------------------------------------------------------------------------
|
|
@@ -443,20 +443,22 @@
|
|
|
443
443
|
/*
|
|
444
444
|
* ─── ChromeOverlay: scope rail layer ───
|
|
445
445
|
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
446
|
+
* Product scope affordance now lives directly on the scoped inline text.
|
|
447
|
+
* Keep the legacy rail layer hidden if an old host mounts it directly:
|
|
448
|
+
* no gutter markers, no rail handles, no overlay tints.
|
|
448
449
|
*/
|
|
449
450
|
.wre-scope-rail-layer {
|
|
451
|
+
display: none;
|
|
450
452
|
pointer-events: none;
|
|
451
453
|
}
|
|
452
454
|
|
|
453
455
|
/*
|
|
454
456
|
* ─── Gutter lane tokens ───
|
|
455
457
|
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
458
|
+
* Kept for legacy gutter consumers and page-card composition. Product
|
|
459
|
+
* scope rails are hidden; scoped text is the visible scope affordance.
|
|
460
|
+
* Page surfaces use 64px, canvas surfaces 48px. Host apps can override
|
|
461
|
+
* via CSS custom property.
|
|
460
462
|
*/
|
|
461
463
|
:root {
|
|
462
464
|
--wre-gutter-lane-width: 64px;
|
|
@@ -531,12 +533,11 @@
|
|
|
531
533
|
}
|
|
532
534
|
|
|
533
535
|
/*
|
|
534
|
-
* ───
|
|
536
|
+
* ─── Legacy scope rail stripe ───
|
|
535
537
|
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
* via transform (zero layout cost) and reveals the edit handle.
|
|
538
|
+
* Hidden by .wre-scope-rail-layer. These rules are retained only so old
|
|
539
|
+
* direct mounts degrade consistently if they have not yet removed the
|
|
540
|
+
* rail DOM.
|
|
540
541
|
*/
|
|
541
542
|
.wre-scope-rail-stripe {
|
|
542
543
|
position: absolute;
|
|
@@ -1076,6 +1077,12 @@
|
|
|
1076
1077
|
background-color: var(--color-bg-hover);
|
|
1077
1078
|
}
|
|
1078
1079
|
|
|
1080
|
+
.wre-page-band[data-page-band-chrome="plain"],
|
|
1081
|
+
.wre-page-band[data-page-band-chrome="plain"]:hover {
|
|
1082
|
+
background-color: transparent;
|
|
1083
|
+
border-color: transparent;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1079
1086
|
.wre-page-band[data-active="true"] {
|
|
1080
1087
|
background-color: var(--color-accent-soft);
|
|
1081
1088
|
border: 1px solid var(--color-border-accent);
|
|
@@ -243,6 +243,17 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
243
243
|
chromeOptions,
|
|
244
244
|
chromeVisibility: props.chromeVisibility,
|
|
245
245
|
});
|
|
246
|
+
const plainPageBands =
|
|
247
|
+
isPageWorkspace &&
|
|
248
|
+
chromeVisibility.pageChrome &&
|
|
249
|
+
!chromeVisibility.toolbar &&
|
|
250
|
+
!chromeVisibility.alerts &&
|
|
251
|
+
!chromeVisibility.selectionOverlay &&
|
|
252
|
+
!chromeVisibility.contextToolbars &&
|
|
253
|
+
!chromeVisibility.contextAnalytics &&
|
|
254
|
+
!chromeVisibility.statusBar &&
|
|
255
|
+
!chromeVisibility.reviewRail &&
|
|
256
|
+
!chromeVisibility.shellHeader;
|
|
246
257
|
const reviewRailAvailable = chromeVisibility.reviewRail && (caps?.reviewRailVisible ?? true);
|
|
247
258
|
const { viewportWidth, viewportHeight } = useViewportDimensions();
|
|
248
259
|
const { reviewRailOpen, setReviewRailOpen } = useReviewRailState({
|
|
@@ -1059,6 +1070,9 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
1059
1070
|
...(pageShellMetrics.frameHeightPx
|
|
1060
1071
|
? { minHeight: `${pageShellMetrics.frameHeightPx}px` }
|
|
1061
1072
|
: {}),
|
|
1073
|
+
...(pageShellMetrics.frameHeightPx
|
|
1074
|
+
? { "--wre-page-frame-height-px": `${pageShellMetrics.frameHeightPx}px` }
|
|
1075
|
+
: {}),
|
|
1062
1076
|
...(zoomScale !== 1 ? { zoom: zoomScale } : {}),
|
|
1063
1077
|
}
|
|
1064
1078
|
: zoomScale !== 1
|
|
@@ -1209,6 +1223,7 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
1209
1223
|
onOpenStory={handleOpenPageModeStory}
|
|
1210
1224
|
pmSurfaceElement={pmSurfaceElement}
|
|
1211
1225
|
visiblePageIndexRange={visiblePageIndexRange}
|
|
1226
|
+
plainPageBands={plainPageBands}
|
|
1212
1227
|
mediaPreviews={props.mediaPreviews}
|
|
1213
1228
|
activeBandRibbonProps={activeBandRibbonProps}
|
|
1214
1229
|
/>
|