All files / roosterjs-content-model-dom/lib/domUtils/style transformColor.ts

100% Statements 36/36
85.71% Branches 24/28
100% Functions 6/6
100% Lines 35/35

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 1191x 1x 1x                                                 1x               21x 21x 21x 25x 25x 25x   25x 25x                 25x 4x     25x     21x               4x 4x 16x 16x 16x 12x 12x 12x             12x 12x                           25x 21x     25x 4x   4x 4x     4x               25x 25x    
import { BorderColorKeyMap, BorderKeys } from '../../formatHandlers/utils/borderKeys';
import { isElementOfType } from '../isElementOfType';
import {
    adaptColor,
    getColor,
    getLightModeColor,
    setColor,
} from '../../formatHandlers/utils/color';
import type { DarkColorHandler } from 'roosterjs-content-model-types';
 
/**
 * Configuration options for controlling which elements and styles undergo color transformation.
 * By default, text and background colors are transformed for all elements.
 */
export interface TransformColorOptions {
    tableBorders: boolean;
}
 
/**
 * Edit and transform color of elements between light mode and dark mode
 * By default, text and background colors are transformed for all elements.
 * @param rootNode The root DOM node to transform
 * @param includeSelf True to transform the root node as well, otherwise false
 * @param direction To specify the transform direction, light to dark, or dark to light
 * @param darkColorHandler The dark color handler object to help do color transformation
 * @param transformColorOptions Configuration options for controlling which elements and styles undergo color transformation.
 */
export function transformColor(
    rootNode: Node,
    includeSelf: boolean,
    direction: 'lightToDark' | 'darkToLight',
    darkColorHandler?: DarkColorHandler,
    transformColorOptions?: TransformColorOptions,
    defaultTextColor?: string
) {
    const toDarkMode = direction == 'lightToDark';
    const tableBorders = transformColorOptions?.tableBorders || false;
    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,
            comparingColor
        );
 
        if (tableBorders) {
            transformBorderColor(element, toDarkMode, darkColorHandler);
        }
 
        return comparingColor;
    };
 
    iterateElements(rootNode, transformer, includeSelf, defaultTextColor);
}
 
function transformBorderColor(
    element: HTMLElement,
    toDarkMode: boolean,
    darkColorHandler?: DarkColorHandler
) {
    Eif (isElementOfType(element, 'td') || isElementOfType(element, 'th')) {
        BorderKeys.forEach(key => {
            const borderColorProperty = BorderColorKeyMap[key];
            const style = element.style.getPropertyValue(borderColorProperty);
            if (style) {
                const lightColor = getLightModeColor(style, !toDarkMode, darkColorHandler);
                Eif (lightColor) {
                    const transformedColor = adaptColor(
                        element,
                        lightColor,
                        'border',
                        toDarkMode,
                        darkColorHandler
                    );
                    Eif (transformedColor) {
                        element.style.setProperty(borderColorProperty, transformedColor);
                    }
                }
            }
        });
    }
}
 
function iterateElements(
    root: Node,
    transformer: (element: HTMLElement, parentTextColor?: string) => string | undefined,
    includeSelf?: boolean,
    parentTextColor?: string
) {
    if (includeSelf && isHTMLElement(root)) {
        parentTextColor = transformer(root, parentTextColor);
    }
 
    for (let child = root.firstChild; child; child = child.nextSibling) {
        let textColor = parentTextColor;
 
        Eif (isHTMLElement(child)) {
            textColor = transformer(child, parentTextColor);
        }
 
        iterateElements(child, transformer, false, textColor);
    }
}
 
// This is not a strict check, we just need to make sure this element has style so that we can set style to it
// We don't use safeInstanceOf() here since this function will be called very frequently when extract html content
// in dark mode, so we need to make sure this check is fast enough
function isHTMLElement(node: Node): node is HTMLElement {
    const htmlElement = <HTMLElement>node;
    return node.nodeType == Node.ELEMENT_NODE && !!htmlElement.style;
}