From baf63bc9539cf766877588d7c4589c8007e865ac Mon Sep 17 00:00:00 2001 From: Guillaume Date: Fri, 23 Apr 2021 14:47:17 +0200 Subject: [PATCH] Improve support of un-computed calc --- lib/CSSStyleDeclaration.test.js | 40 +++++++++ lib/parsers.js | 138 +++++++++++++++++--------------- 2 files changed, 114 insertions(+), 64 deletions(-) diff --git a/lib/CSSStyleDeclaration.test.js b/lib/CSSStyleDeclaration.test.js index 9fa8ed3b..187255a4 100644 --- a/lib/CSSStyleDeclaration.test.js +++ b/lib/CSSStyleDeclaration.test.js @@ -550,7 +550,47 @@ describe('CSSStyleDeclaration', () => { test('supports calc', () => { const style = new CSSStyleDeclaration(); + + // Integer + style.setProperty('counter-increment', 'calc(1 + 1)'); + expect(style.getPropertyValue('counter-increment')).toEqual('calc(1 + 1)'); + + // Number + style.setProperty('opacity', 'calc(0.5 * 2)'); + expect(style.getPropertyValue('opacity')).toEqual('calc(0.5 * 2)'); + + // Dimension (length or percentage) style.setProperty('width', 'calc(100% - 100px)'); expect(style.getPropertyValue('width')).toEqual('calc(100% - 100px)'); + + // Length + style.setProperty('border-width', 'calc(1px * 2)'); + expect(style.getPropertyValue('border-width')).toEqual('calc(1px * 2)'); + + // Percentage + style.setProperty('font-stretch', 'calc(100% * 1.5)'); + expect(style.getPropertyValue('font-stretch')).toEqual('calc(100% * 1.5)'); + // style.setProperty('background-image', 'linear-gradient(black, white calc(25% * 2))'); + // expect(style.getPropertyValue('background-image')).toEqual('linear-gradient(black, white calc(25% * 2))'); + + // Color + style.setProperty('color', 'rgb(calc(100 + 100), 0, 0)'); + expect(style.getPropertyValue('color')).toEqual('rgb(calc(100 + 100), 0, 0)'); + style.setProperty('color', 'rgba(calc(100 + 100), 0, 0, 50%)'); + expect(style.getPropertyValue('color')).toEqual('rgba(calc(100 + 100), 0, 0, 0.5)'); + // style.setProperty('color', 'hsl(calc(100 + 100), 0, 0)'); + // expect(style.getPropertyValue('color')).toEqual('hsla(calc(100 + 100), 0, 0)'); + // style.setProperty('color', 'hsla(calc(100 + 100), 0, 0, 1)'); + // expect(style.getPropertyValue('color')).toEqual('hsla(calc(100 + 100), 0, 0, 1)'); + + // Angle + style.setProperty('offset-rotate', 'calc(45deg * 2)'); + expect(style.getPropertyValue('offset-rotate')).toEqual('calc(45deg * 2)'); + // style.setProperty('background-image', 'linear-gradient(calc(45deg * 2), black, white)'); + // expect(style.getPropertyValue('background-image')).toEqual('linear-gradient(calc(45deg * 2), black, white)'); + + // Time + style.setProperty('transition-duration', 'calc(1s * 2)'); + expect(style.getPropertyValue('transition-duration')).toEqual('calc(1s * 2)'); }); }); diff --git a/lib/parsers.js b/lib/parsers.js index 8ecdf5e3..52ada153 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -28,10 +28,10 @@ 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]+%$/; var urlRegEx = /^url\(\s*([^)]*)\s*\)$/; var stringRegEx = /^("[^"]*"|'[^']*')$/; +var calcRegEx = /^calc\([^)]+\)$/; var colorRegEx1 = /^#([0-9a-fA-F]{3,4}){1,2}$/; -var colorRegEx2 = /^rgb\(([^)]*)\)$/; -var colorRegEx3 = /^rgba\(([^)]*)\)$/; -var calcRegEx = /^calc\(([^)]*)\)$/; +var colorRegEx2 = /^rgb\((.+)\)/; +var colorRegEx3 = /^rgba\((.+)\)/; var colorRegEx4 = /^hsla?\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*(,\s*(-?\d+|-?\d*.\d+)\s*)?\)/; var angleRegEx = /^([-+]?[0-9]*\.?[0-9]+)(deg|grad|rad)$/; @@ -83,6 +83,7 @@ exports.valueType = function valueType(val) { if (parts.length !== 3) { return undefined; } + parts = parts.filter(part => !calcRegEx.test(part)); if ( parts.every(percentRegEx.test.bind(percentRegEx)) || parts.every(integerRegEx.test.bind(integerRegEx)) @@ -94,14 +95,16 @@ exports.valueType = function valueType(val) { res = colorRegEx3.exec(val); if (res !== null) { parts = res[1].split(/\s*,\s*/); - if (parts.length !== 4) { + if (parts.length < 3 || parts.length > 4) { return undefined; } + const alpha = parts.slice(3, 4); + parts = parts.slice(0, 3).filter(part => !calcRegEx.test(part)); if ( - parts.slice(0, 3).every(percentRegEx.test.bind(percentRegEx)) || - parts.slice(0, 3).every(integerRegEx.test.bind(integerRegEx)) + parts.every(percentRegEx.test.bind(percentRegEx)) || + parts.every(integerRegEx.test.bind(integerRegEx)) ) { - if (numberRegEx.test(parts[3])) { + if (numberRegEx.test(alpha) || calcRegEx.test(alpha)) { return exports.TYPES.COLOR; } } @@ -157,7 +160,7 @@ exports.valueType = function valueType(val) { exports.parseInteger = function parseInteger(val) { var type = exports.valueType(val); - if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + if (type === exports.TYPES.NULL_OR_EMPTY_STR || type === exports.TYPES.CALC) { return val; } if (type !== exports.TYPES.INTEGER) { @@ -168,7 +171,7 @@ exports.parseInteger = function parseInteger(val) { exports.parseNumber = function parseNumber(val) { var type = exports.valueType(val); - if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + if (type === exports.TYPES.NULL_OR_EMPTY_STR || type === exports.TYPES.CALC) { return val; } if (type !== exports.TYPES.NUMBER && type !== exports.TYPES.INTEGER) { @@ -182,7 +185,7 @@ exports.parseLength = function parseLength(val) { return '0px'; } var type = exports.valueType(val); - if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + if (type === exports.TYPES.NULL_OR_EMPTY_STR || type === exports.TYPES.CALC) { return val; } if (type !== exports.TYPES.LENGTH) { @@ -196,7 +199,7 @@ exports.parsePercent = function parsePercent(val) { return '0%'; } var type = exports.valueType(val); - if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + if (type === exports.TYPES.NULL_OR_EMPTY_STR || type === exports.TYPES.CALC) { return val; } if (type !== exports.TYPES.PERCENT) { @@ -290,14 +293,7 @@ exports.parseColor = function parseColor(val) { if (type === exports.TYPES.NULL_OR_EMPTY_STR) { return val; } - var red, - green, - blue, - hue, - saturation, - lightness, - alpha = 1; - var parts; + var parts = []; var res = colorRegEx1.exec(val); // is it #aaa, #ababab, #aaaa, #abababaa if (res) { @@ -310,71 +306,85 @@ exports.parseColor = function parseColor(val) { hex = hex + defaultHex[3] + defaultHex[3]; } } - red = parseInt(hex.substr(0, 2), 16); - green = parseInt(hex.substr(2, 2), 16); - blue = parseInt(hex.substr(4, 2), 16); + parts.push(parseInt(hex.substr(0, 2), 16)); + parts.push(parseInt(hex.substr(2, 2), 16)); + parts.push(parseInt(hex.substr(4, 2), 16)); if (hex.length === 8) { var hexAlpha = hex.substr(6, 2); - var hexAlphaToRgbaAlpha = Number((parseInt(hexAlpha, 16) / 255).toFixed(3)); + parts.push(Number((parseInt(hexAlpha, 16) / 255).toFixed(3))); - return 'rgba(' + red + ', ' + green + ', ' + blue + ', ' + hexAlphaToRgbaAlpha + ')'; + return 'rgba(' + parts.join(', ') + ')'; } - return 'rgb(' + red + ', ' + green + ', ' + blue + ')'; + return 'rgb(' + parts.join(', ') + ')'; } + var channelRegEx; + res = colorRegEx2.exec(val); if (res) { parts = res[1].split(/\s*,\s*/); if (parts.length !== 3) { return undefined; } - if (parts.every(percentRegEx.test.bind(percentRegEx))) { - red = Math.floor((parseFloat(parts[0].slice(0, -1)) * 255) / 100); - green = Math.floor((parseFloat(parts[1].slice(0, -1)) * 255) / 100); - blue = Math.floor((parseFloat(parts[2].slice(0, -1)) * 255) / 100); - } else if (parts.every(integerRegEx.test.bind(integerRegEx))) { - red = parseInt(parts[0], 10); - green = parseInt(parts[1], 10); - blue = parseInt(parts[2], 10); - } else { - return undefined; + parts = parts.reduce((parts, part) => { + if (!parts) { + return undefined; + } + if (channelRegEx !== integerRegEx && percentRegEx.test(part)) { + channelRegEx = percentRegEx; + part = Math.floor((parseFloat(part.slice(0, -1)) * 255) / 100); + } else if (channelRegEx !== percentRegEx && integerRegEx.test(part)) { + channelRegEx = integerRegEx; + part = parseInt(part, 10); + } else if (calcRegEx.test(part)) { + return parts.concat(part); + } else { + return undefined; + } + return parts.concat(Math.min(255, Math.max(0, part))); + }, []); + if (parts) { + return 'rgb(' + parts.join(', ') + ')'; } - red = Math.min(255, Math.max(0, red)); - green = Math.min(255, Math.max(0, green)); - blue = Math.min(255, Math.max(0, blue)); - return 'rgb(' + red + ', ' + green + ', ' + blue + ')'; + return undefined; } + var alpha = 1; + res = colorRegEx3.exec(val); if (res) { parts = res[1].split(/\s*,\s*/); - if (parts.length !== 4) { + if (parts.length < 3 || parts.length > 4) { return undefined; } - if (parts.slice(0, 3).every(percentRegEx.test.bind(percentRegEx))) { - red = Math.floor((parseFloat(parts[0].slice(0, -1)) * 255) / 100); - green = Math.floor((parseFloat(parts[1].slice(0, -1)) * 255) / 100); - blue = Math.floor((parseFloat(parts[2].slice(0, -1)) * 255) / 100); - alpha = parseFloat(parts[3]); - } else if (parts.slice(0, 3).every(integerRegEx.test.bind(integerRegEx))) { - red = parseInt(parts[0], 10); - green = parseInt(parts[1], 10); - blue = parseInt(parts[2], 10); - alpha = parseFloat(parts[3]); - } else { - return undefined; + if (percentRegEx.test(parts[3])) { + alpha = Math.min(1, Math.max(0, parseFloat(parts[3]) / 100)); + } else if (numberRegEx.test(parts[3])) { + alpha = Math.min(1, Math.max(0, parseFloat(parts[3]))); + } else if (calcRegEx.test(parts[3])) { + alpha = parts[3]; } - if (isNaN(alpha)) { - alpha = 1; - } - red = Math.min(255, Math.max(0, red)); - green = Math.min(255, Math.max(0, green)); - blue = Math.min(255, Math.max(0, blue)); - alpha = Math.min(1, Math.max(0, alpha)); + parts = parts.slice(0, 3).reduce((parts, part) => { + if (!parts) { + return undefined; + } + if (channelRegEx !== integerRegEx && percentRegEx.test(part)) { + channelRegEx = percentRegEx; + part = Math.floor((parseFloat(part.slice(0, -1)) * 255) / 100); + } else if (channelRegEx !== percentRegEx && integerRegEx.test(part)) { + channelRegEx = integerRegEx; + part = parseInt(part, 10); + } else if (calcRegEx.test(part)) { + return parts.concat(part); + } else { + return undefined; + } + return parts.concat(Math.min(255, Math.max(0, part))); + }, []); if (alpha === 1) { - return 'rgb(' + red + ', ' + green + ', ' + blue + ')'; + return 'rgb(' + parts.join(', ') + ')'; } - return 'rgba(' + red + ', ' + green + ', ' + blue + ', ' + alpha + ')'; + return 'rgba(' + parts.join(', ') + ', ' + alpha + ')'; } res = colorRegEx4.exec(val); @@ -384,9 +394,9 @@ exports.parseColor = function parseColor(val) { if (!_hue || !_saturation || !_lightness) { return undefined; } - hue = parseFloat(_hue); - saturation = parseInt(_saturation, 10); - lightness = parseInt(_lightness, 10); + const hue = parseFloat(_hue); + const saturation = parseInt(_saturation, 10); + const lightness = parseInt(_lightness, 10); if (_alpha && numberRegEx.test(_alpha)) { alpha = parseFloat(_alpha); }