UNPKG

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
"use strict"; 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; };