@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.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/src/api/public-types.ts +33 -19
  3. package/src/api/v3/ui/_types.ts +11 -21
  4. package/src/api/v3/ui/chrome.ts +8 -9
  5. package/src/api/v3/ui/debug.ts +15 -77
  6. package/src/api/v3/ui/overlays-visibility.ts +9 -10
  7. package/src/api/v3/ui/overlays.ts +8 -75
  8. package/src/io/ooxml/parse-main-document.ts +30 -0
  9. package/src/io/ooxml/parse-picture.ts +14 -0
  10. package/src/io/ooxml/parse-shapes.ts +41 -1
  11. package/src/model/canonical-document.ts +17 -0
  12. package/src/runtime/layout/layout-engine-version.ts +8 -1
  13. package/src/runtime/layout/page-story-resolver.ts +1 -0
  14. package/src/runtime/layout/paginated-layout-engine.ts +26 -10
  15. package/src/runtime/surface-projection.ts +114 -12
  16. package/src/ui/WordReviewEditor.tsx +6 -10
  17. package/src/ui/editor-command-bag.ts +2 -0
  18. package/src/ui/ui-controller-factory.ts +2 -2
  19. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +11 -25
  20. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +2 -2
  21. package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +1 -1
  22. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +22 -220
  23. package/src/ui-tailwind/debug/README.md +12 -50
  24. package/src/ui-tailwind/debug/tw-debug-overlay.tsx +6 -6
  25. package/src/ui-tailwind/debug/tw-debug-presentation.tsx +9 -20
  26. package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +5 -6
  27. package/src/ui-tailwind/editor-surface/chart-node-view.tsx +1 -4
  28. package/src/ui-tailwind/editor-surface/picture-effects.ts +96 -0
  29. package/src/ui-tailwind/editor-surface/pm-schema.ts +89 -62
  30. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +205 -0
  31. package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +190 -0
  32. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +53 -53
  33. package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +83 -20
  34. package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +114 -4
  35. package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +5 -0
  36. package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +3 -0
  37. package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +3 -0
  38. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +8 -0
  39. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +26 -0
  40. package/src/ui-tailwind/theme/editor-theme.css +18 -11
  41. 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 = plane === "front" && typeof onActivateFloatingImage === "function";
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 content = item.src ? (
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
- * The overlay sits above PM and paints gutter handles plus optional
447
- * border-only line outlines. It must not fill document content.
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
- * The scope rail and scope card chrome live in a reserved lane to the
457
- * left of the page frame so they visibly read as chrome (not document
458
- * content). Page surfaces use 64px, canvas surfaces 48px. Host apps
459
- * can override via CSS custom property.
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
- * ─── Scope rail stripe ───
536
+ * ─── Legacy scope rail stripe ───
535
537
  *
536
- * The rail stripe is the rest-state representation of a scope: a 4px
537
- * color stripe in the gutter lane. Posture color comes from the
538
- * accent/warning/insert/secondary/danger tokens. Hover widens the stripe
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
  />