Skip to content

Commit 92bab61

Browse files
authored
fix(super-converter): bibliography/index/TOA field-code import fidelity (SD-3066) (#3565)
* fix(super-converter): bibliography/index/TOA field-code import fidelity (SD-3066) Real Word documents surfaced several import defects in the block field-code family (BIBLIOGRAPHY, INDEX, XE, TOA). This addresses them and unifies the shared shape behind one set of helpers. Fixes: - Multi-run instruction aggregation joined fragments with an injected separator space, corrupting instructions Word splits across runs (e.g. `XE " Building Standard "`). Join verbatim; literal spacing is preserved. - Table of Authorities was dropped on import: the v2 importer had no `sd:tableOfAuthorities` handler, so the node was silently discarded. Register tableOfAuthoritiesImporter alongside index/bibliography. - A content control wrapping a block field imported as an inline `structuredContent` node; inside a block-only documentPartObject this threw "Invalid content for node type documentPartObject" and the editor failed to mount. Classify an SDT whose content is a block field as structuredContentBlock. - Bibliography neither captured nor replayed instructionTokens (unlike index/toa), so split BIBLIOGRAPHY instructions did not round-trip. Add the attribute and wire it through preprocessor, encode and decode. Refactors (DRY/KISS): - Extract buildBlockFieldNode (shared by the bibliography/index/toa preprocessors) and wrapParagraphsAsComplexField (shared by their translator decoders), mirroring the existing inline-field helpers. - Centralize BLOCK_FIELD_XML_NAMES so the paragraph importer and SDT classifier agree on which sd:* nodes are block content. Adds RED→GREEN unit tests for each fix and the new shared helpers. Full super-converter suite passes (2990). Linear: SD-3066 (parent of SD-3005) * fix(pm-adapter): render bibliography/index/TOA inside documentPartObject (SD-3066) (#3566) Word wraps a generated bibliography (and other block fields) in a docPartObject SDT, sometimes via a nested content control. The handler only converted paragraph and tableOfContents children to flow blocks, so the field's entry paragraphs were silently dropped — the heading rendered but the entries did not. - documentPartObject now renders bibliography/index/tableOfAuthorities children via the shared paragraph-container handler, and structuredContentBlock children via their handler. - structuredContentBlock recurses block-field children (transparent wrapper), rendering their paragraphs. Section-counting invariant: findParagraphsWithSectPr recurses bibliography (so the handler advances currentParagraphIndex per entry) but not structuredContentBlock (so the scb path renders without advancing). Both paths were validated against the invariant; the prior code dropped entries AND under-counted, which could drift section breaks. Also extracts the shared handleParagraphContainerNode used by the bibliography, index and tableOfAuthorities handlers (previously three byte-identical copies). Regression tests cover both nesting shapes and the counter behavior. pm-adapter suite passes (302); layout corpus comparison shows no regressions attributable to this change. Linear: SD-3066 * feat(painter-dom): custom SDT styling variables under chrome:'none' (SD-3322) Under modules.contentControls.chrome:'none' the painter erased the SDT look entirely, so a consumer who wanted a custom field/clause appearance had to target the painted wrapper with !important and reach into internal state classes (.ProseMirror-selectednode, .sdt-group-hover) to keep it stable across hover and selection. That's the wrong "best practice" to teach. Make the chrome-none reset read a --sd-content-controls-custom-* variable layer with default-preserving fallbacks (0-width transparent border, no background / radius / padding). chrome:'none' stays visually empty by default - existing consumers see no change - but a consumer can now paint inline and block controls by setting variables on a data-sdt-* selector. The painter applies them across rest, hover, and selected, so the box stays stable (no jitter) and no !important or state-class selectors are needed. `border` is a full shorthand; block adds a `-border-left` accent rail; background vars cascade (hover from rest, selected from hover). - variables.css: document the custom-* surface; note the built-in chrome still uses the existing --sd-content-controls-* variables. - docs: add a "Style the controls in place" section to the custom-UI content controls guide. - test: assert the surface is wired and default-preserving; existing chrome-none selector + source-order tests are unchanged and still pass (painter-dom 1178/1178). * fix(painter-dom): custom hover wins on locked SDTs under chrome:'none' (SD-3322) The custom hover background was overridden for LOCKED controls under chrome:'none'. The base lock-hover rules (a built-in tint on inline, transparent on block) have equal specificity to the plain custom hover rules but come later in source order, so they won; the chrome-none lock-hover reset only reset z-index, not background. Re-assert the custom hover background in that reset block - it carries the extra .superdoc-cc-chrome-none class, so it outranks the base lock-hover rules. A locked control now follows --sd-content-controls-custom-*-hover-bg. With no custom var set the default is empty, so the built-in lock-hover tint no longer leaks under chrome:'none' for locked controls (consistently empty). Only the contract-templates demo has locked chrome-none controls, and it wants the custom hover, not the tint. Add a regression test asserting the custom hover vars are re-asserted after the base lock-hover rules (source order = it wins). painter-dom 1179/1179 green. * docs(theming): point chrome:'none' styling at the custom SDT variables (SD-3322) The content-controls theming table themes the built-in chrome. Add a one-line note that under chrome:'none' you style controls with the --sd-content-controls-custom-* variables instead, linking the custom UI guide. * demo/docs: contract-templates use the custom SDT styling variables (SD-3322) Rewrite the contract-templates demo's SDT styling onto SuperDoc's public --sd-content-controls-custom-* variables (from #3590), proving the new API in the real legal-template use case. The demo now styles its inline fields and block clauses with zero !important and zero internal state selectors (.ProseMirror-selectednode, .sdt-group-hover); the painter applies the variables across rest, hover, selected, and locked-hover. This is the copy-pasteable pattern for styling custom SDTs under chrome:'none'. - style.css: replace the per-state !important rules with one variable-setting rule per tag (inline + block); update the host-owned-styling comment. - test: add state coverage - the custom hover background drives a painted field (and wins over the built-in lock-hover tint), the border stays constant across states (no jitter), and no built-in label/chrome leaks. Demo suite 13/13. - docs (Document API > Content controls): correct the contentLocked wording (it rejects Document API content writes too, not just the editor); document the locked-template pattern (unlock -> write -> relock, incl. a locked parent for nested fields); add the single-use governed clause-library pattern alongside versioned reusable sections (kept - it's a valid pattern). - docs (Custom UI > Content controls): add a "Build a custom field system" walkthrough; describe the demo as a full custom contract-template UI. - README: note the demo styles through the public custom variables. Stacked on #3590 (the painter variable layer); retarget to main once it merges.
1 parent de5553c commit 92bab61

41 files changed

Lines changed: 965 additions & 482 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/document-api/features/content-controls.mdx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ for (const control of items) {
6666
}
6767
```
6868

69-
Composed runtime: [`demos/contract-templates`](https://github.com/superdoc-dev/superdoc/tree/main/demos/contract-templates).
69+
Or keep clauses **single-use and governed**: a clause is either in the contract or available to add from a library, and it appears once. Track inclusion by querying `contentControls.list` for the `sectionId` instead of comparing versions, and lock each placed clause (`contentLocked`) so its prose is fixed. A clause can also carry nested smart fields - inline controls inside the block - that fill from one place.
70+
71+
The [`demos/contract-templates`](https://github.com/superdoc-dev/superdoc/tree/main/demos/contract-templates) runtime composes the single-use approach: a clause library that inserts locked block clauses (some with nested fields), each filled by tag from a form.
7072

7173
## Why `tag`, not `nodeId`
7274

@@ -134,10 +136,12 @@ Set `lockMode` when you create a control to govern which changes are allowed.
134136
|---|---|
135137
| `unlocked` | Content and properties can be updated through the Document API. |
136138
| `sdtLocked` | The wrapper is preserved through user edits. |
137-
| `contentLocked` | The content can't be modified through the editor surface. |
139+
| `contentLocked` | The user can't edit the content, **and** content writes through the Document API (`text.setValue`, `replaceContent`) are rejected too - they return a `LOCK_VIOLATION`. |
138140
| `sdtContentLocked` | Both wrapper and content are preserved. |
139141

140-
For controls your app drives with `text.setValue`, `replaceContent`, or `patch`, use `lockMode: 'unlocked'`.
142+
For controls your app drives freely with `text.setValue` or `replaceContent`, use `lockMode: 'unlocked'`.
143+
144+
For a **locked template** - controls the user can't touch, but your app still updates - keep them `contentLocked` and unlock around each write: `setLockMode({ lockMode: 'unlocked' })`, write, then `setLockMode({ lockMode: 'contentLocked' })`. Use `try`/`finally` so a failed write never leaves a control unlocked. `setLockMode` and `patch` are not blocked by `contentLocked`, so only the content write needs the unlock window. A smart field nested inside a locked block control needs the **parent** unlocked for the write too, since the parent's content lock vetoes writes to anything inside it.
141145

142146
## Data binding
143147

apps/docs/editor/custom-ui/content-controls.mdx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,33 @@ new SuperDoc({
3030

3131
The event tells you *what* is active; `getRect` tells you *where* to draw. `active` is an `SdtRef` with `id`, `tag`, `alias`, `controlType`, and `scope`.
3232

33+
## Style the controls in place
34+
35+
Turning off chrome erases the built-in look, including hover and selection. To paint your own field and clause look, set `--sd-content-controls-custom-*` variables on the painted wrapper. Target it by your own `data-sdt-*` attributes. No `!important`, and no need to touch SuperDoc's internal state classes: the painter applies your variables across rest, hover, and selected, so the box stays stable and you never write `.ProseMirror-selectednode` or hover rules yourself.
36+
37+
```css
38+
/* A field your app tagged { kind: 'smartField', ... } */
39+
.superdoc-cc-chrome-none .superdoc-structured-content-inline[data-sdt-tag*='smartField'] {
40+
--sd-content-controls-custom-inline-border: 1px solid #1355ff;
41+
--sd-content-controls-custom-inline-bg: color-mix(in srgb, #1355ff 12%, transparent);
42+
--sd-content-controls-custom-inline-hover-bg: color-mix(in srgb, #1355ff 20%, transparent);
43+
--sd-content-controls-custom-inline-radius: 4px;
44+
--sd-content-controls-custom-inline-padding: 1px 6px;
45+
}
46+
47+
/* A clause your app tagged { kind: 'reusableSection', ... } */
48+
.superdoc-cc-chrome-none .superdoc-structured-content-block[data-sdt-tag*='reusableSection'] {
49+
--sd-content-controls-custom-block-border: 1px solid #d6e0ff;
50+
--sd-content-controls-custom-block-border-left: 4px solid #1355ff; /* accent rail */
51+
--sd-content-controls-custom-block-bg: color-mix(in srgb, #1355ff 4%, transparent);
52+
--sd-content-controls-custom-block-radius: 6px;
53+
}
54+
```
55+
56+
`border` is a full CSS shorthand; `border-left` is an optional accent rail for block clauses. The background variables cascade, so set only what changes: `-hover-bg` defaults to `-bg`, and `-selected-bg` defaults to `-hover-bg`.
57+
58+
This is the path for `chrome: 'none'`. To theme the **built-in** chrome instead (`chrome: 'default'`), use the `--sd-content-controls-*` variables (without `custom`).
59+
3360
## Pick the right surface
3461

3562
| Goal | API |
@@ -55,8 +82,19 @@ The event tells you *what* is active; `getRect` tells you *where* to draw. `acti
5582

5683
You build your UI *over* the control, not inside it. SuperDoc owns how the control's content is painted in the document; you turn off its built-in chrome and draw your own (chips, badges, panels) anchored with `getRect`, react with the events, and change content through `editor.doc.contentControls.*`. Custom field types are expressed as a `tag` - for example `{ kind: 'smartField', key: 'party_name' }`, interpreted by your own UI - the underlying control stays a standard Word SDT so it round-trips to `.docx`.
5784

85+
## Build a custom field system
86+
87+
Putting it together into a fillable template, the way the contract-templates demo does:
88+
89+
1. **Define a tag schema.** Give each control a JSON `tag` your app owns - e.g. `{ kind: 'smartField', key }` for inline variables and `{ kind: 'reusableSection', sectionId }` for clauses.
90+
2. **Insert from a palette.** Drop a control at a point with `editor.doc.create.contentControl({ kind, at, content, tag, lockMode })`, resolving the drop point with `ui.viewport.positionAt({ x, y })`. A clause can wrap its `{ field }` slots as nested inline controls.
91+
3. **Style it.** Set the `--sd-content-controls-custom-*` variables on a `data-sdt-tag` selector (see [Style the controls in place](#style-the-controls-in-place)). The sidebar chips can reuse the same tokens, so palette and document match.
92+
4. **React.** Highlight the active control from `content-control:active-change` / `:click`, and anchor overlays with `getRect` + `ui.viewport.observe`.
93+
5. **Fill by tag.** A form edits a value and fans it to every occurrence: `editor.doc.contentControls.selectByTag({ tag })`, then `text.setValue` per occurrence.
94+
6. **Govern with locks.** Keep controls `contentLocked` so users can't edit them, and have the form unlock → write → relock (see [Lock modes](/document-api/features/content-controls#lock-modes)). For a field nested in a locked clause, unlock the parent for the write.
95+
5896
## See also
5997

60-
- [Contract templates demo](https://github.com/superdoc-dev/superdoc/tree/main/demos/contract-templates) - a working field chip built on these APIs.
98+
- [Contract templates demo](https://github.com/superdoc-dev/superdoc/tree/main/demos/contract-templates) - a full custom contract-template UI: a field + clause library, custom SDT styling, locks, form-driven values, events, insert, and export.
6199
- [Configuration](/editor/superdoc/configuration) - the `modules.contentControls.chrome` option.
62100
- [Document API: content controls](/document-api/features/content-controls) - read and change controls.

apps/docs/editor/theming/custom-themes.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ If you also want tracked-change text inside comment threads to match, set `--sd-
318318

319319
DOCX content controls (SDTs): form fields, dropdowns, date pickers.
320320

321+
These theme the **built-in** chrome (`modules.contentControls.chrome: 'default'`). If you turn the chrome off (`chrome: 'none'`) to draw your own field/clause look, style the controls with the `--sd-content-controls-custom-*` variables instead. See [Custom UI > Content controls](/editor/custom-ui/content-controls).
322+
321323
| Variable | Default | Controls |
322324
|----------|---------|----------|
323325
| `--sd-content-controls-block-border` | `#629be7` | Block control border |

demos/__tests__/contract-templates-smart-tags.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,3 +397,48 @@ test('adding the Return of Materials clause nests a real smart field that fills
397397
.poll(async () => (await receivingPartyControls()).filter((t) => t === 'Beacon Bio').length, { timeout: 6_000 })
398398
.toBe(before + 1);
399399
});
400+
401+
test('the public custom SDT variables drive the painted fields across states (no !important)', async ({ page }) => {
402+
test.skip(process.env.DEMO !== 'contract-templates', 'contract-templates demo only');
403+
404+
await page.route('**/ingest.superdoc.dev/**', (r) =>
405+
r.fulfill({ status: 204, contentType: 'application/json', body: '{}' }),
406+
);
407+
await page.goto('/');
408+
await page.waitForFunction(
409+
() => (window as any).__demo?.state?.ui?.contentControls?.getSnapshot()?.items?.length > 0,
410+
null,
411+
{ timeout: 30_000 },
412+
);
413+
const sel = ".superdoc-structured-content-inline[data-sdt-tag*='smartField']";
414+
await page.waitForSelector(sel);
415+
const field = page.locator(sel).first();
416+
417+
const bg = () => field.evaluate((el) => getComputedStyle(el).backgroundColor);
418+
const borderTop = () =>
419+
field.evaluate((el) => `${getComputedStyle(el).borderTopWidth} ${getComputedStyle(el).borderTopColor}`);
420+
421+
const restBg = await bg();
422+
const restBorder = await borderTop();
423+
await field.hover();
424+
await page.waitForTimeout(250);
425+
const hoverBg = await bg();
426+
const hoverBorder = await borderTop();
427+
428+
// The custom hover background applies (the fill changes)...
429+
expect(hoverBg).not.toBe(restBg);
430+
// ...and it is NOT the built-in lock-hover tint. Fields carry data-lock-mode,
431+
// which matches SuperDoc's lock-hover path; the custom variable must win.
432+
expect(hoverBg).not.toBe('rgba(98, 155, 231, 0.08)');
433+
// The border is constant across states (no jitter) - achieved with variables
434+
// alone: the demo CSS has no !important and no .ProseMirror-selectednode /
435+
// .sdt-group-hover state selectors.
436+
expect(hoverBorder).toBe(restBorder);
437+
expect(restBorder.startsWith('1px ')).toBe(true);
438+
439+
// No built-in label / chrome leaks under chrome:'none'.
440+
const leakedLabels = await page
441+
.locator('.superdoc-structured-content__label, .superdoc-structured-content-inline__label')
442+
.count();
443+
expect(leakedLabels).toBe(0);
444+
});

demos/contract-templates/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ The starting document is `public/nda-template.docx`: inline plain-text fields an
1515
- Smart-field chips wear the same blue token look as the in-document field (CSS on `.superdoc-structured-content-inline[data-sdt-tag*='smartField']`). Drag a chip onto the document, or click to insert it at the cursor. An unfilled field shows its field-name token (e.g. `DISCLOSING_PARTY`) as a stand-in placeholder. That token is literal text content, not a native SDT placeholder.
1616
- Clause cards wear the same blue block look as the in-document clause and carry metadata (category, jurisdiction, version) and a status. A clause is single-use, like an inclusion checklist: a card already in the contract reads **In contract** and clicking it reveals the existing clause; an available card reads **Add clause** and drags or clicks in. The catalog includes clauses that aren't in the document yet (e.g. Indemnification, Return of Materials).
1717

18+
**Custom styling.** With chrome off, the field and clause look is set entirely through SuperDoc's public `--sd-content-controls-custom-*` CSS variables, on a `data-sdt-tag` selector. SuperDoc applies them across rest, hover, selected, and locked-hover, so the demo's CSS has no `!important` and no internal state classes (`.ProseMirror-selectednode`, `.sdt-group-hover`) - copy these rules to style your own SDTs. See [Custom UI > Content controls](https://docs.superdoc.dev/editor/custom-ui/content-controls).
19+
1820
Inserts resolve the drop point with `ui.viewport.positionAt({ x, y })` and create the control with `editor.doc.create.contentControl({ kind, at, content, tag, lockMode })`. A field inserts inline at the exact caret; a clause snaps to a block boundary so it lands as a clean section instead of splitting a paragraph. Clicking a control in the document highlights its chip or card (`content-control:click`).
1921

2022
A clause is assembled from structured `parts`: prose plus `{ field }` slots. Inserting "Permitted Use" creates the block and then wraps each slot as a nested, locked inline smart field, so the inserted clause carries real Receiving party and Purpose fields, just like the seeded one. Filling those fields in the Values tab updates the clause and the header sentence together.

demos/contract-templates/src/style.css

Lines changed: 32 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -263,12 +263,12 @@ input:focus {
263263
/* -----------------------------------------------------------------------
264264
Host-owned SDT styling.
265265
This demo turns off SuperDoc's built-in content-control chrome
266-
(`modules.contentControls.chrome: 'none'` in main.ts) and paints its
267-
own. The painter adds `.superdoc-cc-chrome-none` to the mount and resets
268-
border/padding/radius/background on the SDT wrappers; scoping under that
269-
class keeps these rules above the reset in specificity and cascade, and
270-
restores the box properties the reset strips. No painter label element
271-
exists under chrome-none, so there is nothing to style for it.
266+
(`modules.contentControls.chrome: 'none'` in main.ts) and paints its own,
267+
driving it entirely through SuperDoc's public --sd-content-controls-custom-*
268+
variables. We set those variables per tag (on the data-sdt-tag selector) and
269+
the painter applies them across rest, hover, selected, and locked-hover. So
270+
there is no !important, and no .ProseMirror-selectednode / .sdt-group-hover
271+
state selectors - this is the copy-pasteable pattern for styling custom SDTs.
272272
----------------------------------------------------------------------- */
273273

274274
/* Smart-tag token look, shared by the in-editor inline SDT and the Smart-tags
@@ -290,36 +290,23 @@ input:focus {
290290
--tag-block-bg-hover: color-mix(in srgb, var(--tag-color) 8%, var(--demo-bg));
291291
--tag-radius: 6px;
292292
}
293-
/* Inline smart fields: token pill (painted SDT wrapper under chrome:'none').
294-
The box (border width + padding) is identical in every state so clicking /
295-
hovering a field never shifts layout. Hover changes only the fill. */
293+
/* Inline smart fields: token pill, painted by SuperDoc under chrome:'none'. We
294+
set the public --sd-content-controls-custom-inline-* variables, and SuperDoc
295+
applies them across rest, hover, selected, and locked-hover. No !important, no
296+
.ProseMirror-selectednode / .sdt-group-hover state selectors, and the box
297+
(border + padding) stays identical in every state, so a field never shifts on
298+
hover or click. Only the background changes; the border is constant. */
296299
.superdoc-cc-chrome-none .superdoc-structured-content-inline[data-sdt-tag*='smartField'] {
297-
padding: 1px 6px;
298-
border: 1px solid var(--tag-border);
299-
border-radius: var(--tag-radius);
300-
background-color: var(--tag-bg);
300+
--sd-content-controls-custom-inline-border: 1px solid var(--tag-border);
301+
--sd-content-controls-custom-inline-radius: var(--tag-radius);
302+
--sd-content-controls-custom-inline-padding: 1px 6px;
303+
--sd-content-controls-custom-inline-bg: var(--tag-bg);
304+
--sd-content-controls-custom-inline-hover-bg: var(--tag-bg-hover);
305+
--sd-content-controls-custom-inline-selected-bg: var(--tag-bg-hover);
306+
/* Text colour is not part of the custom border/fill layer and chrome:'none'
307+
does not reset it, so it can be set directly and stays stable across states. */
301308
color: var(--tag-fg);
302309
}
303-
.superdoc-cc-chrome-none .superdoc-structured-content-inline[data-sdt-tag*='smartField']:hover {
304-
/* Under chrome:'none' SuperDoc resets the field's border + fill (including on
305-
hover) so the consumer owns the look. We re-assert the box so hover
306-
never moves or recolors the field. The !important wins over that reset
307-
without coupling to SuperDoc's selector specificity — a custom-UI styling
308-
rough edge today (no first-class per-control styling hook yet). */
309-
border: 1px solid var(--tag-border) !important;
310-
background-color: var(--tag-bg-hover) !important;
311-
}
312-
/* Selecting a field is a ProseMirror NodeSelection (.ProseMirror-selectednode).
313-
Under chrome:'none' SuperDoc resets the border + fill to transparent in that
314-
state too; without re-asserting, the field loses its fill and the box can
315-
shift (~2px) on click. Keep the same box and a controlled blue "selected"
316-
fill so hover/click/selected stay on-brand and never move the field. */
317-
.superdoc-cc-chrome-none .superdoc-structured-content-inline[data-sdt-tag*='smartField'].ProseMirror-selectednode {
318-
/* !important to win over the chrome-none reset; same rough edge as hover. */
319-
border: 1px solid var(--tag-border) !important;
320-
background-color: var(--tag-bg-hover) !important;
321-
color: var(--tag-fg) !important;
322-
}
323310

324311
/* Smart-tags palette (sidebar). Chips reuse the --tag-* token look above, so a
325312
palette chip and the field it inserts are visually identical. */
@@ -364,23 +351,17 @@ input:focus {
364351
.smart-tag.is-active { box-shadow: 0 0 0 2px var(--demo-accent); }
365352
.smart-tag:focus-visible { outline: 1px solid var(--demo-accent); outline-offset: 1px; }
366353

367-
/* Block clauses: a quiet card with a blue left rail, same field language as
368-
the inline pills, but a region not a token: soft border, faint fill, a 4px
369-
blue spine. */
354+
/* Block clauses: a quiet card with a blue left rail, same field language as the
355+
inline pills but a region not a token (soft border, faint fill, a 4px blue
356+
spine). Set the public --sd-content-controls-custom-block-* variables; SuperDoc
357+
applies them across rest, hover, selected, and locked-hover - so, like the
358+
inline fields, there's no !important and no .sdt-group-hover /
359+
.ProseMirror-selectednode state selectors, and the box stays constant. */
370360
.superdoc-cc-chrome-none .superdoc-structured-content-block[data-sdt-tag*='reusableSection'] {
371-
border: 1px solid var(--tag-block-border);
372-
border-left: 4px solid var(--tag-color);
373-
border-radius: var(--tag-radius);
374-
background-color: var(--tag-block-bg);
375-
}
376-
/* Under chrome:'none' SuperDoc resets the block's border + fill on hover
377-
(.sdt-group-hover) and select (.ProseMirror-selectednode) — via ::before/
378-
::after pseudo-elements, different mechanics than inline. Re-assert the exact
379-
box (no jitter) and lift the fill slightly to show activity. !important wins
380-
over the reset; same custom-UI rough edge as the inline rules above. */
381-
.superdoc-cc-chrome-none .superdoc-structured-content-block[data-sdt-tag*='reusableSection'].sdt-group-hover,
382-
.superdoc-cc-chrome-none .superdoc-structured-content-block[data-sdt-tag*='reusableSection'].ProseMirror-selectednode {
383-
border: 1px solid var(--tag-block-border) !important;
384-
border-left: 4px solid var(--tag-color) !important;
385-
background-color: var(--tag-block-bg-hover) !important;
361+
--sd-content-controls-custom-block-border: 1px solid var(--tag-block-border);
362+
--sd-content-controls-custom-block-border-left: 4px solid var(--tag-color);
363+
--sd-content-controls-custom-block-radius: var(--tag-radius);
364+
--sd-content-controls-custom-block-bg: var(--tag-block-bg);
365+
--sd-content-controls-custom-block-hover-bg: var(--tag-block-bg-hover);
366+
--sd-content-controls-custom-block-selected-bg: var(--tag-block-bg-hover);
386367
}

0 commit comments

Comments
 (0)