Skip to content

Commit 291d34c

Browse files
klaesraclaude
andcommitted
fix: [#2344] Resolve every var() in a value, not only the first one
Since v20.12.1 resolveVariables() substituted the first var() and returned, leaving later occurrences in the value unresolved. The remainder of the value is now resolved as well. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent eac5a38 commit 291d34c

2 files changed

Lines changed: 46 additions & 1 deletion

File tree

packages/happy-dom/src/css/declaration/property-manager/utilities/CSSVariableFormatter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ export default class CSSVariableFormatter {
6666
if (variable && variable.parentheses === parentheses) {
6767
const fallbackValue = value.substring(variable.fallbackIndex, match.index).trim();
6868
const variableValue = cssVariables[variable.name];
69-
return `${value.substring(0, variable.index)}${this.resolveVariables(variableValue || fallbackValue, cssVariables)}${value.substring(match.index! + match[0].length)}`;
69+
// The rest of the value may hold further var() references (e.g. "calc(var(--a) + var(--b))").
70+
return `${value.substring(0, variable.index)}${this.resolveVariables(variableValue || fallbackValue, cssVariables)}${this.resolveVariables(value.substring(match.index! + match[0].length), cssVariables)}`;
7071
}
7172
}
7273

packages/happy-dom/test/css/declaration/computed-style/CSSComputedStyle.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,50 @@ describe('CSSComputedStyle', () => {
9898
expect(propertyManager.get('background-color')?.value).toBe('rgb(255, 219, 0)');
9999
});
100100

101+
it('Resolves every var() in a value, not only the first one.', () => {
102+
document.body.appendChild(element);
103+
element.setAttribute(
104+
'style',
105+
`--box: 20px; --gap: 2px; width: calc(5 * var(--box) + 6 * var(--gap)); height: calc(var(--gap) + var(--gap));`
106+
);
107+
108+
const computedStyle = new CSSComputedStyle(element);
109+
const propertyManager = computedStyle.getComputedStyle();
110+
111+
expect(propertyManager.get('width')?.value).toBe('calc(5 * 20px + 6 * 2px)');
112+
expect(propertyManager.get('height')?.value).toBe('calc(2px + 2px)');
113+
});
114+
115+
it('Resolves every var() in a shorthand value.', () => {
116+
document.body.appendChild(element);
117+
element.setAttribute(
118+
'style',
119+
`--x: 16px; padding: calc(0.375 * var(--x)) calc(0.75 * var(--x)); margin: var(--x) var(--x, 4px) var(--y, 8px);`
120+
);
121+
122+
const computedStyle = new CSSComputedStyle(element);
123+
const propertyManager = computedStyle.getComputedStyle();
124+
125+
expect(propertyManager.get('padding-top')?.value).toBe('calc(0.375 * 16px)');
126+
expect(propertyManager.get('padding-right')?.value).toBe('calc(0.75 * 16px)');
127+
expect(propertyManager.get('margin-top')?.value).toBe('16px');
128+
expect(propertyManager.get('margin-right')?.value).toBe('16px');
129+
expect(propertyManager.get('margin-bottom')?.value).toBe('8px');
130+
});
131+
132+
it('Resolves every var() in an inherited value on a descendant.', () => {
133+
const child = document.createElement('div');
134+
element.appendChild(child);
135+
document.body.appendChild(element);
136+
element.setAttribute('style', `--box: 20px; --gap: 2px;`);
137+
child.setAttribute('style', `width: calc(5 * var(--box) + 6 * var(--gap));`);
138+
139+
const computedStyle = new CSSComputedStyle(child);
140+
const propertyManager = computedStyle.getComputedStyle();
141+
142+
expect(propertyManager.get('width')?.value).toBe('calc(5 * 20px + 6 * 2px)');
143+
});
144+
101145
it('Ignores invalid CSS variable fallback value.', () => {
102146
document.body.appendChild(element);
103147
element.setAttribute('style', `width: var(--my-width, invalid);`);

0 commit comments

Comments
 (0)