Operand

do no harem.

gram: docs

> ./packages/core/src/prosemirror/editor.css

/**
* ProseMirror Editor Styles
*
* Provides page-like layout similar to Word/Google Docs
*/
/* Outer wrapper - handles scrolling and background */
.prosemirror-editor-wrapper {
min-height: 100%;
/* overflow, background, and padding set via inline styles */
}
/* Zoom container - sized to match scaled content for proper scrolling */
.prosemirror-zoom-container {
/* Width, height, margin set via inline styles based on zoom */
}
/* Editor container - the transform target */
.prosemirror-editor {
/* Transform applied via inline styles for zoom */
/* CSS variables (--page-width, etc.) set via inline styles */
/* Width is set to page width so transform scales correctly */
width: var(--page-width, 8.5in);
}
/* Editor content area (the actual editable content) */
.prosemirror-editor .ProseMirror {
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
/* Use CSS variables for page layout, with fallbacks */
width: var(--page-width, 8.5in);
/* min-height creates a single page appearance */
min-height: var(--page-height, 11in);
padding-top: var(--margin-top, 72px);
padding-bottom: var(--margin-bottom, 72px);
padding-left: var(--margin-left, 72px);
padding-right: var(--margin-right, 72px);
/* Remove max-width constraint - let the page be its natural size */
outline: none;
/* Default font - will be overridden by inline styles from DOCX */
font-family: 'Calibri', 'Arial', sans-serif;
font-size: 11pt;
/* DO NOT set line-height here - it overrides inline paragraph styles */
/* line-height is set per-paragraph via inline styles from paragraphAttrsToDOMStyle */
color: #000;
white-space: pre-wrap;
word-wrap: break-word;
/* Tab size for proper tab character rendering (0.5 inch = 48px at 96dpi) */
tab-size: 48;
-moz-tab-size: 48;
/* Ensure box-sizing includes padding in width calculation */
box-sizing: border-box;
/* Page break behavior - show page breaks visually */
position: relative;
}
/* ============================================================================
PAGE BREAK STYLES - Visual page boundaries like Word/Google Docs
============================================================================ */
/* Page break indicator - inserted by PageBreakPlugin */
.prosemirror-editor .ProseMirror .docx-page-break {
display: block;
position: relative;
height: 40px;
margin: 0 calc(-1 * var(--margin-left, 72px));
margin-right: calc(-1 * var(--margin-right, 72px));
background: var(--doc-bg);
border: none;
pointer-events: none;
}
/* Top shadow of next page */
.prosemirror-editor .ProseMirror .docx-page-break::before {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 8px;
background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.08));
}
/* Bottom shadow of previous page */
.prosemirror-editor .ProseMirror .docx-page-break::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 8px;
background: linear-gradient(to top, transparent, rgba(0, 0, 0, 0.08));
}
/* Explicit page break (from DOCX pageBreakBefore) */
.prosemirror-editor .ProseMirror [data-page-break-before='true'] {
break-before: page;
}
/* Page break dashed line indicator (alternative style) */
.prosemirror-editor .ProseMirror .docx-page-break-line {
display: block;
position: relative;
height: 1px;
margin: 20px calc(-1 * var(--margin-left, 72px));
margin-right: calc(-1 * var(--margin-right, 72px));
border-top: 1px dashed var(--doc-border);
}
.prosemirror-editor .ProseMirror .docx-page-break-line::before {
content: 'Page Break';
position: absolute;
top: -10px;
left: 50%;
transform: translateX(-50%);
background: var(--doc-bg);
padding: 0 8px;
font-size: 10px;
color: var(--doc-text-muted);
white-space: nowrap;
}
/* Focus state */
.prosemirror-editor .ProseMirror:focus {
outline: none;
}
/* Paragraphs - ensure they display as blocks with proper spacing */
/* IMPORTANT: Do NOT set margin, line-height, or font here - use inline styles from DOCX */
.prosemirror-editor .ProseMirror p {
display: block;
/* Default margin only when no inline style is set */
margin: 0;
padding: 0;
min-height: 1em;
white-space: pre-wrap;
word-wrap: break-word;
/* Default line height - can be overridden by inline styles */
line-height: normal;
}
/* Empty paragraph placeholder */
.prosemirror-editor .ProseMirror p:empty::before {
content: '\00a0';
}
/* Inline spans for formatting - ensure they don't break layout */
.prosemirror-editor .ProseMirror span {
display: inline;
white-space: pre-wrap;
}
/* List styles */
.prosemirror-editor .ProseMirror .docx-list-bullet,
.prosemirror-editor .ProseMirror .docx-list-numbered {
position: relative;
/* Increased padding to accommodate longer markers like "1.1.1." */
padding-left: 48px;
}
/* Use the computed marker from DOCX when available */
.prosemirror-editor .ProseMirror [data-list-marker]::before {
content: attr(data-list-marker);
position: absolute;
/* Position marker at right edge of the marker area so it aligns with content */
left: 0;
width: 44px;
text-align: right;
white-space: nowrap;
}
/* Fallback for bullet lists without marker */
.prosemirror-editor .ProseMirror .docx-list-bullet:not([data-list-marker])::before {
content: '•';
position: absolute;
left: 0;
width: 44px;
text-align: right;
}
/* Fallback for numbered lists without marker - use CSS counters */
.prosemirror-editor .ProseMirror .docx-list-numbered:not([data-list-marker]) {
counter-increment: list-counter;
}
.prosemirror-editor .ProseMirror .docx-list-decimal:not([data-list-marker])::before {
content: counter(list-counter) '.';
position: absolute;
left: 0;
width: 44px;
text-align: right;
}
.prosemirror-editor .ProseMirror .docx-list-lower-roman:not([data-list-marker])::before {
content: counter(list-counter, lower-roman) '.';
position: absolute;
left: 0;
width: 44px;
text-align: right;
}
.prosemirror-editor .ProseMirror .docx-list-upper-roman:not([data-list-marker])::before {
content: counter(list-counter, upper-roman) '.';
position: absolute;
left: 0;
width: 44px;
text-align: right;
}
.prosemirror-editor .ProseMirror .docx-list-lower-alpha:not([data-list-marker])::before {
content: counter(list-counter, lower-alpha) '.';
position: absolute;
left: 0;
width: 44px;
text-align: right;
}
.prosemirror-editor .ProseMirror .docx-list-upper-alpha:not([data-list-marker])::before {
content: counter(list-counter, upper-alpha) '.';
position: absolute;
left: 0;
width: 44px;
text-align: right;
}
/* Reset counter at level 0 for fallback */
.prosemirror-editor .ProseMirror .docx-list-level-0:not([data-list-marker]):first-of-type {
counter-reset: list-counter;
}
/* Indent levels */
.prosemirror-editor .ProseMirror .docx-list-level-1 {
margin-left: 0.5in;
}
.prosemirror-editor .ProseMirror .docx-list-level-2 {
margin-left: 1in;
}
.prosemirror-editor .ProseMirror .docx-list-level-3 {
margin-left: 1.5in;
}
.prosemirror-editor .ProseMirror .docx-list-level-4 {
margin-left: 2in;
}
.prosemirror-editor .ProseMirror .docx-list-level-5 {
margin-left: 2.5in;
}
.prosemirror-editor .ProseMirror .docx-list-level-6 {
margin-left: 3in;
}
.prosemirror-editor .ProseMirror .docx-list-level-7 {
margin-left: 3.5in;
}
.prosemirror-editor .ProseMirror .docx-list-level-8 {
margin-left: 4in;
}
/* Selection styling */
.prosemirror-editor .ProseMirror ::selection {
background: rgba(66, 133, 244, 0.3);
}
/* Placeholder styling */
.prosemirror-editor[data-placeholder]::before {
content: attr(data-placeholder);
color: #aaa;
position: absolute;
pointer-events: none;
}
/* ============================================================================
IMAGE STYLES - Word-like image rendering
============================================================================
DOCX images have three main display modes:
1. Inline - flows with text like a character (displayMode='inline')
2. Float - text wraps around image (displayMode='float', cssFloat='left'|'right')
3. Block - centered on its own line (displayMode='block')
*/
/* Base image styling - all images */
.prosemirror-editor .ProseMirror img.docx-image {
/* IMPORTANT: Override global CSS reset that sets img { display: block } */
/* Default to inline for inline images - block/float set via inline style */
display: inline;
/* Dimensions set via inline styles, but constrain to page */
max-width: 100%;
/* Maintain aspect ratio when width-constrained */
object-fit: contain;
/* Vertical alignment for inline images - aligns bottom of image with text baseline */
vertical-align: baseline;
/* Prevent selection outline issues */
outline: none;
}
/* Floating images - text wraps around */
.prosemirror-editor .ProseMirror img.docx-image-float {
/* Float direction set via inline style */
/* Margins set via inline style based on wrap distances */
}
/* Left-floated images - text wraps on right side */
.prosemirror-editor .ProseMirror img.docx-image-float-left {
clear: left;
}
/* Right-floated images - text wraps on left side */
.prosemirror-editor .ProseMirror img.docx-image-float-right {
clear: right;
}
/* Block images - centered on their own line */
.prosemirror-editor .ProseMirror img.docx-image-block {
/* display:block and margin:auto set via inline style */
clear: both;
}
/* Paragraphs containing only a block image: remove text-indent and center */
.prosemirror-editor .ProseMirror p:has(> img.docx-image-block:only-child) {
text-indent: 0 !important;
text-align: center;
}
/* Clear floats after paragraphs containing floated images */
.prosemirror-editor .ProseMirror p:has(img.docx-image-float)::after {
content: '';
display: table;
clear: both;
}
/* Selected image indicator */
.prosemirror-editor .ProseMirror img.docx-image.ProseMirror-selectednode {
outline: 2px solid var(--doc-primary, #4285f4);
outline-offset: 2px;
cursor: grab;
}
/* Image dragging state */
.prosemirror-editor .ProseMirror.pm-image-dragging img.docx-image.ProseMirror-selectednode {
opacity: 0.4;
outline: 2px dashed var(--doc-primary, #4285f4);
cursor: grabbing;
}
/* Fallback for images without docx-image class */
.prosemirror-editor .ProseMirror img:not(.docx-image) {
max-width: 100%;
height: auto;
}
/* Shapes */
.prosemirror-editor .ProseMirror .docx-shape {
display: inline-block;
vertical-align: middle;
line-height: 0;
cursor: pointer;
}
.prosemirror-editor .ProseMirror .docx-shape.ProseMirror-selectednode {
outline: 2px solid var(--doc-primary, #4285f4);
outline-offset: 2px;
}
/* Text boxes */
.prosemirror-editor .ProseMirror .docx-textbox {
border-radius: 2px;
margin: 4px 0;
cursor: text;
}
.prosemirror-editor .ProseMirror .docx-textbox.ProseMirror-selectednode {
outline: 2px solid var(--doc-primary, #4285f4);
outline-offset: 2px;
}
/* Links */
.prosemirror-editor .ProseMirror a {
color: var(--doc-link);
text-decoration: underline;
}
.prosemirror-editor .ProseMirror a:hover {
color: var(--doc-primary-hover);
}
/* Footnote/Endnote references */
.prosemirror-editor .ProseMirror .docx-footnote-ref,
.prosemirror-editor .ProseMirror .docx-endnote-ref {
color: var(--doc-link, #0066cc);
cursor: pointer;
vertical-align: super;
font-size: smaller;
}
.prosemirror-editor .ProseMirror .docx-footnote-ref:hover,
.prosemirror-editor .ProseMirror .docx-endnote-ref:hover {
text-decoration: underline;
}
/* Horizontal rule */
.prosemirror-editor .ProseMirror hr {
border: none;
border-top: 1px solid #000;
margin: 12pt 0;
height: 0;
}
/* ============================================================================
TABLE STYLES
============================================================================ */
/* Table container */
.prosemirror-editor .ProseMirror table.docx-table {
border-collapse: collapse;
margin: 12pt 0;
/* Constrain table to fit within the page - prevent overflow */
max-width: 100%;
}
/* Table cells */
.prosemirror-editor .ProseMirror td.docx-table-cell,
.prosemirror-editor .ProseMirror th.docx-table-header {
/* Borders are set via inline styles from DOCX - no default border here */
/* to avoid conflicting with per-side border styling */
padding: 4px 8px;
vertical-align: top;
min-width: 30px;
/* Text wrapping handled via inline styles based on noWrap attribute */
}
/* Table header cells are structural; DOCX text formatting comes from marks/styles. */
.prosemirror-editor .ProseMirror th.docx-table-header {
font-weight: inherit;
text-align: inherit;
}
/* Cell content - paragraphs inside cells */
.prosemirror-editor .ProseMirror td p,
.prosemirror-editor .ProseMirror th p {
margin: 0;
padding: 0;
min-height: auto;
}
/* First paragraph in cell shouldn't have top margin */
.prosemirror-editor .ProseMirror td p:first-child,
.prosemirror-editor .ProseMirror th p:first-child {
margin-top: 0;
}
/* Last paragraph in cell shouldn't have bottom margin */
.prosemirror-editor .ProseMirror td p:last-child,
.prosemirror-editor .ProseMirror th p:last-child {
margin-bottom: 0;
}
/* Active cell (cursor is inside this cell) — subtle outline so user knows which cell will be affected */
.prosemirror-editor .ProseMirror .activeCell {
outline: 2px solid rgba(66, 133, 244, 0.5);
outline-offset: -2px;
}
/* Selected cell highlight (multi-cell CellSelection) */
.prosemirror-editor .ProseMirror .selectedCell {
background-color: rgba(66, 133, 244, 0.15);
}
/* Table selection */
.prosemirror-editor .ProseMirror .selectedCell::after {
content: '';
position: absolute;
inset: 0;
background: rgba(66, 133, 244, 0.1);
pointer-events: none;
}
/* Nested tables */
.prosemirror-editor .ProseMirror table table {
margin: 4px 0;
}
/* ============================================================================
TABLE COLUMN RESIZE
============================================================================ */
/* Column resize handle - visible on hover */
.prosemirror-editor .ProseMirror .column-resize-handle {
position: absolute;
right: -2px;
top: 0;
bottom: 0;
width: 4px;
background-color: rgba(66, 133, 244, 0.5);
cursor: col-resize;
z-index: 20;
}
/* Make cells position relative for resize handle positioning */
.prosemirror-editor .ProseMirror td.docx-table-cell,
.prosemirror-editor .ProseMirror th.docx-table-header {
position: relative;
}
/* Cursor change when resizing */
.prosemirror-editor .ProseMirror.resize-cursor {
cursor: col-resize;
}
/* Hide resize handle when not hovering the table, show on hover */
.prosemirror-editor .ProseMirror table:not(:hover) .column-resize-handle {
background-color: transparent;
}
.prosemirror-editor .ProseMirror table:hover .column-resize-handle {
background-color: rgba(66, 133, 244, 0.3);
}
/* Highlight resize handle on direct hover */
.prosemirror-editor .ProseMirror .column-resize-handle:hover {
background-color: rgba(66, 133, 244, 0.7);
}
/* ============================================================================
TAB STYLES
============================================================================ */
/* Tab character - renders as inline block with minimum width */
.prosemirror-editor .ProseMirror span.docx-tab {
display: inline-block;
min-width: 48px; /* 0.5 inch default tab stop */
white-space: pre;
/* Align to tab grid using CSS */
background: transparent;
}
/* Tab leader styles (for future enhancement) */
.prosemirror-editor .ProseMirror span.docx-tab[data-leader='dot']::before {
content: '...........';
position: absolute;
color: inherit;
white-space: nowrap;
overflow: hidden;
}
.prosemirror-editor .ProseMirror span.docx-tab[data-leader='hyphen']::before {
content: '-----------';
position: absolute;
color: inherit;
white-space: nowrap;
overflow: hidden;
}
.prosemirror-editor .ProseMirror span.docx-tab[data-leader='underscore']::before {
content: '___________';
position: absolute;
color: inherit;
white-space: nowrap;
overflow: hidden;
}
/* ============================================================================
TEXT TRANSFORM STYLES - All caps, small caps
============================================================================ */
/* All caps (w:caps) - transforms text to uppercase */
.prosemirror-editor .ProseMirror .docx-run-all-caps {
text-transform: uppercase;
}
/* Small caps (w:smallCaps) - displays lowercase as smaller capitals */
.prosemirror-editor .ProseMirror .docx-run-small-caps {
font-variant: small-caps;
}
/* Section break indicator */
.docx-section-break {
position: relative;
}
.docx-section-break::after {
content: 'Section Break (' attr(data-section-break) ')';
display: block;
text-align: center;
font-size: 9px;
color: var(--doc-text-muted, #9ca3af);
border-top: 1px dashed var(--doc-border, #d1d5db);
margin-top: 4px;
padding-top: 2px;
pointer-events: none;
}
/* ============================================================================
IMAGE DROP INDICATOR - Shows where a dragged image will land
============================================================================ */
/* Custom drop indicator (from ImageDragExtension) */
.prosemirror-editor .ProseMirror .pm-drop-indicator {
position: absolute;
width: 2px;
background: var(--doc-primary, #4285f4);
pointer-events: none;
z-index: 50;
}
/* prosemirror-dropcursor styling */
.prosemirror-editor .ProseMirror .ProseMirror-dropcursor {
z-index: 50;
}
/* Page content area — show text cursor when hovering over editable content */
.layout-page-content {
cursor: text;
}
/* Header/footer areas — double-click hint */
.layout-page-header,
.layout-page-footer {
cursor: pointer;
transition: background-color 0.15s ease;
}
.layout-page-header:hover,
.layout-page-footer:hover {
background-color: rgba(37, 99, 235, 0.06);
}
/* Show "Double-click to edit" hint on hover when area is empty */
.layout-page-header:empty:hover::after {
content: 'Double-click to add header';
display: block;
text-align: center;
color: #9ca3af;
font-size: 11px;
padding: 4px 0;
}
.layout-page-footer:empty:hover::after {
content: 'Double-click to add footer';
display: block;
text-align: center;
color: #9ca3af;
font-size: 11px;
padding: 4px 0;
}
/*
* Phase 5 of HF editing unification (openspec/changes/unify-hf-editing/):
* the inline overlay no longer mounts its own EditorView — the painter is
* the visible HF renderer and the persistent hidden HF PM is the sole
* editor. The entire `.hf-editor-pm` rule block that lived here existed
* only to make PM's `toDOM` table match the painter's flex layout; with
* the renderer unified, those workarounds (font-strut zeroing, `top:
* 0.4em` shim, table-layout overrides, line-height resets) are deleted
* by construction. Issue #468's class of bug is gone.
*/
/* ============================================================================
INLINE HEADER/FOOTER EDITING — Dim body, separator bar, dotted borders
============================================================================ */
/* Dim body content when editing header/footer */
.paged-editor--hf-editing .layout-page-content {
opacity: 0.4;
pointer-events: none;
transition: opacity 0.15s ease;
}
/* While editing one HF region, the sibling region (and the non-edited area)
shows a normal arrow, not the double-click "pointer" hint. The active
region's `cursor: text` below wins via later source order. */
.paged-editor--hf-editing .layout-page-header,
.paged-editor--hf-editing .layout-page-footer {
cursor: default;
}
/* Dotted border on active header area; text caret since it's being edited */
.paged-editor--editing-header .layout-page-header {
border-bottom: 1px dotted #4285f4;
cursor: text;
}
/* Dotted border on active footer area; text caret since it's being edited */
.paged-editor--editing-footer .layout-page-footer {
border-top: 1px dotted #4285f4;
cursor: text;
}
/*
* HF editing unification phase 2 (openspec/changes/unify-hf-editing/):
* we used to hide the painter's HF region during edit and show the inline
* overlay's PM-rendered `<table>` on top. Now the painter is the sole
* visible renderer in both modes — the overlay's PM container moves
* off-screen instead (see InlineHeaderFooterEditor.tsx). Keeping the
* dotted-border affordance above; everything painter-related stays visible.
*/
/* Remove hover effect on header/footer when in editing mode */
.paged-editor--hf-editing .layout-page-header:hover,
.paged-editor--hf-editing .layout-page-footer:hover {
background-color: transparent;
}
/* =============================================================================
* Tracked structural revisions — painter + ProseMirror DOM cues
*
* Lives in core because both React and Vue painters/PM trees emit the same
* class set via toDOM and renderParagraph. Color palette matches the
* insertion/deletion mark colors (#2e7d32 green, #c62828 red) used by
* InsertionExtension / DeletionExtension.
* ============================================================================= */
/* Paragraph mark — painter fragment (renderParagraph.ts) */
.layout-revision-pmark {
position: relative;
}
.layout-revision-pmark::before {
content: '';
position: absolute;
left: -10px;
top: 0;
bottom: 0;
width: 2px;
pointer-events: none;
}
.layout-revision-ins.layout-revision-pmark::before {
background-color: #2e7d32;
}
.layout-revision-del.layout-revision-pmark::before {
background-color: #c62828;
}
/* Pilcrow glyph — appended into the last line element by the painter so it
sits inline with the text instead of as a ::after pseudo on the fragment
(which would render as its own block row below the line). */
.layout-revision-pmark-glyph {
display: inline;
margin-left: 2px;
font-weight: normal;
opacity: 0.75;
pointer-events: none;
}
.layout-revision-pmark-glyph.layout-revision-ins {
color: #2e7d32;
}
.layout-revision-pmark-glyph.layout-revision-del {
color: #c62828;
text-decoration: line-through;
}
/* Paragraph mark — hidden ProseMirror DOM (ParagraphExtension toDOM) */
.ep-revision-pmark {
padding-left: 6px;
box-shadow: inset 2px 0 0 currentColor;
}
.ep-revision-pmark.ep-revision-ins {
color: #2e7d32;
}
.ep-revision-pmark.ep-revision-del {
color: #c62828;
}
/* Tracked row — colored left margin bar. Hidden ProseMirror table uses
normal block flow so we paint on the per-cell child (one bar per row).
The painted layout uses absolutely-positioned cells inside a relative
`.layout-table-row`, so a single ::before pseudo on the row draws one
continuous bar on the left edge — much cleaner than stamping a bar on
every cell child. */
.ep-revision-row {
position: relative;
}
.layout-table-row.ep-revision-row::before {
content: '';
position: absolute;
left: -10px;
top: 0;
bottom: 0;
width: 2px;
pointer-events: none;
}
.layout-table-row.ep-revision-row.ep-revision-ins::before {
background-color: #2e7d32;
}
.layout-table-row.ep-revision-row.ep-revision-del::before {
background-color: #c62828;
}
/* Whole-table tracked insertion / deletion — one tall bar in the page
margin to the LEFT of the table. Matches the paragraph change-bar
visual (`.layout-revision-pmark::before`) so the user sees a
consistent margin indicator for any tracked block. The painter sets
`overflow: visible` on tracked tables so this pseudo can extend
beyond the table's own bounds into the page padding area. */
.layout-table.ep-revision-table::before {
content: '';
position: absolute;
left: -10px;
top: 0;
bottom: 0;
width: 2px;
pointer-events: none;
}
.layout-table.ep-revision-table.ep-revision-ins::before {
background-color: #2e7d32;
}
.layout-table.ep-revision-table.ep-revision-del::before {
background-color: #c62828;
}
/* Hidden PM DOM path — kept for the off-screen editing surface. */
.ep-revision-row:not(.layout-table-row).ep-revision-ins > * {
box-shadow: inset 3px 0 0 #2e7d32;
}
.ep-revision-row:not(.layout-table-row).ep-revision-del > * {
box-shadow: inset 3px 0 0 #c62828;
text-decoration: line-through;
text-decoration-color: rgba(198, 40, 40, 0.6);
}
/* Tracked cell — colored top border + faint background tint */
.ep-revision-cell.ep-revision-ins {
box-shadow: inset 0 3px 0 #2e7d32;
background-color: rgba(46, 125, 50, 0.05);
}
.ep-revision-cell.ep-revision-del {
box-shadow: inset 0 3px 0 #c62828;
background-color: rgba(198, 40, 40, 0.05);
text-decoration: line-through;
text-decoration-color: rgba(198, 40, 40, 0.6);
}
.ep-revision-cell.ep-revision-merge {
box-shadow: inset 0 3px 0 #5f6368;
background-color: rgba(95, 99, 104, 0.05);
}
/* Property-change paragraphs/blocks — quiet left margin bar in a neutral
color so the change is visible without being loud. */
.ep-revision-prop-change {
box-shadow: inset 2px 0 0 #5f6368;
padding-left: 6px;
}
/* Block-level Structured Document Tag (content control) boundary.
The painter lays body fragments as flat absolutely-positioned siblings, so
the boundary is a single overlay box per control (see renderSdtBoundaryBoxes)
spanning its fragments at content width — drawn as one rounded rectangle
like Word, with a corner label chip revealed on hover/focus. The box is
non-interactive so it never steals clicks from the editable content under it.
Nested controls each draw their own (slightly inset) box. */
.layout-block-sdt-box {
pointer-events: none;
border: 1px solid transparent;
border-radius: 4px;
box-sizing: border-box;
transition: border-color 0.1s ease;
z-index: 1;
}
.layout-block-sdt-box.is-active,
.layout-block-sdt-box.is-focused {
border-color: rgba(25, 103, 210, 0.5);
}
/* Corner label chip — hidden until its control is hovered/active. */
.layout-block-sdt-label {
position: absolute;
left: -1px;
bottom: 100%;
max-width: 160px;
height: 16px;
padding: 0 6px;
font-size: 10px;
line-height: 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #fff;
background-color: rgba(25, 103, 210, 0.92);
border-radius: 4px 4px 0 0;
display: none;
}
.layout-block-sdt-box.is-active .layout-block-sdt-label,
.layout-block-sdt-box.is-focused .layout-block-sdt-label {
display: block;
}
/* Interactive trigger for typed controls (checkbox/dropdown/date). The box is
pointer-events:none; the trigger re-enables them so it is clickable. Sits at
the top-right corner of the control box. */
.layout-sdt-widget {
position: absolute;
top: 1px;
right: 1px;
min-width: 18px;
height: 18px;
padding: 0 3px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
line-height: 1;
color: #1967d2;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(25, 103, 210, 0.5);
border-radius: 3px;
cursor: pointer;
pointer-events: auto;
opacity: 0.55;
transition: opacity 0.1s ease;
}
.layout-block-sdt-box.is-active .layout-sdt-widget,
.layout-block-sdt-box.is-focused .layout-sdt-widget,
.layout-sdt-widget:focus {
opacity: 1;
}
.layout-sdt-widget:hover {
background: #e8f0fe;
}
.layout-inline-sdt-widget {
cursor: pointer;
pointer-events: auto;
border-radius: 2px;
outline-offset: 1px;
user-select: none;
}
.layout-inline-sdt-widget:hover,
.layout-inline-sdt-widget:focus {
background: #e8f0fe;
outline: 1px solid rgba(25, 103, 210, 0.55);
}
/* Popup for dropdown/date content-control widgets (rendered by the adapter). */
.layout-sdt-widget-popup {
min-width: 120px;
max-height: 240px;
overflow-y: auto;
background: #fff;
border: 1px solid #dadce0;
border-radius: 6px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18);
padding: 4px;
font-size: 13px;
}
.layout-sdt-widget-option {
display: block;
width: 100%;
text-align: left;
padding: 6px 10px;
border: 0;
background: transparent;
border-radius: 4px;
cursor: pointer;
color: #202124;
}
.layout-sdt-widget-option:hover {
background: #e8f0fe;
}
.layout-sdt-widget-empty {
padding: 6px 10px;
color: #5f6368;
}
.layout-sdt-widget-date {
font: inherit;
padding: 4px 6px;
border: 1px solid #dadce0;
border-radius: 4px;
}
.layout-sdt-widget-option.is-selected {
font-weight: 600;
background: #f1f3f4;
}
/* Repeating-section item add/remove affordances (bottom-right of the item box). */
.layout-sdt-repeat-controls {
position: absolute;
bottom: 1px;
right: 1px;
display: flex;
gap: 2px;
opacity: 0.55;
pointer-events: none;
}
.layout-block-sdt-box.is-active .layout-sdt-repeat-controls,
.layout-block-sdt-box.is-focused .layout-sdt-repeat-controls {
opacity: 1;
}
.layout-sdt-repeat-btn {
min-width: 16px;
height: 16px;
padding: 0 3px;
font-size: 11px;
line-height: 1;
color: #1967d2;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(25, 103, 210, 0.5);
border-radius: 3px;
cursor: pointer;
pointer-events: auto;
}
.layout-sdt-repeat-btn:hover {
background: #e8f0fe;
}