From 80835093672c29b7596ac63616a934dd1621e3d0 Mon Sep 17 00:00:00 2001 From: Artem Nistuley Date: Mon, 25 May 2026 21:45:42 +0300 Subject: [PATCH] feat: add non-hover field color for sdt --- .../painters/dom/src/styles.test.ts | 9 ++++ .../layout-engine/painters/dom/src/styles.ts | 3 ++ .../src/styles/field-types.css | 24 +++++++--- .../template-builder/src/tests/utils.test.ts | 13 +++++- packages/template-builder/src/utils.ts | 7 ++- ...mplate-builder-block-field-styling.spec.ts | 44 +++++++++++++++++++ 6 files changed, 91 insertions(+), 9 deletions(-) diff --git a/packages/layout-engine/painters/dom/src/styles.test.ts b/packages/layout-engine/painters/dom/src/styles.test.ts index 14f084b300..55a313d8c4 100644 --- a/packages/layout-engine/painters/dom/src/styles.test.ts +++ b/packages/layout-engine/painters/dom/src/styles.test.ts @@ -40,6 +40,15 @@ describe('ensureSdtContainerStyles', () => { ); expect(cssText).toContain('background: none;'); }); + + it('keeps hidden-appearance inline SDTs transparent at rest', () => { + ensureSdtContainerStyles(document); + const styleEl = document.querySelector('[data-superdoc-sdt-container-styles="true"]'); + const cssText = styleEl?.textContent ?? ''; + + expect(cssText).toContain(".superdoc-structured-content-inline[data-appearance='hidden'] {"); + expect(cssText).toContain('background-color: transparent;'); + }); }); describe('ensureTrackChangeStyles', () => { diff --git a/packages/layout-engine/painters/dom/src/styles.ts b/packages/layout-engine/painters/dom/src/styles.ts index cd916debff..f7768b5b4f 100644 --- a/packages/layout-engine/painters/dom/src/styles.ts +++ b/packages/layout-engine/painters/dom/src/styles.ts @@ -501,6 +501,7 @@ const SDT_CONTAINER_STYLES = ` box-sizing: border-box; border-radius: 4px; border: 1px solid transparent; + background-color: var(--sd-content-controls-block-bg, transparent); position: relative; } @@ -596,6 +597,7 @@ const SDT_CONTAINER_STYLES = ` box-sizing: border-box; border-radius: 4px; border: 1px solid transparent; + background-color: var(--sd-content-controls-inline-bg, transparent); position: relative; display: inline; font-size: initial; @@ -651,6 +653,7 @@ const SDT_CONTAINER_STYLES = ` padding: 0; border: none; border-radius: 0; + background-color: transparent; } .superdoc-structured-content-inline[data-appearance='hidden']:hover { background-color: transparent; diff --git a/packages/template-builder/src/styles/field-types.css b/packages/template-builder/src/styles/field-types.css index 4aab93e920..7d5010592b 100644 --- a/packages/template-builder/src/styles/field-types.css +++ b/packages/template-builder/src/styles/field-types.css @@ -2,12 +2,18 @@ .superdoc-structured-content-block.superdoc-structured-content-block { border-color: var(--superdoc-field-owner-color, #629be7); --sd-content-controls-inline-border: var(--superdoc-field-owner-color, #629be7); + --sd-content-controls-inline-bg: color-mix(in srgb, var(--superdoc-field-owner-color, #629be7) 8%, transparent); --sd-content-controls-inline-hover-border: var(--superdoc-field-owner-color, #629be7); - --sd-content-controls-inline-hover-bg: color-mix(in srgb, var(--superdoc-field-owner-color, #629be7) 8%, transparent); + --sd-content-controls-inline-hover-bg: color-mix( + in srgb, + var(--superdoc-field-owner-color, #629be7) 12%, + transparent + ); --sd-content-controls-block-border: var(--superdoc-field-owner-color, #629be7); + --sd-content-controls-block-bg: color-mix(in srgb, var(--superdoc-field-owner-color, #629be7) 8%, transparent); --sd-content-controls-block-hover-border: var(--superdoc-field-owner-color, #629be7); - --sd-content-controls-block-hover-bg: color-mix(in srgb, var(--superdoc-field-owner-color, #629be7) 8%, transparent); - --sd-content-controls-lock-hover-bg: color-mix(in srgb, var(--superdoc-field-owner-color, #629be7) 8%, transparent); + --sd-content-controls-block-hover-bg: color-mix(in srgb, var(--superdoc-field-owner-color, #629be7) 12%, transparent); + --sd-content-controls-lock-hover-bg: color-mix(in srgb, var(--superdoc-field-owner-color, #629be7) 12%, transparent); --sd-content-controls-label-border: var(--superdoc-field-owner-color, #629be7); --sd-content-controls-label-bg: color-mix(in srgb, var(--superdoc-field-owner-color, #629be7) 87%, transparent); } @@ -26,16 +32,22 @@ .superdoc-structured-content-block[data-sdt-tag*='"fieldType":"signer"'] { border-color: var(--superdoc-field-signer-color, #d97706); --sd-content-controls-inline-border: var(--superdoc-field-signer-color, #d97706); + --sd-content-controls-inline-bg: color-mix(in srgb, var(--superdoc-field-signer-color, #d97706) 8%, transparent); --sd-content-controls-inline-hover-border: var(--superdoc-field-signer-color, #d97706); --sd-content-controls-inline-hover-bg: color-mix( in srgb, - var(--superdoc-field-signer-color, #d97706) 8%, + var(--superdoc-field-signer-color, #d97706) 12%, transparent ); --sd-content-controls-block-border: var(--superdoc-field-signer-color, #d97706); + --sd-content-controls-block-bg: color-mix(in srgb, var(--superdoc-field-signer-color, #d97706) 8%, transparent); --sd-content-controls-block-hover-border: var(--superdoc-field-signer-color, #d97706); - --sd-content-controls-block-hover-bg: color-mix(in srgb, var(--superdoc-field-signer-color, #d97706) 8%, transparent); - --sd-content-controls-lock-hover-bg: color-mix(in srgb, var(--superdoc-field-signer-color, #d97706) 8%, transparent); + --sd-content-controls-block-hover-bg: color-mix( + in srgb, + var(--superdoc-field-signer-color, #d97706) 12%, + transparent + ); + --sd-content-controls-lock-hover-bg: color-mix(in srgb, var(--superdoc-field-signer-color, #d97706) 12%, transparent); --sd-content-controls-label-border: var(--superdoc-field-signer-color, #d97706); --sd-content-controls-label-bg: color-mix(in srgb, var(--superdoc-field-signer-color, #d97706) 87%, transparent); } diff --git a/packages/template-builder/src/tests/utils.test.ts b/packages/template-builder/src/tests/utils.test.ts index ef796174b5..f2030e96d0 100644 --- a/packages/template-builder/src/tests/utils.test.ts +++ b/packages/template-builder/src/tests/utils.test.ts @@ -238,9 +238,18 @@ describe('generateFieldColorCSS', () => { const css = generateFieldColorCSS({ signer: '#d97706' }, '.scope'); expect(css).toContain('--sd-content-controls-block-border: #d97706;'); + expect(css).toContain('--sd-content-controls-block-bg: color-mix(in srgb, #d97706 8%, transparent);'); expect(css).toContain('--sd-content-controls-block-hover-border: #d97706;'); - expect(css).toContain('--sd-content-controls-block-hover-bg: color-mix(in srgb, #d97706 8%, transparent);'); - expect(css).toContain('--sd-content-controls-lock-hover-bg: color-mix(in srgb, #d97706 8%, transparent);'); + expect(css).toContain('--sd-content-controls-block-hover-bg: color-mix(in srgb, #d97706 12%, transparent);'); + expect(css).toContain('--sd-content-controls-inline-bg: color-mix(in srgb, #d97706 8%, transparent);'); + expect(css).toContain('--sd-content-controls-lock-hover-bg: color-mix(in srgb, #d97706 12%, transparent);'); + }); + + it('sets resting background variables for owner default rules', () => { + const css = generateFieldColorCSS({ owner: '#629be7' }, '.scope'); + + expect(css).toContain('--sd-content-controls-inline-bg: color-mix(in srgb, #629be7 8%, transparent);'); + expect(css).toContain('--sd-content-controls-block-bg: color-mix(in srgb, #629be7 8%, transparent);'); }); it('keeps label text color configurable through the content control token', () => { diff --git a/packages/template-builder/src/utils.ts b/packages/template-builder/src/utils.ts index 2b0e2129c4..f18977f5a5 100644 --- a/packages/template-builder/src/utils.ts +++ b/packages/template-builder/src/utils.ts @@ -87,7 +87,10 @@ const BLOCK_LABEL = '.superdoc-structured-content-block__label'; const LEGACY_BLOCK_LABEL = '.superdoc-structured-content__label'; function buildColorRules(scope: string, selector: string, color: string): string { - const hoverFill = `color-mix(in srgb, ${color} 8%, transparent)`; + // Keep base/hover fills separate so we can tune resting vs hover opacity independently later. + // SD-2533: resting uses 8%, hover uses 12% for a slightly stronger interaction cue. + const baseFill = `color-mix(in srgb, ${color} 8%, transparent)`; + const hoverFill = `color-mix(in srgb, ${color} 12%, transparent)`; const labelFill = `color-mix(in srgb, ${color} 87%, transparent)`; return ` @@ -95,9 +98,11 @@ ${scope} ${SDT_INLINE}${selector}, ${scope} ${SDT_BLOCK}${selector} { border-color: ${color}; --sd-content-controls-inline-border: ${color}; + --sd-content-controls-inline-bg: ${baseFill}; --sd-content-controls-inline-hover-border: ${color}; --sd-content-controls-inline-hover-bg: ${hoverFill}; --sd-content-controls-block-border: ${color}; + --sd-content-controls-block-bg: ${baseFill}; --sd-content-controls-block-hover-border: ${color}; --sd-content-controls-block-hover-bg: ${hoverFill}; --sd-content-controls-lock-hover-bg: ${hoverFill}; diff --git a/tests/visual/tests/behavior/template-builder-block-field-styling.spec.ts b/tests/visual/tests/behavior/template-builder-block-field-styling.spec.ts index 44371681df..ca415f391d 100644 --- a/tests/visual/tests/behavior/template-builder-block-field-styling.spec.ts +++ b/tests/visual/tests/behavior/template-builder-block-field-styling.spec.ts @@ -79,6 +79,38 @@ test('inline and block structured content field chrome use Template Builder fiel const getBlock = (fieldType: 'owner' | 'signer') => renderedPage.locator(`.superdoc-structured-content-block[data-sdt-tag*='"fieldType":"${fieldType}"']`).first(); + const expectRestingBackgrounds = async (fieldType: 'owner' | 'signer') => { + const inline = getInline(fieldType); + const block = getBlock(fieldType); + + await inline.evaluate((el) => el.classList.remove('ProseMirror-selectednode')); + await block.evaluate((el) => { + el.classList.remove('ProseMirror-selectednode'); + el.classList.remove('sdt-group-hover'); + }); + + const inlineBackground = await inline.evaluate((el) => getComputedStyle(el).backgroundColor); + const blockBackground = await block.evaluate((el) => getComputedStyle(el).backgroundColor); + + expect(inlineBackground).toBe(blockBackground); + expect(inlineBackground).not.toBe('rgba(0, 0, 0, 0)'); + }; + + const expectSelectedStateBackgrounds = async (fieldType: 'owner' | 'signer') => { + const inline = getInline(fieldType); + const block = getBlock(fieldType); + + await inline.evaluate((el) => el.classList.add('ProseMirror-selectednode')); + await block.evaluate((el) => { + el.classList.remove('sdt-group-hover'); + el.classList.add('ProseMirror-selectednode'); + }); + + await expect(inline).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); + const blockSelectedBackground = await block.evaluate((el) => getComputedStyle(el).backgroundColor); + expect(blockSelectedBackground).not.toBe('rgba(0, 0, 0, 0)'); + }; + const showInlineLabel = async (fieldType: 'owner' | 'signer') => { const inline = getInline(fieldType); await inline.evaluate((el) => el.classList.add('ProseMirror-selectednode')); @@ -132,7 +164,9 @@ test('inline and block structured content field chrome use Template Builder fiel const signerBlockLabel = signerBlock.locator('.superdoc-structured-content-block__label'); await expect(signerBlockLabel).toHaveCSS('color', 'rgb(255, 255, 0)'); await expect(signerBlockLabel).toHaveCSS('border-color', 'rgb(220, 38, 38)'); + await expectRestingBackgrounds('signer'); await expectHoverBackgroundParity('signer'); + await expectSelectedStateBackgrounds('signer'); const ownerInline = await showInlineLabel('owner'); await expect(ownerInline).toBeVisible(); @@ -149,7 +183,17 @@ test('inline and block structured content field chrome use Template Builder fiel const ownerBlockLabel = ownerBlock.locator('.superdoc-structured-content-block__label'); await expect(ownerBlockLabel).toHaveCSS('color', 'rgb(255, 255, 0)'); await expect(ownerBlockLabel).toHaveCSS('border-color', 'rgb(37, 99, 235)'); + await expectRestingBackgrounds('owner'); await expectHoverBackgroundParity('owner'); + await expectSelectedStateBackgrounds('owner'); + + for (const fieldType of ['signer', 'owner'] as const) { + await getInline(fieldType).evaluate((el) => el.classList.remove('ProseMirror-selectednode')); + await getBlock(fieldType).evaluate((el) => { + el.classList.remove('ProseMirror-selectednode'); + el.classList.remove('sdt-group-hover'); + }); + } await superdoc.screenshot('template-builder-owner-and-signer-inline-and-block-field-styling'); });