Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions apps/docs/editor/custom-ui/content-controls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,33 @@ new SuperDoc({

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

## Style the controls in place

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.

```css
/* A field your app tagged { kind: 'smartField', ... } */
.superdoc-cc-chrome-none .superdoc-structured-content-inline[data-sdt-tag*='smartField'] {
--sd-content-controls-custom-inline-border: 1px solid #1355ff;
--sd-content-controls-custom-inline-bg: color-mix(in srgb, #1355ff 12%, transparent);
--sd-content-controls-custom-inline-hover-bg: color-mix(in srgb, #1355ff 20%, transparent);
--sd-content-controls-custom-inline-radius: 4px;
--sd-content-controls-custom-inline-padding: 1px 6px;
}

/* A clause your app tagged { kind: 'reusableSection', ... } */
.superdoc-cc-chrome-none .superdoc-structured-content-block[data-sdt-tag*='reusableSection'] {
--sd-content-controls-custom-block-border: 1px solid #d6e0ff;
--sd-content-controls-custom-block-border-left: 4px solid #1355ff; /* accent rail */
--sd-content-controls-custom-block-bg: color-mix(in srgb, #1355ff 4%, transparent);
--sd-content-controls-custom-block-radius: 6px;
}
```

`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`.

This is the path for `chrome: 'none'`. To theme the **built-in** chrome instead (`chrome: 'default'`), use the `--sd-content-controls-*` variables (without `custom`).

## Pick the right surface

| Goal | API |
Expand Down
30 changes: 30 additions & 0 deletions packages/layout-engine/painters/dom/src/styles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,36 @@ describe('ensureSdtContainerStyles', () => {
expect(lastChromeShowing).toBeGreaterThan(-1);
expect(chromeNoneSuppression).toBeGreaterThan(lastChromeShowing);
});

it('exposes a --sd-content-controls-custom-* styling surface under chrome-none (SD-3322)', () => {
ensureSdtContainerStyles(document);
const styleEl = document.querySelector('[data-superdoc-sdt-container-styles="true"]');
const cssText = styleEl?.textContent ?? '';

// Inline rest reads the custom vars; the default-preserving fallbacks
// (0-width transparent border, no background/radius/padding) keep
// chrome-none visually empty when no variable is set.
expect(cssText).toContain('background: var(--sd-content-controls-custom-inline-bg, none);');
expect(cssText).toContain('border: var(--sd-content-controls-custom-inline-border, 0 solid transparent);');
expect(cssText).toContain('padding: var(--sd-content-controls-custom-inline-padding, 0);');
expect(cssText).toContain('border-radius: var(--sd-content-controls-custom-inline-radius, 0);');

// Hover and selected re-assert the SAME border var (constant box, no jitter)
// and read the background vars, which cascade from the rest background.
expect(cssText).toContain(
'background: var(--sd-content-controls-custom-inline-hover-bg, var(--sd-content-controls-custom-inline-bg, none));',
);
expect(cssText).toContain(
'background: var(--sd-content-controls-custom-inline-selected-bg, var(--sd-content-controls-custom-inline-hover-bg, var(--sd-content-controls-custom-inline-bg, none)));',
);

// Block exposes the same set plus an accent rail (-border-left) that falls
// back to the regular border.
expect(cssText).toContain('background: var(--sd-content-controls-custom-block-bg, none);');
expect(cssText).toContain(
'border-left: var(--sd-content-controls-custom-block-border-left, var(--sd-content-controls-custom-block-border, 0 solid transparent));',
);
});
});

