From 89ee7fc8c63a37b2a4114cfc36b9bf62d29c356e Mon Sep 17 00:00:00 2001
From: Marco Beretta <81851188+berry-13@users.noreply.github.com>
Date: Mon, 22 Jun 2026 09:54:26 +0200
Subject: [PATCH] fix: treat currentColor as a tone and stroked closed shapes
as filled
---
client/src/utils/__tests__/svg.test.ts | 26 +++++++++++++-
client/src/utils/svg.ts | 47 +++++++++++++++++++++-----
2 files changed, 63 insertions(+), 10 deletions(-)
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 `