Skip to content

Commit bab4fcd

Browse files
authored
Support nested var() and nested calc()
Fixes #125. Closes #127.
1 parent 8a19acd commit bab4fcd

File tree

6 files changed

+236
-22
lines changed

6 files changed

+236
-22
lines changed

lib/CSSStyleDeclaration.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ CSSStyleDeclaration.prototype = {
5353
this.removeProperty(name);
5454
return;
5555
}
56-
var isCustomProperty = name.indexOf('--') === 0;
56+
var isCustomProperty =
57+
name.indexOf('--') === 0 ||
58+
(typeof value === 'string' && /^var\(--[-\w]+,?.*\)$/.test(value));
5759
if (isCustomProperty) {
5860
this._setProperty(name, value, priority);
5961
return;

lib/parsers.js

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
********************************************************************/
55
'use strict';
66

7-
const { isColor, resolve } = require('@asamuzakjp/css-color');
7+
const { cssCalc, isColor, resolve } = require('@asamuzakjp/css-color');
88

99
exports.TYPES = {
1010
INTEGER: 1,
@@ -18,17 +18,24 @@ exports.TYPES = {
1818
KEYWORD: 9,
1919
NULL_OR_EMPTY_STR: 10,
2020
CALC: 11,
21+
VAR: 12,
2122
};
2223

23-
// rough regular expressions
24-
var integerRegEx = /^[-+]?[0-9]+$/;
25-
var numberRegEx = /^[-+]?[0-9]*\.?[0-9]+$/;
26-
var lengthRegEx = /^(0|[-+]?[0-9]*\.?[0-9]+(in|cm|em|mm|pt|pc|px|ex|rem|vh|vw|ch))$/;
27-
var percentRegEx = /^[-+]?[0-9]*\.?[0-9]+%$/;
24+
// regular expressions
25+
var DIGIT = '(?:0|[1-9]\\d*)';
26+
var NUMBER = `[+-]?(?:${DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${DIGIT})?`;
27+
var integerRegEx = new RegExp(`^[+-]?${DIGIT}$`);
28+
var numberRegEx = new RegExp(`^${NUMBER}$`);
29+
var lengthRegEx = new RegExp(
30+
`^${NUMBER}(?:[cm]m|[dls]?v(?:[bhiw]|max|min)|in|p[ctx]|q|r?(?:[cl]h|cap|e[mx]|ic))$`
31+
);
32+
var percentRegEx = new RegExp(`^${NUMBER}%$`);
33+
var angleRegEx = new RegExp(`^${NUMBER}(?:deg|g?rad|turn)$`);
2834
var urlRegEx = /^url\(\s*([^)]*)\s*\)$/;
2935
var stringRegEx = /^("[^"]*"|'[^']*')$/;
30-
var calcRegEx = /^calc\(([^)]*)\)$/;
31-
var angleRegEx = /^([-+]?[0-9]*\.?[0-9]+)(deg|grad|rad)$/;
36+
var varRegEx = /^var\(|(?<=[*/\s(])var\(/;
37+
var calcRegEx =
38+
/^(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)\(/;
3239

3340
// This will return one of the above types based on the passed in string
3441
exports.valueType = function valueType(val) {
@@ -38,11 +45,9 @@ exports.valueType = function valueType(val) {
3845
if (typeof val === 'number') {
3946
val = val.toString();
4047
}
41-
4248
if (typeof val !== 'string') {
4349
return undefined;
4450
}
45-
4651
if (integerRegEx.test(val)) {
4752
return exports.TYPES.INTEGER;
4853
}
@@ -58,6 +63,9 @@ exports.valueType = function valueType(val) {
5863
if (urlRegEx.test(val)) {
5964
return exports.TYPES.URL;
6065
}
66+
if (varRegEx.test(val)) {
67+
return exports.TYPES.VAR;
68+
}
6169
if (calcRegEx.test(val)) {
6270
return exports.TYPES.CALC;
6371
}
@@ -160,9 +168,14 @@ exports.parsePercent = function parsePercent(val) {
160168
// either a length or a percent
161169
exports.parseMeasurement = function parseMeasurement(val) {
162170
var type = exports.valueType(val);
163-
if (type === exports.TYPES.CALC) {
171+
if (type === exports.TYPES.VAR) {
164172
return val;
165173
}
174+
if (type === exports.TYPES.CALC) {
175+
return cssCalc(val, {
176+
format: 'specifiedValue',
177+
});
178+
}
166179

167180
var length = exports.parseLength(val);
168181
if (length !== undefined) {

package-lock.json

Lines changed: 56 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
],
3838
"main": "./lib/CSSStyleDeclaration.js",
3939
"dependencies": {
40-
"@asamuzakjp/css-color": "^2.5.0",
40+
"@asamuzakjp/css-color": "^2.8.2",
4141
"rrweb-cssom": "^0.7.1"
4242
},
4343
"devDependencies": {

test/CSSStyleDeclaration.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,4 +748,85 @@ describe('CSSStyleDeclaration', () => {
748748
assert.strictEqual(style.getPropertyValue(property), 'calc(100% - 100px)');
749749
});
750750
}
751+
752+
it('supports nested calc', () => {
753+
const style = new CSSStyleDeclaration();
754+
style.setProperty('width', 'calc(100% - calc(200px - 100px))');
755+
assert.strictEqual(style.getPropertyValue('width'), 'calc(100% - 100px)');
756+
});
757+
758+
it('supports nested calc', () => {
759+
const style = new CSSStyleDeclaration();
760+
style.setProperty('width', 'calc(100% * calc(2 / 3))');
761+
assert.strictEqual(style.getPropertyValue('width'), 'calc(66.6667%)');
762+
});
763+
764+
it('supports var', () => {
765+
const style = new CSSStyleDeclaration();
766+
style.setProperty('width', 'var(--foo)');
767+
assert.strictEqual(style.getPropertyValue('width'), 'var(--foo)');
768+
});
769+
770+
it('supports var with fallback', () => {
771+
const style = new CSSStyleDeclaration();
772+
style.setProperty('width', 'var(--foo, 100px)');
773+
assert.strictEqual(style.getPropertyValue('width'), 'var(--foo, 100px)');
774+
});
775+
776+
it('supports var with var fallback', () => {
777+
const style = new CSSStyleDeclaration();
778+
style.setProperty('width', 'var(--foo, var(--bar))');
779+
assert.strictEqual(style.getPropertyValue('width'), 'var(--foo, var(--bar))');
780+
});
781+
782+
it('supports calc with var inside', () => {
783+
const style = new CSSStyleDeclaration();
784+
style.setProperty('width', 'calc(100% - var(--foo))');
785+
assert.strictEqual(style.getPropertyValue('width'), 'calc(100% - var(--foo))');
786+
});
787+
788+
it('supports var with calc inside', () => {
789+
const style = new CSSStyleDeclaration();
790+
style.setProperty('width', 'var(--foo, calc(var(--bar) + 3px))');
791+
assert.strictEqual(style.getPropertyValue('width'), 'var(--foo, calc(var(--bar) + 3px))');
792+
});
793+
794+
it('supports color var', () => {
795+
const style = new CSSStyleDeclaration();
796+
style.setProperty('color', 'var(--foo)');
797+
assert.strictEqual(style.getPropertyValue('color'), 'var(--foo)');
798+
});
799+
800+
it('should not normalize if var() is included', () => {
801+
const style = new CSSStyleDeclaration();
802+
style.setProperty('width', 'calc( /* comment */ 100% - calc(var(--foo) *2 ))');
803+
assert.strictEqual(
804+
style.getPropertyValue('width'),
805+
'calc( /* comment */ 100% - calc(var(--foo) *2 ))'
806+
);
807+
});
808+
809+
it('supports abs', () => {
810+
const style = new CSSStyleDeclaration();
811+
style.setProperty('width', 'abs(1 + 2 + 3)');
812+
assert.strictEqual(style.getPropertyValue('width'), 'calc(6)');
813+
});
814+
815+
it('supports abs inside calc', () => {
816+
const style = new CSSStyleDeclaration();
817+
style.setProperty('width', 'calc(abs(1) + abs(2))');
818+
assert.strictEqual(style.getPropertyValue('width'), 'calc(3)');
819+
});
820+
821+
it('supports sign', () => {
822+
const style = new CSSStyleDeclaration();
823+
style.setProperty('width', 'sign(.1)');
824+
assert.strictEqual(style.getPropertyValue('width'), 'calc(1)');
825+
});
826+
827+
it('supports sign inside calc', () => {
828+
const style = new CSSStyleDeclaration();
829+
style.setProperty('width', 'calc(sign(.1) + sign(.2))');
830+
assert.strictEqual(style.getPropertyValue('width'), 'calc(2)');
831+
});
751832
});

test/parsers.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,49 @@ describe('valueType', () => {
6868
assert.strictEqual(output, parsers.TYPES.LENGTH);
6969
});
7070

71+
it('returns var from calc(100px * var(--foo))', () => {
72+
let input = 'calc(100px * var(--foo))';
73+
let output = parsers.valueType(input);
74+
75+
assert.strictEqual(output, parsers.TYPES.VAR);
76+
});
77+
78+
it('returns var from var(--foo)', () => {
79+
let input = 'var(--foo)';
80+
let output = parsers.valueType(input);
81+
82+
assert.strictEqual(output, parsers.TYPES.VAR);
83+
});
84+
85+
it('returns var from var(--foo, var(--bar))', () => {
86+
let input = 'var(--foo, var(--bar))';
87+
let output = parsers.valueType(input);
88+
89+
assert.strictEqual(output, parsers.TYPES.VAR);
90+
});
91+
92+
it('returns var from var(--foo, calc(var(--bar) * 2))', () => {
93+
let input = 'var(--foo, calc(var(--bar) * 2))';
94+
let output = parsers.valueType(input);
95+
96+
assert.strictEqual(output, parsers.TYPES.VAR);
97+
});
98+
7199
it('returns calc from calc(100px * 2)', () => {
72100
let input = 'calc(100px * 2)';
73101
let output = parsers.valueType(input);
74102

75103
assert.strictEqual(output, parsers.TYPES.CALC);
76104
});
105+
106+
it('returns calc from calc(100px * calc(2 * 1))', () => {
107+
let input = 'calc(100px * calc(2 * 1))';
108+
let output = parsers.valueType(input);
109+
110+
assert.strictEqual(output, parsers.TYPES.CALC);
111+
});
77112
});
113+
78114
describe('parseInteger', () => {
79115
it.todo('test');
80116
});
@@ -88,6 +124,41 @@ describe('parsePercent', () => {
88124
it.todo('test');
89125
});
90126
describe('parseMeasurement', () => {
127+
it('should return value with em unit', () => {
128+
let input = '1em';
129+
let output = parsers.parseMeasurement(input);
130+
131+
assert.strictEqual(output, '1em');
132+
});
133+
134+
it('should return value with percent', () => {
135+
let input = '100%';
136+
let output = parsers.parseMeasurement(input);
137+
138+
assert.strictEqual(output, '100%');
139+
});
140+
141+
it('should return value as is', () => {
142+
let input = 'var(/* comment */ --foo)';
143+
let output = parsers.parseMeasurement(input);
144+
145+
assert.strictEqual(output, 'var(/* comment */ --foo)');
146+
});
147+
148+
it('should return calculated value', () => {
149+
let input = 'calc(2em / 3)';
150+
let output = parsers.parseMeasurement(input);
151+
152+
assert.strictEqual(output, 'calc(0.666667em)');
153+
});
154+
155+
it('should return calculated value', () => {
156+
let input = 'calc(100% / 3)';
157+
let output = parsers.parseMeasurement(input);
158+
159+
assert.strictEqual(output, 'calc(33.3333%)');
160+
});
161+
91162
it.todo('test');
92163
});
93164
describe('parseUrl', () => {

0 commit comments

Comments
 (0)