mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-28 04:37:37 +00:00
fix: count paint only on rendered shapes and filter hidden CSS currentColor
This commit is contained in:
parent
166ca58919
commit
f9a6a37143
2 changed files with 91 additions and 9 deletions
|
|
@ -241,6 +241,18 @@ describe('isMonochromeSvg', () => {
|
|||
expect(isMonochromeSvg(svg)).toBe(true);
|
||||
});
|
||||
|
||||
it('ignores a CSS currentColor paint that only targets a hidden element', () => {
|
||||
const svg =
|
||||
'<svg viewBox="0 0 24 24"><style>.ghost{display:none;fill:currentColor}</style><path class="ghost" d="M0 0h4v4H0z" /><path fill="#333" d="M6 6h12v12H6z" /></svg>';
|
||||
expect(isMonochromeSvg(svg)).toBe(true);
|
||||
});
|
||||
|
||||
it('still counts a CSS currentColor paint on a visible shape', () => {
|
||||
const svg =
|
||||
'<svg viewBox="0 0 24 24"><style>.a{fill:currentColor}</style><path class="a" d="M0 0h24v24H0z" /><path fill="#fff" d="M6 6h12v12H6z" /></svg>';
|
||||
expect(isMonochromeSvg(svg)).toBe(false);
|
||||
});
|
||||
|
||||
it('preserves a chromatic fill applied to the root via CSS', () => {
|
||||
const svg =
|
||||
'<svg viewBox="0 0 24 24"><style>svg{fill:#f00}</style><path d="M4 4h16v16H4z" /></svg>';
|
||||
|
|
@ -254,6 +266,26 @@ describe('isMonochromeSvg', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('paint set on non-rendering containers', () => {
|
||||
it('tints a glyph whose root fill is overridden by every rendered shape', () => {
|
||||
const svg =
|
||||
'<svg viewBox="0 0 24 24" fill="#f00"><path fill="#333" d="M4 4h16v16H4z" /></svg>';
|
||||
expect(isMonochromeSvg(svg)).toBe(true);
|
||||
});
|
||||
|
||||
it('tints a glyph whose group fill no rendered shape inherits', () => {
|
||||
const svg =
|
||||
'<svg viewBox="0 0 24 24"><g fill="#f00"><path fill="#333" d="M4 4h16v16H4z" /></g></svg>';
|
||||
expect(isMonochromeSvg(svg)).toBe(true);
|
||||
});
|
||||
|
||||
it('still counts a container fill inherited by an unpainted shape', () => {
|
||||
const svg =
|
||||
'<svg viewBox="0 0 24 24"><g fill="#f00"><path d="M4 4h16v16H4z" /></g><path fill="#333" d="M0 0h4v4H0z" /></svg>';
|
||||
expect(isMonochromeSvg(svg)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('default fills alongside <style> rules', () => {
|
||||
it('rejects a CSS-filled shape combined with a default-black glyph', () => {
|
||||
const svg =
|
||||
|
|
|
|||
|
|
@ -41,6 +41,15 @@ const PAINT_PROPS = ['fill', 'stroke', 'stop-color'];
|
|||
*/
|
||||
const FILLABLE_SHAPES = 'path, rect, circle, ellipse, polygon, polyline, text';
|
||||
|
||||
/**
|
||||
* Elements that actually paint a tone, so a fill/stroke counts only when it lands
|
||||
* here (own or inherited). A `<use>` paints the geometry it instantiates; `line`
|
||||
* has no fill area but can stroke. Paint set on a pure container (`svg`, `g`) is
|
||||
* ignored unless a painter below inherits it.
|
||||
*/
|
||||
const FILL_PAINTERS = `${FILLABLE_SHAPES}, use`;
|
||||
const STROKE_PAINTERS = `${FILLABLE_SHAPES}, line, use`;
|
||||
|
||||
/**
|
||||
* Containers whose descendants supply functional paint (clipping, masking,
|
||||
* markers, tiles) that never appears as a visible tone. Their colors are ignored.
|
||||
|
|
@ -282,11 +291,17 @@ function hasOpaqueBackground(root: Element, rules: StyleRule[]): boolean {
|
|||
}
|
||||
|
||||
/**
|
||||
* The tones a CSS `currentColor` paint contributes: it is resolved per element
|
||||
* the rule matches (against an inherited fixed `color`), so a fixed color is
|
||||
* preserved rather than recorded as the theme-following sentinel.
|
||||
* The tones a CSS `currentColor` paint contributes: it is resolved per rendered
|
||||
* element the rule matches (against an inherited fixed `color`), so a fixed color
|
||||
* is preserved rather than recorded as the theme-following sentinel. Hidden,
|
||||
* functional, or fully transparent matches paint nothing and are skipped.
|
||||
*/
|
||||
function resolveCssCurrentColor(root: Element, selector: string, rules: StyleRule[]): string[] {
|
||||
function resolveCssCurrentColor(
|
||||
root: Element,
|
||||
selector: string,
|
||||
rules: StyleRule[],
|
||||
paintProp: string,
|
||||
): string[] {
|
||||
if (selector === '') {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -299,7 +314,14 @@ function resolveCssCurrentColor(root: Element, selector: string, rules: StyleRul
|
|||
} catch {
|
||||
return [CURRENT_COLOR];
|
||||
}
|
||||
return matched.map((el) => resolveCurrentColor(el, root, rules));
|
||||
return matched
|
||||
.filter(
|
||||
(el) =>
|
||||
!isHidden(el, root, rules) &&
|
||||
!isInside(el, root, FUNCTIONAL_CONTAINERS) &&
|
||||
!paintInvisible(el, root, rules, paintProp),
|
||||
)
|
||||
.map((el) => resolveCurrentColor(el, root, rules));
|
||||
}
|
||||
|
||||
function colorsFromStyleBlocks(root: Element, rules: StyleRule[]): string[] {
|
||||
|
|
@ -315,7 +337,7 @@ function colorsFromStyleBlocks(root: Element, rules: StyleRule[]): string[] {
|
|||
const property = match[1].toLowerCase();
|
||||
const value = match[2].trim();
|
||||
if (value.toLowerCase() === CURRENT_COLOR) {
|
||||
colors.push(...resolveCssCurrentColor(root, selector, rules));
|
||||
colors.push(...resolveCssCurrentColor(root, selector, rules, property));
|
||||
} else if (selectorPaintsRendered(root, selector, rules, property)) {
|
||||
colors.push(value);
|
||||
}
|
||||
|
|
@ -399,6 +421,25 @@ function referencedElements(root: Element, rules: StyleRule[]): Set<Element> {
|
|||
return referenced;
|
||||
}
|
||||
|
||||
/**
|
||||
* The paint an element renders for a property, or null when it paints none.
|
||||
* Fill/stroke are resolved through inheritance but only on actual painters, so a
|
||||
* value declared on a pure container (`svg`, `g`) is ignored unless a painter
|
||||
* inherits it. `stop-color` only paints on a gradient `<stop>`.
|
||||
*/
|
||||
function renderedPaint(
|
||||
el: Element,
|
||||
root: Element,
|
||||
rules: StyleRule[],
|
||||
property: string,
|
||||
): string | null {
|
||||
if (property === 'stop-color') {
|
||||
return el.matches('stop') ? readPaint(el, property) : null;
|
||||
}
|
||||
const painters = property === 'stroke' ? STROKE_PAINTERS : FILL_PAINTERS;
|
||||
return el.matches(painters) ? resolvePaint(el, root, rules, property) : null;
|
||||
}
|
||||
|
||||
function collectColors(root: Element, rules: StyleRule[]): string[] {
|
||||
const colors: string[] = [];
|
||||
const referenced = referencedElements(root, rules);
|
||||
|
|
@ -412,7 +453,7 @@ function collectColors(root: Element, rules: StyleRule[]): string[] {
|
|||
continue;
|
||||
}
|
||||
for (const prop of PAINT_PROPS) {
|
||||
const value = readPaint(el, prop);
|
||||
const value = renderedPaint(el, root, rules, prop);
|
||||
if (!value || paintInvisible(el, root, rules, prop)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -556,10 +597,15 @@ function paintInvisible(
|
|||
* rendered through `<use>`, `boundary` is the referenced root (the instance
|
||||
* re-parents there).
|
||||
*/
|
||||
function resolveFill(el: Element, boundary: Element, rules: StyleRule[]): string | null {
|
||||
function resolvePaint(
|
||||
el: Element,
|
||||
boundary: Element,
|
||||
rules: StyleRule[],
|
||||
property: string,
|
||||
): string | null {
|
||||
let current: Element | null = el;
|
||||
while (current != null) {
|
||||
const value = styleValue(current, rules, 'fill');
|
||||
const value = styleValue(current, rules, property);
|
||||
if (value != null && value !== 'inherit') {
|
||||
return value;
|
||||
}
|
||||
|
|
@ -571,6 +617,10 @@ function resolveFill(el: Element, boundary: Element, rules: StyleRule[]): string
|
|||
return null;
|
||||
}
|
||||
|
||||
function resolveFill(el: Element, boundary: Element, rules: StyleRule[]): string | null {
|
||||
return resolvePaint(el, boundary, rules, 'fill');
|
||||
}
|
||||
|
||||
function fillIsResolved(el: Element, root: Element, rules: StyleRule[]): boolean {
|
||||
return resolveFill(el, root, rules) != null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue