diff --git a/client/src/utils/__tests__/svg.test.ts b/client/src/utils/__tests__/svg.test.ts index 42e40977eb..ec3c0d6ad8 100644 --- a/client/src/utils/__tests__/svg.test.ts +++ b/client/src/utils/__tests__/svg.test.ts @@ -79,17 +79,41 @@ describe('isMonochromeSvg', () => { expect(isMonochromeSvg(svg)).toBe(true); }); - it('does not add an implicit fill for an explicitly stroked path', () => { + it('does not add an implicit fill for a stroked open path (no enclosed area)', () => { const svg = ''; expect(isMonochromeSvg(svg)).toBe(true); }); + it('rejects a closed stroked path that still renders its default black fill', () => { + const svg = ''; + expect(isMonochromeSvg(svg)).toBe(false); + }); + it('inherits an ancestor fill instead of assuming the default black', () => { const svg = ''; expect(isMonochromeSvg(svg)).toBe(true); }); }); + describe('currentColor (a visible, theme-following tone)', () => { + it('tints an icon painted entirely with currentColor', () => { + const svg = ''; + expect(isMonochromeSvg(svg)).toBe(true); + }); + + it('preserves currentColor mixed with an explicit light paint', () => { + const svg = + ''; + expect(isMonochromeSvg(svg)).toBe(false); + }); + + it('preserves currentColor mixed with a default-black glyph', () => { + const svg = + ''; + expect(isMonochromeSvg(svg)).toBe(false); + }); + }); + describe('multi-color icons (colors preserved)', () => { it('treats a saturated hex color as multi-color', () => { const svg = ''; diff --git a/client/src/utils/svg.ts b/client/src/utils/svg.ts index 7445106d75..1c8dab13ae 100644 --- a/client/src/utils/svg.ts +++ b/client/src/utils/svg.ts @@ -8,8 +8,12 @@ import DOMPurify from 'dompurify'; * with regexes. */ -/** Color keywords that carry no chromatic information and are ignored. */ -const IGNORABLE_COLORS = new Set(['none', 'transparent', 'inherit', 'currentcolor']); +/** Color keywords that paint nothing (or defer) and so contribute no tone. */ +const IGNORABLE_COLORS = new Set(['none', 'transparent', 'inherit']); + +/** `currentColor` is a visible paint that follows the theme; it has no fixed level. */ +const CURRENT_COLOR = 'currentcolor'; +const CURRENT_COLOR_TONE = -1; /** Named CSS grayscale colors mapped to their 0-255 level. Unknown names are chromatic. */ const GRAY_LEVELS = new Map([ @@ -269,24 +273,45 @@ function inNonRenderingContainer(el: Element, root: Element): boolean { } /** - * True when a rendered shape paints with SVG's default black fill: it has no - * explicit fill or stroke, inherits no fill from an ancestor, and is not a - * non-rendering template. Such a shape contributes a black tone that explicit - * paint values alone do not capture. Skipped when a `