css-calc-transform
Version:
Transform CSS properties with calc() values to pixels based on window and element dimensions.
156 lines (155 loc) • 6.54 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.transform = void 0;
var evaluate = require("evaluator.js");
var CALC_REG = /^calc\(([\s\S]+)\)$/i;
var CLAMP_REG = /clamp[(]([^()]*)[)]/i;
var MULTI_WHITESPACE = /\s{2,10}/g;
var CLAMP = /clamp\((\s?\d{1,20})\s?,\s?(\d{1,20})\s?,\s?(\d{1,20}\s?)\)/i;
var MIX_MAX = /(min|max\()/gi;
var PERCENT = /[\d.]{1,20}%/;
var VIEWPORT_WIDTH = /[\d.]{1,20}vw/i;
var VIEWPORT_HEIGHT = /[\d.]{1,20}vh/i;
var VIEWPORT_MIN = /[\d.]{1,20}vmin/i;
var VIEWPORT_MAX = /[\d.]{1,20}vmax/i;
var PIXEL = /(\d{1,20})px/gi;
var EM = /[\d.]{1,20}em/i;
var REM = /[\d.]{1,20}rem/i;
var UNIT = /[\d.]{1,20}([a-z]{1,20})/i;
var MATH_EXP = /[+\-/*]?[\d.]{1,20}(px|%|em|rem|vw|vh|vmin|vmax)?/gi;
var PLACEHOLDER = "$1";
var PLUS_MINUS = /[-+]/g;
var CALC_WITH_OPERATOR = /^calc\([-+]/i;
var MINUS_PERCENTAGE = /\s{1}\-\d{1,20}%/g;
var PLUS_MINUS_WHITESPACE = /\s{1}(\+|\-)\s{1}/g;
var DIVIDE_BY_ZERO = /\/\s?0/g;
var MULTIPLY_BY_UNIT = /\d{1,20}(px|%|em|rem|vw|vh|vmin|vmax)\s?\*\s?\d{1,20}(px|%|em|rem|vw|vh|vmin|vmax)/gi;
var DIVIDE_BY_UNIT = /\/\s?\d{1,20}(px|%|em|rem|vw|vh|vmin|vmax)/i;
var UNITLESS_VALUE_LEFT = /\d{1,20}\s{1}(\+|\-)\s{1}\d{1,20}(px|%|em|rem|vw|vh|vmin|vmax)/gi;
var UNITLESS_VALUE_RIGHT = /\d{1,20}(px|%|em|rem|vw|vh|vmin|vmax)\s{1}(\+|\-)\s{1}\d{1,20}(\s{1}|$|\))/gi;
var FUNCTION_CALL = /[a-zA-Z]{1,20}[(]([^()]*)[)]/g;
var ALLOWED_FUNCTIONS = /min|max|clamp|calc\(/gi;
var DISALLOWED_CHARS = /[!$%^&_|~=`\\#{}\[\]:";'<>?]/;
var CSS_CALC = "CSS calc(): ";
var CLAMP_REPLACEMENT = "MAX($1, MIN($2, $3))";
var noClamp = ["top", "left", "bottom", "right", "z-index", "marginTop", "marginRight", "marginBottom", "marginLeft"];
var transform = exports.transform = function transform(_ref) {
var prop = _ref.prop,
value = _ref.value,
win = _ref.win,
parent = _ref.parent,
font = _ref.font;
var calcMatches = typeof value === "string" && value.trim().match(CALC_REG);
if (!calcMatches) {
return value;
}
var hasFunctionCalls = typeof calcMatches[1] === "string" && calcMatches[1].match(FUNCTION_CALL);
if (hasFunctionCalls) {
var functionCalls = calcMatches[1].match(FUNCTION_CALL);
var allowedFunctions = calcMatches[1].match(ALLOWED_FUNCTIONS);
var allFunctionsAreAllowed = Array.isArray(functionCalls) && Array.isArray(allowedFunctions) && functionCalls.length === allowedFunctions.length;
if (allowedFunctions === null || !allFunctionsAreAllowed) {
return value;
}
}
if (value.match(UNITLESS_VALUE_LEFT) || value.match(UNITLESS_VALUE_RIGHT)) {
throw new Error(CSS_CALC + "unexpected unitless value.");
}
if (value.match(MULTIPLY_BY_UNIT)) {
throw new Error(CSS_CALC + "cannot multiply by \"".concat(RegExp.$2, "\", number expected."));
}
if (value.match(DIVIDE_BY_UNIT)) {
throw new Error(CSS_CALC + "cannot divide by \"".concat(RegExp.$1, "\", number expected."));
}
if (value.match(DIVIDE_BY_ZERO)) {
throw new Error(CSS_CALC + "cannot divide by zero.");
}
var plusMinus = value.match(PLUS_MINUS);
if (!value.match(CALC_WITH_OPERATOR) && plusMinus) {
var minusPercentage = value.match(MINUS_PERCENTAGE);
if (!minusPercentage) {
var white = value.match(PLUS_MINUS_WHITESPACE);
if (plusMinus && !white || plusMinus && white && plusMinus.length !== white.length) {
throw new Error(CSS_CALC + "white space is required on both sides of the + and - operators.");
}
}
}
var calcPart = calcMatches[0];
var formula = calcMatches[1].replace(/calc\(/gi, "(");
var matches = formula.match(MATH_EXP);
var currentFormula = formula.trim().replace(MULTI_WHITESPACE, " ").replace(PIXEL, PLACEHOLDER);
matches.forEach(function (match) {
var refValue;
var modifier;
if (match.match(PERCENT)) {
if (prop === "fontSize") {
if (parent && parent.font && typeof parent.font.size === "number") {
refValue = parent.font.size;
} else {
throw new Error(CSS_CALC + "you have to define parent.font.size when using the \"%\" unit with font-size.");
}
} else {
refValue = prop === "height" ? parent.height : parent.width;
}
modifier = parseFloat(match) / 100;
} else if (match.match(VIEWPORT_WIDTH)) {
refValue = win.width;
modifier = parseFloat(match) / 100;
} else if (match.match(VIEWPORT_HEIGHT)) {
refValue = win.height;
modifier = parseFloat(match) / 100;
} else if (match.match(VIEWPORT_MIN)) {
refValue = Math.min(win.width, win.height);
modifier = parseFloat(match) / 100;
} else if (match.match(VIEWPORT_MAX)) {
refValue = Math.max(win.width, win.height);
modifier = parseFloat(match) / 100;
} else if (match.match(EM)) {
if (prop === "fontSize") {
if (parent && parent.font && typeof parent.font.size === "number") {
refValue = parent.font.size;
} else {
throw new Error(CSS_CALC + "you have to define parent.font.size when using the \"em\" unit with font-size.");
}
} else if (font && typeof font.size === "number") {
refValue = font.size;
} else {
throw new Error(CSS_CALC + "you have to define font.size when using the \"em\" unit.");
}
modifier = parseFloat(match);
} else if (match.match(REM)) {
refValue = 16;
modifier = parseFloat(match);
}
if (modifier !== undefined) {
currentFormula = currentFormula.replace(match, refValue * modifier);
}
});
if (DISALLOWED_CHARS.test(currentFormula)) {
return value;
}
var unitMatch = currentFormula.match(UNIT);
if (unitMatch) {
var unit = unitMatch[1];
throw new Error(CSS_CALC + "unsupported unit ".concat(unit, "."));
}
var clampMatch = currentFormula.match(CLAMP_REG);
var isClampWithArgs = clampMatch != null && typeof clampMatch[0] === "string" && typeof clampMatch[1] === "string";
if (isClampWithArgs) {
var args = clampMatch[1].split(",");
if (args.length !== 3) {
throw new Error(CSS_CALC + "clamp() needs to be called with exactly three parameters.");
}
}
var replacedFunctionsFormula = currentFormula.toLowerCase().replace(MIX_MAX, function (val) {
return val.toUpperCase();
}).replace(CLAMP, CLAMP_REPLACEMENT);
var result = evaluate("(" + replacedFunctionsFormula + ")");
var resultFloat = parseFloat(value.replace(calcPart, result));
if (noClamp.indexOf(prop) === -1 && resultFloat < 0) {
return 0;
}
return resultFloat;
};