UNPKG

6.03 kBJavaScriptView Raw
1"use strict";
2
3exports.__esModule = true;
4exports["default"] = math;
5var _defaultSymbols = _interopRequireDefault(require("./presets/defaultSymbols"));
6var _errors = _interopRequireDefault(require("../internalHelpers/_errors"));
7function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
8function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
9var unitRegExp = /((?!\w)a|na|hc|mc|dg|me[r]?|xe|ni(?![a-zA-Z])|mm|cp|tp|xp|q(?!s)|hv|xamv|nimv|wv|sm|s(?!\D|$)|ged|darg?|nrut)/g;
10
11// Merges additional math functionality into the defaults.
12function mergeSymbolMaps(additionalSymbols) {
13 var symbolMap = {};
14 symbolMap.symbols = additionalSymbols ? _extends({}, _defaultSymbols["default"].symbols, additionalSymbols.symbols) : _extends({}, _defaultSymbols["default"].symbols);
15 return symbolMap;
16}
17function exec(operators, values) {
18 var _ref;
19 var op = operators.pop();
20 values.push(op.f.apply(op, (_ref = []).concat.apply(_ref, values.splice(-op.argCount))));
21 return op.precedence;
22}
23function calculate(expression, additionalSymbols) {
24 var symbolMap = mergeSymbolMaps(additionalSymbols);
25 var match;
26 var operators = [symbolMap.symbols['('].prefix];
27 var values = [];
28 var pattern = new RegExp( // Pattern for numbers
29 "\\d+(?:\\.\\d+)?|" +
30 // ...and patterns for individual operators/function names
31 Object.keys(symbolMap.symbols).map(function (key) {
32 return symbolMap.symbols[key];
33 })
34 // longer symbols should be listed first
35 // $FlowFixMe
36 .sort(function (a, b) {
37 return b.symbol.length - a.symbol.length;
38 })
39 // $FlowFixMe
40 .map(function (val) {
41 return val.regSymbol;
42 }).join('|') + "|(\\S)", 'g');
43 pattern.lastIndex = 0; // Reset regular expression object
44
45 var afterValue = false;
46 do {
47 match = pattern.exec(expression);
48 var _ref2 = match || [')', undefined],
49 token = _ref2[0],
50 bad = _ref2[1];
51 var notNumber = symbolMap.symbols[token];
52 var notNewValue = notNumber && !notNumber.prefix && !notNumber.func;
53 var notAfterValue = !notNumber || !notNumber.postfix && !notNumber.infix;
54
55 // Check for syntax errors:
56 if (bad || (afterValue ? notAfterValue : notNewValue)) {
57 throw new _errors["default"](37, match ? match.index : expression.length, expression);
58 }
59 if (afterValue) {
60 // We either have an infix or postfix operator (they should be mutually exclusive)
61 var curr = notNumber.postfix || notNumber.infix;
62 do {
63 var prev = operators[operators.length - 1];
64 if ((curr.precedence - prev.precedence || prev.rightToLeft) > 0) break;
65 // Apply previous operator, since it has precedence over current one
66 } while (exec(operators, values)); // Exit loop after executing an opening parenthesis or function
67 afterValue = curr.notation === 'postfix';
68 if (curr.symbol !== ')') {
69 operators.push(curr);
70 // Postfix always has precedence over any operator that follows after it
71 if (afterValue) exec(operators, values);
72 }
73 } else if (notNumber) {
74 // prefix operator or function
75 operators.push(notNumber.prefix || notNumber.func);
76 if (notNumber.func) {
77 // Require an opening parenthesis
78 match = pattern.exec(expression);
79 if (!match || match[0] !== '(') {
80 throw new _errors["default"](38, match ? match.index : expression.length, expression);
81 }
82 }
83 } else {
84 // number
85 values.push(+token);
86 afterValue = true;
87 }
88 } while (match && operators.length);
89 if (operators.length) {
90 throw new _errors["default"](39, match ? match.index : expression.length, expression);
91 } else if (match) {
92 throw new _errors["default"](40, match ? match.index : expression.length, expression);
93 } else {
94 return values.pop();
95 }
96}
97function reverseString(str) {
98 return str.split('').reverse().join('');
99}
100
101/**
102 * Helper for doing math with CSS Units. Accepts a formula as a string. All values in the formula must have the same unit (or be unitless). Supports complex formulas utliziing addition, subtraction, multiplication, division, square root, powers, factorial, min, max, as well as parentheses for order of operation.
103 *
104 *In cases where you need to do calculations with mixed units where one unit is a [relative length unit](https://developer.mozilla.org/en-US/docs/Web/CSS/length#Relative_length_units), you will want to use [CSS Calc](https://developer.mozilla.org/en-US/docs/Web/CSS/calc).
105 *
106 * *warning* While we've done everything possible to ensure math safely evalutes formulas expressed as strings, you should always use extreme caution when passing `math` user provided values.
107 * @example
108 * // Styles as object usage
109 * const styles = {
110 * fontSize: math('12rem + 8rem'),
111 * fontSize: math('(12px + 2px) * 3'),
112 * fontSize: math('3px^2 + sqrt(4)'),
113 * }
114 *
115 * // styled-components usage
116 * const div = styled.div`
117 * fontSize: ${math('12rem + 8rem')};
118 * fontSize: ${math('(12px + 2px) * 3')};
119 * fontSize: ${math('3px^2 + sqrt(4)')};
120 * `
121 *
122 * // CSS as JS Output
123 *
124 * div: {
125 * fontSize: '20rem',
126 * fontSize: '42px',
127 * fontSize: '11px',
128 * }
129 */
130function math(formula, additionalSymbols) {
131 var reversedFormula = reverseString(formula);
132 var formulaMatch = reversedFormula.match(unitRegExp);
133
134 // Check that all units are the same
135 if (formulaMatch && !formulaMatch.every(function (unit) {
136 return unit === formulaMatch[0];
137 })) {
138 throw new _errors["default"](41);
139 }
140 var cleanFormula = reverseString(reversedFormula.replace(unitRegExp, ''));
141 return "" + calculate(cleanFormula, additionalSymbols) + (formulaMatch ? reverseString(formulaMatch[0]) : '');
142}
143module.exports = exports.default;
\No newline at end of file