describe('ensureTrackChangeStyles', () => {
Expand Down
56 changes: 41 additions & 15 deletions packages/layout-engine/painters/dom/src/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -787,30 +787,56 @@ const SDT_CONTAINER_STYLES = `
/* Global content-control chrome opt-out: preserve SDT wrappers/datasets while
* suppressing built-in visual chrome on structured-content controls. Their
* label elements are not emitted by renderer/helpers when this class is
* present (DOM non-emission), and these rules neutralize
* border/padding/hover/selection visuals. documentSection chrome (e.g. the
* locked-section tooltip) is intentionally preserved and not in scope. */
.superdoc-cc-chrome-none .superdoc-structured-content-inline,
* present (DOM non-emission). documentSection chrome (e.g. the locked-section
* tooltip) is intentionally preserved and not in scope.
*
* Custom styling surface (SD-3322): instead of fully erasing the look, these
* rules read --sd-content-controls-custom-* variables whose defaults reproduce
* the empty look (0-width transparent border, no background, no radius/padding).
* So chrome:'none' stays visually empty by default, but a consumer can paint
* their own field/clause look by setting those variables on the painted wrapper
* (target it via data-sdt-* attributes) - no !important, and no need to fight
* the .ProseMirror-selectednode / .sdt-group-hover state classes, because the
* painter reads the variables across rest, hover, and selected. The border is a
* full shorthand (e.g. "1px solid #1355ff"); its default "0 solid transparent"
* is identical in layout to no border. It's re-asserted in every state so the
* box never shifts (no jitter); only the background changes on hover/selected.
* Block controls add a -border-left override for an accent rail. */
.superdoc-cc-chrome-none .superdoc-structured-content-inline {
padding: var(--sd-content-controls-custom-inline-padding, 0);
border: var(--sd-content-controls-custom-inline-border, 0 solid transparent);
border-radius: var(--sd-content-controls-custom-inline-radius, 0);
background: var(--sd-content-controls-custom-inline-bg, none);
}
.superdoc-cc-chrome-none .superdoc-structured-content-block {
border: none;
padding: 0;
border-radius: 0;
background: none;
padding: var(--sd-content-controls-custom-block-padding, 0);
border: var(--sd-content-controls-custom-block-border, 0 solid transparent);
border-left: var(--sd-content-controls-custom-block-border-left, var(--sd-content-controls-custom-block-border, 0 solid transparent));
border-radius: var(--sd-content-controls-custom-block-radius, 0);
background: var(--sd-content-controls-custom-block-bg, none);
}

.superdoc-cc-chrome-none .superdoc-structured-content-inline:hover,
.superdoc-cc-chrome-none .superdoc-structured-content-inline[data-lock-mode]:hover {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
border: var(--sd-content-controls-custom-inline-border, 0 solid transparent);
background: var(--sd-content-controls-custom-inline-hover-bg, var(--sd-content-controls-custom-inline-bg, none));
}
Comment thread
caio-pizzol marked this conversation as resolved.
.superdoc-cc-chrome-none .superdoc-structured-content-block:hover,
.superdoc-cc-chrome-none .superdoc-structured-content-block.sdt-group-hover,
.superdoc-cc-chrome-none .superdoc-structured-content-block[data-lock-mode].sdt-group-hover,
.superdoc-cc-chrome-none .superdoc-structured-content-inline[data-lock-mode]:hover {
border: none;
background: none;
.superdoc-cc-chrome-none .superdoc-structured-content-block[data-lock-mode].sdt-group-hover {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
border: var(--sd-content-controls-custom-block-border, 0 solid transparent);
border-left: var(--sd-content-controls-custom-block-border-left, var(--sd-content-controls-custom-block-border, 0 solid transparent));
background: var(--sd-content-controls-custom-block-hover-bg, var(--sd-content-controls-custom-block-bg, none));
}

.superdoc-cc-chrome-none .superdoc-structured-content-inline.ProseMirror-selectednode,
.superdoc-cc-chrome-none .superdoc-structured-content-inline.ProseMirror-selectednode {
border: var(--sd-content-controls-custom-inline-border, 0 solid transparent);
background: var(--sd-content-controls-custom-inline-selected-bg, var(--sd-content-controls-custom-inline-hover-bg, var(--sd-content-controls-custom-inline-bg, none)));
}
.superdoc-cc-chrome-none .superdoc-structured-content-block.ProseMirror-selectednode {
border-color: transparent;
background: none;
border: var(--sd-content-controls-custom-block-border, 0 solid transparent);
border-left: var(--sd-content-controls-custom-block-border-left, var(--sd-content-controls-custom-block-border, 0 solid transparent));
background: var(--sd-content-controls-custom-block-selected-bg, var(--sd-content-controls-custom-block-hover-bg, var(--sd-content-controls-custom-block-bg, none)));
}

/* Hover highlight for SDT containers.
Expand Down
19 changes: 18 additions & 1 deletion packages/superdoc/src/assets/styles/helpers/variables.css
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@
--sd-tracked-changes-delete-background-focused: #cb0e4744;
--sd-tracked-changes-format-background-focused: #ffd70033;

/* Styles: content controls (SDT) — blue accent, intentionally standalone */
/* Styles: content controls (SDT) — blue accent, intentionally standalone.
These theme the BUILT-IN chrome (modules.contentControls.chrome: 'default'). */
--sd-content-controls-block-border: #629be7;
--sd-content-controls-block-hover-border: transparent;
--sd-content-controls-block-hover-bg: var(--sd-ui-hover-bg);
Expand All @@ -211,6 +212,22 @@
--sd-content-controls-label-text: #ffffff;
--sd-content-controls-lock-hover-bg: rgba(98, 155, 231, 0.08);

/* Custom SDT styling under chrome:'none' (SD-3322). The built-in chrome is
off, so set these on the painted wrapper (target via data-sdt-* attributes)
to paint your own field/clause look. The painter applies them across rest,
hover, and selected, so no !important and no state-class selectors are
needed. Unset by default (chrome:'none' stays visually empty). `border` is a
full shorthand, e.g. `1px solid #1355ff`; block adds a `-border-left` for an
accent rail. Inline:
--sd-content-controls-custom-inline-bg
--sd-content-controls-custom-inline-border
--sd-content-controls-custom-inline-radius
--sd-content-controls-custom-inline-padding
--sd-content-controls-custom-inline-hover-bg
--sd-content-controls-custom-inline-selected-bg
Block (same set, plus):
--sd-content-controls-custom-block-border-left */

/* UI: surface system — dialog and floating overlays */
--sd-ui-surface-bg: var(--sd-popover-bg, var(--sd-ui-bg));
--sd-ui-surface-border: var(--sd-ui-border);
Expand Down
Loading