Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support nested var() and nested calc() #179

Merged
merged 5 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/CSSStyleDeclaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ CSSStyleDeclaration.prototype = {
this.removeProperty(name);
return;
}
var isCustomProperty = name.indexOf('--') === 0;
var isCustomProperty =
name.indexOf('--') === 0 ||
(typeof value === 'string' && /^var\(--[-\w]+,?.*\)$/.test(value));
if (isCustomProperty) {
this._setProperty(name, value, priority);
return;
Expand Down
35 changes: 24 additions & 11 deletions lib/parsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
********************************************************************/
'use strict';

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

exports.TYPES = {
INTEGER: 1,
Expand All @@ -18,17 +18,24 @@ exports.TYPES = {
KEYWORD: 9,
NULL_OR_EMPTY_STR: 10,
CALC: 11,
VAR: 12,
};

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

// This will return one of the above types based on the passed in string
exports.valueType = function valueType(val) {
Expand All @@ -38,11 +45,9 @@ exports.valueType = function valueType(val) {
if (typeof val === 'number') {
val = val.toString();
}

if (typeof val !== 'string') {
return undefined;
}

if (integerRegEx.test(val)) {
return exports.TYPES.INTEGER;
}
Expand All @@ -58,6 +63,9 @@ exports.valueType = function valueType(val) {
if (urlRegEx.test(val)) {
return exports.TYPES.URL;
}
if (varRegEx.test(val)) {
return exports.TYPES.VAR;
}
if (calcRegEx.test(val)) {
return exports.TYPES.CALC;
}
Expand Down Expand Up @@ -160,9 +168,14 @@ exports.parsePercent = function parsePercent(val) {
// either a length or a percent
exports.parseMeasurement = function parseMeasurement(val) {
var type = exports.valueType(val);
if (type === exports.TYPES.CALC) {
if (type === exports.TYPES.VAR) {
return val;
}
if (type === exports.TYPES.CALC) {
return cssCalc(val, {
format: 'specifiedValue',
});
}

var length = exports.parseLength(val);
if (length !== undefined) {
Expand Down
65 changes: 56 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
],
"main": "./lib/CSSStyleDeclaration.js",
"dependencies": {
"@asamuzakjp/css-color": "^2.5.0",
"@asamuzakjp/css-color": "^2.8.2",
"rrweb-cssom": "^0.7.1"
},
"devDependencies": {
Expand Down
81 changes: 81 additions & 0 deletions test/CSSStyleDeclaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -748,4 +748,85 @@ describe('CSSStyleDeclaration', () => {
assert.strictEqual(style.getPropertyValue(property), 'calc(100% - 100px)');
});
}

it('supports nested calc', () => {
const style = new CSSStyleDeclaration();
style.setProperty('width', 'calc(100% - calc(200px - 100px))');
assert.strictEqual(style.getPropertyValue('width'), 'calc(100% - 100px)');
});

it('supports nested calc', () => {
const style = new CSSStyleDeclaration();
style.setProperty('width', 'calc(100% * calc(2 / 3))');
assert.strictEqual(style.getPropertyValue('width'), 'calc(66.6667%)');
});

it('supports var', () => {
const style = new CSSStyleDeclaration();
style.setProperty('width', 'var(--foo)');
assert.strictEqual(style.getPropertyValue('width'), 'var(--foo)');
});

it('supports var with fallback', () => {
const style = new CSSStyleDeclaration();
style.setProperty('width', 'var(--foo, 100px)');
assert.strictEqual(style.getPropertyValue('width'), 'var(--foo, 100px)');
});

it('supports var with var fallback', () => {
const style = new CSSStyleDeclaration();
style.setProperty('width', 'var(--foo, var(--bar))');
assert.strictEqual(style.getPropertyValue('width'), 'var(--foo, var(--bar))');
});

it('supports calc with var inside', () => {
const style = new CSSStyleDeclaration();
style.setProperty('width', 'calc(100% - var(--foo))');
assert.strictEqual(style.getPropertyValue('width'), 'calc(100% - var(--foo))');
});

it('supports var with calc inside', () => {
const style = new CSSStyleDeclaration();
style.setProperty('width', 'var(--foo, calc(var(--bar) + 3px))');
assert.strictEqual(style.getPropertyValue('width'), 'var(--foo, calc(var(--bar) + 3px))');
});

it('supports color var', () => {
const style = new CSSStyleDeclaration();
style.setProperty('color', 'var(--foo)');
assert.strictEqual(style.getPropertyValue('color'), 'var(--foo)');
});

it('should not normalize if var() is included', () => {
const style = new CSSStyleDeclaration();
style.setProperty('width', 'calc( /* comment */ 100% - calc(var(--foo) *2 ))');
assert.strictEqual(
style.getPropertyValue('width'),
'calc( /* comment */ 100% - calc(var(--foo) *2 ))'
);
});

it('supports abs', () => {
const style = new CSSStyleDeclaration();
style.setProperty('width', 'abs(1 + 2 + 3)');
assert.strictEqual(style.getPropertyValue('width'), 'calc(6)');
});

it('supports abs inside calc', () => {
const style = new CSSStyleDeclaration();
style.setProperty('width', 'calc(abs(1) + abs(2))');
assert.strictEqual(style.getPropertyValue('width'), 'calc(3)');
});

it('supports sign', () => {
const style = new CSSStyleDeclaration();
style.setProperty('width', 'sign(.1)');
assert.strictEqual(style.getPropertyValue('width'), 'calc(1)');
});

it('supports sign inside calc', () => {
const style = new CSSStyleDeclaration();
style.setProperty('width', 'calc(sign(.1) + sign(.2))');
assert.strictEqual(style.getPropertyValue('width'), 'calc(2)');
});
});
71 changes: 71 additions & 0 deletions test/parsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,49 @@ describe('valueType', () => {
assert.strictEqual(output, parsers.TYPES.LENGTH);
});

it('returns var from calc(100px * var(--foo))', () => {
let input = 'calc(100px * var(--foo))';
let output = parsers.valueType(input);

assert.strictEqual(output, parsers.TYPES.VAR);
});

it('returns var from var(--foo)', () => {
let input = 'var(--foo)';
let output = parsers.valueType(input);

assert.strictEqual(output, parsers.TYPES.VAR);
});

it('returns var from var(--foo, var(--bar))', () => {
let input = 'var(--foo, var(--bar))';
let output = parsers.valueType(input);

assert.strictEqual(output, parsers.TYPES.VAR);
});

it('returns var from var(--foo, calc(var(--bar) * 2))', () => {
let input = 'var(--foo, calc(var(--bar) * 2))';
let output = parsers.valueType(input);

assert.strictEqual(output, parsers.TYPES.VAR);
});

it('returns calc from calc(100px * 2)', () => {
let input = 'calc(100px * 2)';
let output = parsers.valueType(input);

assert.strictEqual(output, parsers.TYPES.CALC);
});

it('returns calc from calc(100px * calc(2 * 1))', () => {
let input = 'calc(100px * calc(2 * 1))';
let output = parsers.valueType(input);

assert.strictEqual(output, parsers.TYPES.CALC);
});
});

describe('parseInteger', () => {
it.todo('test');
});
Expand All @@ -88,6 +124,41 @@ describe('parsePercent', () => {
it.todo('test');
});
describe('parseMeasurement', () => {
it('should return value with em unit', () => {
let input = '1em';
let output = parsers.parseMeasurement(input);

assert.strictEqual(output, '1em');
});

it('should return value with percent', () => {
let input = '100%';
let output = parsers.parseMeasurement(input);

assert.strictEqual(output, '100%');
});

it('should return value as is', () => {
let input = 'var(/* comment */ --foo)';
let output = parsers.parseMeasurement(input);

assert.strictEqual(output, 'var(/* comment */ --foo)');
});

it('should return calculated value', () => {
let input = 'calc(2em / 3)';
let output = parsers.parseMeasurement(input);

assert.strictEqual(output, 'calc(0.666667em)');
});

it('should return calculated value', () => {
let input = 'calc(100% / 3)';
let output = parsers.parseMeasurement(input);

assert.strictEqual(output, 'calc(33.3333%)');
});

it.todo('test');
});
describe('parseUrl', () => {
Expand Down
Loading