From cb30db7ae49188a602e0c06600221008d5098614 Mon Sep 17 00:00:00 2001 From: jiuqingsong Date: Thu, 5 Feb 2026 13:26:45 -0800 Subject: [PATCH 1/2] Dark color improvement --- .../lib/editor/Editor.ts | 3 +- .../test/editor/EditorTest.ts | 6 +- .../lib/domUtils/style/transformColor.ts | 32 ++- .../common/backgroundColorFormatHandler.ts | 9 +- .../lib/formatHandlers/utils/color.ts | 41 ++- .../test/domUtils/style/transformColorTest.ts | 31 ++- .../backgroundColorFormatHandlerTest.ts | 22 +- .../test/formatHandlers/utils/colorTest.ts | 234 +++++++++++++++--- .../lib/context/DarkColorHandler.ts | 4 +- .../lib/editor/EditorAdapter.ts | 6 +- 10 files changed, 317 insertions(+), 71 deletions(-) diff --git a/packages/roosterjs-content-model-core/lib/editor/Editor.ts b/packages/roosterjs-content-model-core/lib/editor/Editor.ts index 106ca6fb19bf..a0fe1140632b 100644 --- a/packages/roosterjs-content-model-core/lib/editor/Editor.ts +++ b/packages/roosterjs-content-model-core/lib/editor/Editor.ts @@ -310,7 +310,8 @@ export class Editor implements IEditor { core.darkColorHandler, { tableBorders: this.isExperimentalFeatureEnabled('TransformTableBorderColors'), - } + }, + core.format.defaultFormat.textColor ); core.lifecycle.isDarkMode = !!isDarkMode; diff --git a/packages/roosterjs-content-model-core/test/editor/EditorTest.ts b/packages/roosterjs-content-model-core/test/editor/EditorTest.ts index a646bd835d54..c6ff9050e646 100644 --- a/packages/roosterjs-content-model-core/test/editor/EditorTest.ts +++ b/packages/roosterjs-content-model-core/test/editor/EditorTest.ts @@ -1016,7 +1016,8 @@ describe('Editor', () => { mockedColorHandler, { tableBorders: false, - } + }, + undefined ); expect(mockedCore.lifecycle.isDarkMode).toEqual(true); expect(triggerEventSpy).toHaveBeenCalledTimes(1); @@ -1041,7 +1042,8 @@ describe('Editor', () => { mockedColorHandler, { tableBorders: false, - } + }, + undefined ); expect(triggerEventSpy).toHaveBeenCalledTimes(2); expect(triggerEventSpy).toHaveBeenCalledWith( diff --git a/packages/roosterjs-content-model-dom/lib/domUtils/style/transformColor.ts b/packages/roosterjs-content-model-dom/lib/domUtils/style/transformColor.ts index 5a22a54de6b6..709ea5e01141 100644 --- a/packages/roosterjs-content-model-dom/lib/domUtils/style/transformColor.ts +++ b/packages/roosterjs-content-model-dom/lib/domUtils/style/transformColor.ts @@ -30,23 +30,34 @@ export function transformColor( includeSelf: boolean, direction: 'lightToDark' | 'darkToLight', darkColorHandler?: DarkColorHandler, - transformColorOptions?: TransformColorOptions + transformColorOptions?: TransformColorOptions, + defaultTextColor?: string ) { const toDarkMode = direction == 'lightToDark'; const tableBorders = transformColorOptions?.tableBorders || false; - const transformer = (element: HTMLElement) => { + const transformer = (element: HTMLElement, parentTextColor?: string) => { const textColor = getColor(element, false /*isBackground*/, !toDarkMode, darkColorHandler); const backColor = getColor(element, true /*isBackground*/, !toDarkMode, darkColorHandler); + const comparingColor = textColor || parentTextColor; setColor(element, textColor, false /*isBackground*/, toDarkMode, darkColorHandler); - setColor(element, backColor, true /*isBackground*/, toDarkMode, darkColorHandler); + setColor( + element, + backColor, + true /*isBackground*/, + toDarkMode, + darkColorHandler, + comparingColor + ); if (tableBorders) { transformBorderColor(element, toDarkMode, darkColorHandler); } + + return comparingColor; }; - iterateElements(rootNode, transformer, includeSelf); + iterateElements(rootNode, transformer, includeSelf, defaultTextColor); } function transformBorderColor( @@ -79,19 +90,22 @@ function transformBorderColor( function iterateElements( root: Node, - transformer: (element: HTMLElement) => void, - includeSelf?: boolean + transformer: (element: HTMLElement, parentTextColor?: string) => string | undefined, + includeSelf?: boolean, + parentTextColor?: string ) { if (includeSelf && isHTMLElement(root)) { - transformer(root); + parentTextColor = transformer(root, parentTextColor); } for (let child = root.firstChild; child; child = child.nextSibling) { + let textColor = parentTextColor; + if (isHTMLElement(child)) { - transformer(child); + textColor = transformer(child, parentTextColor); } - iterateElements(child, transformer); + iterateElements(child, transformer, false, textColor); } } diff --git a/packages/roosterjs-content-model-dom/lib/formatHandlers/common/backgroundColorFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/backgroundColorFormatHandler.ts index a2d7577f254b..f81f7af323f0 100644 --- a/packages/roosterjs-content-model-dom/lib/formatHandlers/common/backgroundColorFormatHandler.ts +++ b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/backgroundColorFormatHandler.ts @@ -1,12 +1,14 @@ import { getColor, setColor } from '../utils/color'; import { shouldSetValue } from '../utils/shouldSetValue'; -import type { BackgroundColorFormat } from 'roosterjs-content-model-types'; +import type { BackgroundColorFormat, TextColorFormat } from 'roosterjs-content-model-types'; import type { FormatHandler } from '../FormatHandler'; /** * @internal */ -export const backgroundColorFormatHandler: FormatHandler = { +export const backgroundColorFormatHandler: FormatHandler< + BackgroundColorFormat & TextColorFormat +> = { parse: (format, element, context, defaultStyle) => { const backgroundColor = getColor( @@ -34,7 +36,8 @@ export const backgroundColorFormatHandler: FormatHandler format.backgroundColor, true /*isBackground*/, !!context.isDarkMode, - context.darkColorHandler + context.darkColorHandler, + format.textColor ); } }, diff --git a/packages/roosterjs-content-model-dom/lib/formatHandlers/utils/color.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/utils/color.ts index eb68b5fe7858..a4c4af14f89b 100644 --- a/packages/roosterjs-content-model-dom/lib/formatHandlers/utils/color.ts +++ b/packages/roosterjs-content-model-dom/lib/formatHandlers/utils/color.ts @@ -84,12 +84,12 @@ export function getLightModeColor( ) { if (DeprecatedColors.indexOf(color) > -1) { return fallback; - } else if (darkColorHandler) { + } else { const match = color.startsWith(VARIABLE_PREFIX) ? VARIABLE_REGEX.exec(color) : null; if (match) { color = match[2] || ''; - } else if (isDarkMode) { + } else if (isDarkMode && darkColorHandler) { // If editor is in dark mode but the color is not in dark color format, it is possible the color was inserted from external code // without any light color info. So we first try to see if there is a known dark color can match this color, and use its related // light color as light mode color. Otherwise we need to drop this color to avoid show "white on white" content. @@ -126,20 +126,23 @@ export function retrieveElementColor( * @param isBackground True to set background color, false to set text color * @param isDarkMode Whether element is in dark mode now * @param darkColorHandler @optional The dark color handler object to help manager dark mode color + * @param comparingColor @optional When generating dark color for background color, we can provide text color as comparingColor to make sure the generated dark border color has enough contrast with text color in dark mode */ export function setColor( element: HTMLElement, color: string | null | undefined, isBackground: boolean, isDarkMode: boolean, - darkColorHandler?: DarkColorHandler + darkColorHandler?: DarkColorHandler, + comparingColor?: string ) { const newColor = adaptColor( element, color, isBackground ? 'background' : 'text', isDarkMode, - darkColorHandler + darkColorHandler, + comparingColor ); element.removeAttribute(isBackground ? 'bgcolor' : 'color'); @@ -154,7 +157,8 @@ export function adaptColor( color: string | null | undefined, colorType: 'text' | 'background' | 'border', isDarkMode: boolean, - darkColorHandler?: DarkColorHandler + darkColorHandler?: DarkColorHandler, + comparingColor?: string ) { const match = color && color.startsWith(VARIABLE_PREFIX) ? VARIABLE_REGEX.exec(color) : null; const [_, existingKey, fallbackColor] = match ?? []; @@ -164,10 +168,22 @@ export function adaptColor( if (darkColorHandler && color) { const key = existingKey || - darkColorHandler.generateColorKey(color, undefined /*baseLValue*/, colorType, element); + darkColorHandler.generateColorKey( + color, + undefined /*baseLValue*/, + colorType, + element, + comparingColor + ); const darkModeColor = darkColorHandler.knownColors?.[key]?.darkModeColor || - darkColorHandler.getDarkColor(color, undefined /*baseLValue*/, colorType, element); + darkColorHandler.getDarkColor( + color, + undefined /*baseLValue*/, + colorType, + element, + comparingColor + ); darkColorHandler.updateKnownColor(isDarkMode, key, { lightModeColor: color, @@ -185,8 +201,15 @@ export function adaptColor( * @param lightColor The input light color * @returns Key of the color */ -export const defaultGenerateColorKey: ColorTransformFunction = lightColor => { - return `${COLOR_VAR_PREFIX}_${lightColor.replace(/[^\d\w]/g, '_')}`; +export const defaultGenerateColorKey: ColorTransformFunction = ( + lightColor, + _1, + _2, + _3, + comparingColor +) => { + const comparingColorKey = comparingColor ? `_${comparingColor.replace(/[^\d\w]/g, '_')}` : ''; + return `${COLOR_VAR_PREFIX}_${lightColor.replace(/[^\d\w]/g, '_')}${comparingColorKey}`; }; /** diff --git a/packages/roosterjs-content-model-dom/test/domUtils/style/transformColorTest.ts b/packages/roosterjs-content-model-dom/test/domUtils/style/transformColorTest.ts index d14c0952409f..0e612435bb19 100644 --- a/packages/roosterjs-content-model-dom/test/domUtils/style/transformColorTest.ts +++ b/packages/roosterjs-content-model-dom/test/domUtils/style/transformColorTest.ts @@ -32,8 +32,8 @@ describe('transform to dark mode', () => { runTest( element, - '
', - '
' + '
', + '
' ); }); @@ -44,8 +44,8 @@ describe('transform to dark mode', () => { runTest( element, - '
', - '
' + '
', + '
' ); }); @@ -58,8 +58,8 @@ describe('transform to dark mode', () => { runTest( element, - '
', - '
' + '
', + '
' ); }); @@ -98,8 +98,8 @@ describe('transform to dark mode', () => { runTest( element, - '
', - '
' + '
', + '
' ); }); @@ -142,6 +142,21 @@ describe('transform to dark mode', () => { '
' ); }); + + it('Has text color on parent element and background color on child element, transform with dark color handler', () => { + const element = document.createElement('div'); + element.style.color = 'red'; + + const child = document.createElement('div'); + child.style.backgroundColor = 'green'; + element.appendChild(child); + + runTest( + element, + '
', + '
' + ); + }); }); describe('transform to light mode', () => { diff --git a/packages/roosterjs-content-model-dom/test/formatHandlers/common/backgroundColorFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/common/backgroundColorFormatHandlerTest.ts index 3e5f24ccc61e..a027c04e5e71 100644 --- a/packages/roosterjs-content-model-dom/test/formatHandlers/common/backgroundColorFormatHandlerTest.ts +++ b/packages/roosterjs-content-model-dom/test/formatHandlers/common/backgroundColorFormatHandlerTest.ts @@ -7,6 +7,7 @@ import { BackgroundColorFormat, DomToModelContext, ModelToDomContext, + TextColorFormat, } from 'roosterjs-content-model-types'; describe('backgroundColorFormatHandler.parse', () => { @@ -84,7 +85,7 @@ describe('backgroundColorFormatHandler.parse', () => { describe('backgroundColorFormatHandler.apply', () => { let div: HTMLElement; let context: ModelToDomContext; - let format: BackgroundColorFormat; + let format: BackgroundColorFormat & TextColorFormat; beforeEach(() => { div = document.createElement('div'); @@ -122,4 +123,23 @@ describe('backgroundColorFormatHandler.apply', () => { expectHtml(div.outerHTML, expectedResult); }); + + it('Has both text and background color in dark mode', () => { + format.backgroundColor = 'red'; + format.textColor = 'green'; + context.isDarkMode = true; + context.darkColorHandler = { + updateKnownColor: () => {}, + getDarkColor: (lightColor: string) => `var(--darkColor_${lightColor}, ${lightColor})`, + generateColorKey: defaultGenerateColorKey, + } as any; + + backgroundColorFormatHandler.apply(format, div, context); + + const expectedResult = [ + '
', + ]; + + expectHtml(div.outerHTML, expectedResult); + }); }); diff --git a/packages/roosterjs-content-model-dom/test/formatHandlers/utils/colorTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/utils/colorTest.ts index 86ef59307b8f..745c8f52d613 100644 --- a/packages/roosterjs-content-model-dom/test/formatHandlers/utils/colorTest.ts +++ b/packages/roosterjs-content-model-dom/test/formatHandlers/utils/colorTest.ts @@ -88,10 +88,10 @@ describe('getColor without darkColorHandler', () => { const backDark = getColor(div, true, true); const textDark = getColor(div, false, true); - expect(backLight).toBe('var(--test, green)'); - expect(textLight).toBe('var(--test, red)'); - expect(backDark).toBe('var(--test, green)'); - expect(textDark).toBe('var(--test, red)'); + expect(backLight).toBe('green'); + expect(textLight).toBe('red'); + expect(backDark).toBe('green'); + expect(textDark).toBe('red'); }); it('has style color, deprecated value', () => { @@ -399,18 +399,42 @@ describe('setColor with darkColorHandler', () => { setColor(lightDiv, 'red', true, false, darkColorHandler); setColor(lightDiv, 'green', false, false, darkColorHandler); - setColor(darkDiv, 'red', true, true, darkColorHandler); + setColor(darkDiv, 'red', true, true, darkColorHandler, 'blue'); setColor(darkDiv, 'green', false, true, darkColorHandler); expect(lightDiv.outerHTML).toBe('
'); expect(darkDiv.outerHTML).toBe( - '
' + '
' ); expect(getDarkColorSpy).toHaveBeenCalledTimes(4); - expect(getDarkColorSpy).toHaveBeenCalledWith('green', undefined, 'text', lightDiv); - expect(getDarkColorSpy).toHaveBeenCalledWith('red', undefined, 'background', lightDiv); - expect(getDarkColorSpy).toHaveBeenCalledWith('green', undefined, 'text', darkDiv); - expect(getDarkColorSpy).toHaveBeenCalledWith('red', undefined, 'background', darkDiv); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'green', + undefined, + 'text', + lightDiv, + undefined + ); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'red', + undefined, + 'background', + lightDiv, + undefined + ); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'green', + undefined, + 'text', + darkDiv, + undefined + ); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'red', + undefined, + 'background', + darkDiv, + 'blue' + ); expect(updateKnownColorSpy).toHaveBeenCalledTimes(4); expect(updateKnownColorSpy).toHaveBeenCalledWith(false, '--darkColor_red', { lightModeColor: 'red', @@ -420,7 +444,7 @@ describe('setColor with darkColorHandler', () => { lightModeColor: 'green', darkModeColor: '--dark_green', }); - expect(updateKnownColorSpy).toHaveBeenCalledWith(true, '--darkColor_red', { + expect(updateKnownColorSpy).toHaveBeenCalledWith(true, '--darkColor_red_blue', { lightModeColor: 'red', darkModeColor: '--dark_red', }); @@ -445,10 +469,34 @@ describe('setColor with darkColorHandler', () => { ); expect(knownColors).toEqual({}); expect(getDarkColorSpy).toHaveBeenCalledTimes(4); - expect(getDarkColorSpy).toHaveBeenCalledWith('green', undefined, 'text', lightDiv); - expect(getDarkColorSpy).toHaveBeenCalledWith('red', undefined, 'background', lightDiv); - expect(getDarkColorSpy).toHaveBeenCalledWith('green', undefined, 'text', darkDiv); - expect(getDarkColorSpy).toHaveBeenCalledWith('red', undefined, 'background', darkDiv); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'green', + undefined, + 'text', + lightDiv, + undefined + ); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'red', + undefined, + 'background', + lightDiv, + undefined + ); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'green', + undefined, + 'text', + darkDiv, + undefined + ); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'red', + undefined, + 'background', + darkDiv, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledTimes(4); expect(updateKnownColorSpy).toHaveBeenCalledWith(false, '--test', { lightModeColor: 'red', @@ -491,10 +539,34 @@ describe('setColor with darkColorHandler', () => { '
' ); expect(getDarkColorSpy).toHaveBeenCalledTimes(4); - expect(getDarkColorSpy).toHaveBeenCalledWith('green', undefined, 'text', lightDiv); - expect(getDarkColorSpy).toHaveBeenCalledWith('red', undefined, 'background', lightDiv); - expect(getDarkColorSpy).toHaveBeenCalledWith('green', undefined, 'text', darkDiv); - expect(getDarkColorSpy).toHaveBeenCalledWith('red', undefined, 'background', darkDiv); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'green', + undefined, + 'text', + lightDiv, + undefined + ); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'red', + undefined, + 'background', + lightDiv, + undefined + ); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'green', + undefined, + 'text', + darkDiv, + undefined + ); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'red', + undefined, + 'background', + darkDiv, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledTimes(4); expect(updateKnownColorSpy).toHaveBeenCalledWith(false, '--darkColor_red', { lightModeColor: 'red', @@ -534,10 +606,34 @@ describe('setColor with darkColorHandler', () => { '
' ); expect(getDarkColorSpy).toHaveBeenCalledTimes(4); - expect(getDarkColorSpy).toHaveBeenCalledWith('green', undefined, 'text', lightDiv); - expect(getDarkColorSpy).toHaveBeenCalledWith('red', undefined, 'background', lightDiv); - expect(getDarkColorSpy).toHaveBeenCalledWith('green', undefined, 'text', darkDiv); - expect(getDarkColorSpy).toHaveBeenCalledWith('red', undefined, 'background', darkDiv); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'green', + undefined, + 'text', + lightDiv, + undefined + ); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'red', + undefined, + 'background', + lightDiv, + undefined + ); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'green', + undefined, + 'text', + darkDiv, + undefined + ); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'red', + undefined, + 'background', + darkDiv, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledTimes(4); expect(updateKnownColorSpy).toHaveBeenCalledWith(false, '--red_key', { lightModeColor: 'red', @@ -808,7 +904,13 @@ describe('adaptColor', () => { it('should return color as-is in light mode with dark color handler', () => { const result = adaptColor(element, 'red', 'border', false, darkColorHandler); expect(result).toBe('red'); - expect(getDarkColorSpy).toHaveBeenCalledWith('red', undefined, 'border', element); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'red', + undefined, + 'border', + element, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledWith(false, '--darkColor_red', { lightModeColor: 'red', darkModeColor: 'dark_red', @@ -818,7 +920,13 @@ describe('adaptColor', () => { it('should wrap color with CSS variable in dark mode', () => { const result = adaptColor(element, 'blue', 'border', true, darkColorHandler); expect(result).toBe('var(--darkColor_blue, blue)'); - expect(getDarkColorSpy).toHaveBeenCalledWith('blue', undefined, 'border', element); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'blue', + undefined, + 'border', + element, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledWith(true, '--darkColor_blue', { lightModeColor: 'blue', darkModeColor: 'dark_red', @@ -834,7 +942,13 @@ describe('adaptColor', () => { darkColorHandler ); expect(result).toBe('green'); - expect(getDarkColorSpy).toHaveBeenCalledWith('green', undefined, 'border', element); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'green', + undefined, + 'border', + element, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledWith(false, '--existing', { lightModeColor: 'green', darkModeColor: 'dark_red', @@ -850,7 +964,13 @@ describe('adaptColor', () => { darkColorHandler ); expect(result).toBe('var(--existing, green)'); - expect(getDarkColorSpy).toHaveBeenCalledWith('green', undefined, 'border', element); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'green', + undefined, + 'border', + element, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledWith(true, '--existing', { lightModeColor: 'green', darkModeColor: 'dark_red', @@ -875,7 +995,13 @@ describe('adaptColor', () => { it('should generate new dark color when not in known colors', () => { const result = adaptColor(element, 'purple', 'border', true, darkColorHandler); expect(result).toBe('var(--darkColor_purple, purple)'); - expect(getDarkColorSpy).toHaveBeenCalledWith('purple', undefined, 'border', element); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'purple', + undefined, + 'border', + element, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledWith(true, '--darkColor_purple', { lightModeColor: 'purple', darkModeColor: 'dark_red', @@ -885,7 +1011,7 @@ describe('adaptColor', () => { it('should handle text color in light mode', () => { const result = adaptColor(element, 'red', 'text', false, darkColorHandler); expect(result).toBe('red'); - expect(getDarkColorSpy).toHaveBeenCalledWith('red', undefined, 'text', element); + expect(getDarkColorSpy).toHaveBeenCalledWith('red', undefined, 'text', element, undefined); expect(updateKnownColorSpy).toHaveBeenCalledWith(false, '--darkColor_red', { lightModeColor: 'red', darkModeColor: 'dark_red', @@ -895,7 +1021,7 @@ describe('adaptColor', () => { it('should handle text color in dark mode', () => { const result = adaptColor(element, 'red', 'text', true, darkColorHandler); expect(result).toBe('var(--darkColor_red, red)'); - expect(getDarkColorSpy).toHaveBeenCalledWith('red', undefined, 'text', element); + expect(getDarkColorSpy).toHaveBeenCalledWith('red', undefined, 'text', element, undefined); expect(updateKnownColorSpy).toHaveBeenCalledWith(true, '--darkColor_red', { lightModeColor: 'red', darkModeColor: 'dark_red', @@ -905,7 +1031,13 @@ describe('adaptColor', () => { it('should handle background color in light mode', () => { const result = adaptColor(element, 'blue', 'background', false, darkColorHandler); expect(result).toBe('blue'); - expect(getDarkColorSpy).toHaveBeenCalledWith('blue', undefined, 'background', element); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'blue', + undefined, + 'background', + element, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledWith(false, '--darkColor_blue', { lightModeColor: 'blue', darkModeColor: 'dark_red', @@ -915,7 +1047,13 @@ describe('adaptColor', () => { it('should handle background color in dark mode', () => { const result = adaptColor(element, 'blue', 'background', true, darkColorHandler); expect(result).toBe('var(--darkColor_blue, blue)'); - expect(getDarkColorSpy).toHaveBeenCalledWith('blue', undefined, 'background', element); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'blue', + undefined, + 'background', + element, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledWith(true, '--darkColor_blue', { lightModeColor: 'blue', darkModeColor: 'dark_red', @@ -931,7 +1069,13 @@ describe('adaptColor', () => { darkColorHandler ); expect(result).toBe('black'); - expect(getDarkColorSpy).toHaveBeenCalledWith('black', undefined, 'text', element); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'black', + undefined, + 'text', + element, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledWith(false, '--text-color', { lightModeColor: 'black', darkModeColor: 'dark_red', @@ -947,7 +1091,13 @@ describe('adaptColor', () => { darkColorHandler ); expect(result).toBe('var(--text-color, black)'); - expect(getDarkColorSpy).toHaveBeenCalledWith('black', undefined, 'text', element); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'black', + undefined, + 'text', + element, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledWith(true, '--text-color', { lightModeColor: 'black', darkModeColor: 'dark_red', @@ -963,7 +1113,13 @@ describe('adaptColor', () => { darkColorHandler ); expect(result).toBe('white'); - expect(getDarkColorSpy).toHaveBeenCalledWith('white', undefined, 'background', element); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'white', + undefined, + 'background', + element, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledWith(false, '--bg-color', { lightModeColor: 'white', darkModeColor: 'dark_red', @@ -979,7 +1135,13 @@ describe('adaptColor', () => { darkColorHandler ); expect(result).toBe('var(--bg-color, white)'); - expect(getDarkColorSpy).toHaveBeenCalledWith('white', undefined, 'background', element); + expect(getDarkColorSpy).toHaveBeenCalledWith( + 'white', + undefined, + 'background', + element, + undefined + ); expect(updateKnownColorSpy).toHaveBeenCalledWith(true, '--bg-color', { lightModeColor: 'white', darkModeColor: 'dark_red', diff --git a/packages/roosterjs-content-model-types/lib/context/DarkColorHandler.ts b/packages/roosterjs-content-model-types/lib/context/DarkColorHandler.ts index f9f459cd9922..270f2ac70835 100644 --- a/packages/roosterjs-content-model-types/lib/context/DarkColorHandler.ts +++ b/packages/roosterjs-content-model-types/lib/context/DarkColorHandler.ts @@ -20,12 +20,14 @@ export interface Colors { * @param baseLValue Base value of light used for dark value calculation * @param colorType @optional Type of color, can be text, background, or border * @param element @optional Source HTML element of the color + * @param comparingColor @optional When generating dark color for background color, we can provide text color as comparingColor to make sure the generated dark border color has enough contrast with text color in dark mode */ export type ColorTransformFunction = ( lightColor: string, baseLValue?: number, colorType?: 'text' | 'background' | 'border', - element?: HTMLElement + element?: HTMLElement, + comparingColor?: string ) => string; /** diff --git a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts index 5c749c40634a..91b64831958a 100644 --- a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts +++ b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts @@ -1062,7 +1062,11 @@ export class EditorAdapter extends Editor implements ILegacyEditor { node, true /*includeSelf*/, direction == ColorTransformDirection.DarkToLight ? 'darkToLight' : 'lightToDark', - core.darkColorHandler + core.darkColorHandler, + { + tableBorders: this.isExperimentalFeatureEnabled('TransformTableBorderColors'), + }, + core.format.defaultFormat.textColor ); } } From 666baa4f23ac21ec9625b98ee960a87d60cfce36 Mon Sep 17 00:00:00 2001 From: jiuqingsong Date: Fri, 6 Feb 2026 10:00:20 -0800 Subject: [PATCH 2/2] improve --- .../test/domUtils/style/transformColorTest.ts | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/roosterjs-content-model-dom/test/domUtils/style/transformColorTest.ts b/packages/roosterjs-content-model-dom/test/domUtils/style/transformColorTest.ts index 0e612435bb19..a68dd6169ab0 100644 --- a/packages/roosterjs-content-model-dom/test/domUtils/style/transformColorTest.ts +++ b/packages/roosterjs-content-model-dom/test/domUtils/style/transformColorTest.ts @@ -147,14 +147,26 @@ describe('transform to dark mode', () => { const element = document.createElement('div'); element.style.color = 'red'; - const child = document.createElement('div'); - child.style.backgroundColor = 'green'; - element.appendChild(child); + const child1 = document.createElement('div'); + child1.style.color = 'green'; + element.appendChild(child1); + + const span1 = document.createElement('span'); + span1.style.backgroundColor = 'red'; + child1.appendChild(span1); + + const child2 = document.createElement('div'); + child2.style.color = 'yellow'; + element.appendChild(child2); + + const span2 = document.createElement('span'); + span2.style.backgroundColor = 'gray'; + child2.appendChild(span2); runTest( element, - '
', - '
' + '
', + '
' ); }); });