diff options
| author | littleblack111 <littleblack11111@gmail.com> | 2023-10-14 15:05:55 +0800 |
|---|---|---|
| committer | littleblack111 <littleblack11111@gmail.com> | 2023-10-14 15:05:55 +0800 |
| commit | c06f7ee0a40255214c477e13d9b956a7e07c2900 (patch) | |
| tree | f5982a568ef8fab865d0142e71c7494fea3e4c1b /node_modules/postcss-calc/src/lib | |
| parent | 3fd9400d4dccc25aa29b5702216732e0b7542073 (diff) | |
postcss,(plugins): nanocss, postcss-preset-env
Diffstat (limited to 'node_modules/postcss-calc/src/lib')
| -rw-r--r-- | node_modules/postcss-calc/src/lib/convertUnit.js | 160 | ||||
| -rw-r--r-- | node_modules/postcss-calc/src/lib/reducer.js | 362 | ||||
| -rw-r--r-- | node_modules/postcss-calc/src/lib/stringifier.js | 98 | ||||
| -rw-r--r-- | node_modules/postcss-calc/src/lib/transform.js | 109 |
4 files changed, 729 insertions, 0 deletions
diff --git a/node_modules/postcss-calc/src/lib/convertUnit.js b/node_modules/postcss-calc/src/lib/convertUnit.js new file mode 100644 index 00000000..5f7bc268 --- /dev/null +++ b/node_modules/postcss-calc/src/lib/convertUnit.js @@ -0,0 +1,160 @@ +'use strict'; +/** + * @type {{[key:string]: {[key:string]: number}}} + */ +const conversions = { + // Absolute length units + px: { + px: 1, + cm: 96 / 2.54, + mm: 96 / 25.4, + q: 96 / 101.6, + in: 96, + pt: 96 / 72, + pc: 16, + }, + cm: { + px: 2.54 / 96, + cm: 1, + mm: 0.1, + q: 0.025, + in: 2.54, + pt: 2.54 / 72, + pc: 2.54 / 6, + }, + mm: { + px: 25.4 / 96, + cm: 10, + mm: 1, + q: 0.25, + in: 25.4, + pt: 25.4 / 72, + pc: 25.4 / 6, + }, + q: { + px: 101.6 / 96, + cm: 40, + mm: 4, + q: 1, + in: 101.6, + pt: 101.6 / 72, + pc: 101.6 / 6, + }, + in: { + px: 1 / 96, + cm: 1 / 2.54, + mm: 1 / 25.4, + q: 1 / 101.6, + in: 1, + pt: 1 / 72, + pc: 1 / 6, + }, + pt: { + px: 0.75, + cm: 72 / 2.54, + mm: 72 / 25.4, + q: 72 / 101.6, + in: 72, + pt: 1, + pc: 12, + }, + pc: { + px: 0.0625, + cm: 6 / 2.54, + mm: 6 / 25.4, + q: 6 / 101.6, + in: 6, + pt: 6 / 72, + pc: 1, + }, + // Angle units + deg: { + deg: 1, + grad: 0.9, + rad: 180 / Math.PI, + turn: 360, + }, + grad: { + deg: 400 / 360, + grad: 1, + rad: 200 / Math.PI, + turn: 400, + }, + rad: { + deg: Math.PI / 180, + grad: Math.PI / 200, + rad: 1, + turn: Math.PI * 2, + }, + turn: { + deg: 1 / 360, + grad: 0.0025, + rad: 0.5 / Math.PI, + turn: 1, + }, + // Duration units + s: { + s: 1, + ms: 0.001, + }, + ms: { + s: 1000, + ms: 1, + }, + // Frequency units + hz: { + hz: 1, + khz: 1000, + }, + khz: { + hz: 0.001, + khz: 1, + }, + // Resolution units + dpi: { + dpi: 1, + dpcm: 1 / 2.54, + dppx: 1 / 96, + }, + dpcm: { + dpi: 2.54, + dpcm: 1, + dppx: 2.54 / 96, + }, + dppx: { + dpi: 96, + dpcm: 96 / 2.54, + dppx: 1, + }, +}; +/** + * @param {number} value + * @param {string} sourceUnit + * @param {string} targetUnit + * @param {number|false} precision + */ +function convertUnit(value, sourceUnit, targetUnit, precision) { + const sourceUnitNormalized = sourceUnit.toLowerCase(); + const targetUnitNormalized = targetUnit.toLowerCase(); + + if (!conversions[targetUnitNormalized]) { + throw new Error('Cannot convert to ' + targetUnit); + } + + if (!conversions[targetUnitNormalized][sourceUnitNormalized]) { + throw new Error('Cannot convert from ' + sourceUnit + ' to ' + targetUnit); + } + + const converted = + conversions[targetUnitNormalized][sourceUnitNormalized] * value; + + if (precision !== false) { + precision = Math.pow(10, Math.ceil(precision) || 5); + + return Math.round(converted * precision) / precision; + } + + return converted; +} + +module.exports = convertUnit; diff --git a/node_modules/postcss-calc/src/lib/reducer.js b/node_modules/postcss-calc/src/lib/reducer.js new file mode 100644 index 00000000..8a36bf13 --- /dev/null +++ b/node_modules/postcss-calc/src/lib/reducer.js @@ -0,0 +1,362 @@ +'use strict'; +const convertUnit = require('./convertUnit.js'); + +/** + * @param {import('../parser').CalcNode} node + * @return {node is import('../parser').ValueExpression} + */ +function isValueType(node) { + switch (node.type) { + case 'LengthValue': + case 'AngleValue': + case 'TimeValue': + case 'FrequencyValue': + case 'ResolutionValue': + case 'EmValue': + case 'ExValue': + case 'ChValue': + case 'RemValue': + case 'VhValue': + case 'VwValue': + case 'VminValue': + case 'VmaxValue': + case 'PercentageValue': + case 'Number': + return true; + } + return false; +} + +/** @param {'-'|'+'} operator */ +function flip(operator) { + return operator === '+' ? '-' : '+'; +} + +/** + * @param {string} operator + * @returns {operator is '+'|'-'} + */ +function isAddSubOperator(operator) { + return operator === '+' || operator === '-'; +} + +/** + * @typedef {{preOperator: '+'|'-', node: import('../parser').CalcNode}} Collectible + */ + +/** + * @param {'+'|'-'} preOperator + * @param {import('../parser').CalcNode} node + * @param {Collectible[]} collected + * @param {number} precision + */ +function collectAddSubItems(preOperator, node, collected, precision) { + if (!isAddSubOperator(preOperator)) { + throw new Error(`invalid operator ${preOperator}`); + } + if (isValueType(node)) { + const itemIndex = collected.findIndex((x) => x.node.type === node.type); + if (itemIndex >= 0) { + if (node.value === 0) { + return; + } + // can cast because of the criterion used to find itemIndex + const otherValueNode = /** @type import('../parser').ValueExpression*/ ( + collected[itemIndex].node + ); + const { left: reducedNode, right: current } = convertNodesUnits( + otherValueNode, + node, + precision + ); + + if (collected[itemIndex].preOperator === '-') { + collected[itemIndex].preOperator = '+'; + reducedNode.value *= -1; + } + if (preOperator === '+') { + reducedNode.value += current.value; + } else { + reducedNode.value -= current.value; + } + // make sure reducedNode.value >= 0 + if (reducedNode.value >= 0) { + collected[itemIndex] = { node: reducedNode, preOperator: '+' }; + } else { + reducedNode.value *= -1; + collected[itemIndex] = { node: reducedNode, preOperator: '-' }; + } + } else { + // make sure node.value >= 0 + if (node.value >= 0) { + collected.push({ node, preOperator }); + } else { + node.value *= -1; + collected.push({ node, preOperator: flip(preOperator) }); + } + } + } else if (node.type === 'MathExpression') { + if (isAddSubOperator(node.operator)) { + collectAddSubItems(preOperator, node.left, collected, precision); + const collectRightOperator = + preOperator === '-' ? flip(node.operator) : node.operator; + collectAddSubItems( + collectRightOperator, + node.right, + collected, + precision + ); + } else { + // * or / + const reducedNode = reduce(node, precision); + // prevent infinite recursive call + if ( + reducedNode.type !== 'MathExpression' || + isAddSubOperator(reducedNode.operator) + ) { + collectAddSubItems(preOperator, reducedNode, collected, precision); + } else { + collected.push({ node: reducedNode, preOperator }); + } + } + } else if (node.type === 'ParenthesizedExpression') { + collectAddSubItems(preOperator, node.content, collected, precision); + } else { + collected.push({ node, preOperator }); + } +} + +/** + * @param {import('../parser').CalcNode} node + * @param {number} precision + */ +function reduceAddSubExpression(node, precision) { + /** @type Collectible[] */ + const collected = []; + collectAddSubItems('+', node, collected, precision); + + const withoutZeroItem = collected.filter( + (item) => !(isValueType(item.node) && item.node.value === 0) + ); + const firstNonZeroItem = withoutZeroItem[0]; // could be undefined + + // prevent producing "calc(-var(--a))" or "calc()" + // which is invalid css + if ( + !firstNonZeroItem || + (firstNonZeroItem.preOperator === '-' && + !isValueType(firstNonZeroItem.node)) + ) { + const firstZeroItem = collected.find( + (item) => isValueType(item.node) && item.node.value === 0 + ); + if (firstZeroItem) { + withoutZeroItem.unshift(firstZeroItem); + } + } + + // make sure the preOperator of the first item is + + if ( + withoutZeroItem[0].preOperator === '-' && + isValueType(withoutZeroItem[0].node) + ) { + withoutZeroItem[0].node.value *= -1; + withoutZeroItem[0].preOperator = '+'; + } + + let root = withoutZeroItem[0].node; + for (let i = 1; i < withoutZeroItem.length; i++) { + root = { + type: 'MathExpression', + operator: withoutZeroItem[i].preOperator, + left: root, + right: withoutZeroItem[i].node, + }; + } + + return root; +} +/** + * @param {import('../parser').MathExpression} node + */ +function reduceDivisionExpression(node) { + if (!isValueType(node.right)) { + return node; + } + + if (node.right.type !== 'Number') { + throw new Error(`Cannot divide by "${node.right.unit}", number expected`); + } + + return applyNumberDivision(node.left, node.right.value); +} + +/** + * apply (expr) / number + * + * @param {import('../parser').CalcNode} node + * @param {number} divisor + * @return {import('../parser').CalcNode} + */ +function applyNumberDivision(node, divisor) { + if (divisor === 0) { + throw new Error('Cannot divide by zero'); + } + if (isValueType(node)) { + node.value /= divisor; + return node; + } + if (node.type === 'MathExpression' && isAddSubOperator(node.operator)) { + // turn (a + b) / num into a/num + b/num + // is good for further reduction + // checkout the test case + // "should reduce division before reducing additions" + return { + type: 'MathExpression', + operator: node.operator, + left: applyNumberDivision(node.left, divisor), + right: applyNumberDivision(node.right, divisor), + }; + } + // it is impossible to reduce it into a single value + // .e.g the node contains css variable + // so we just preserve the division and let browser do it + return { + type: 'MathExpression', + operator: '/', + left: node, + right: { + type: 'Number', + value: divisor, + }, + }; +} +/** + * @param {import('../parser').MathExpression} node + */ +function reduceMultiplicationExpression(node) { + // (expr) * number + if (node.right.type === 'Number') { + return applyNumberMultiplication(node.left, node.right.value); + } + // number * (expr) + if (node.left.type === 'Number') { + return applyNumberMultiplication(node.right, node.left.value); + } + return node; +} + +/** + * apply (expr) * number + * @param {number} multiplier + * @param {import('../parser').CalcNode} node + * @return {import('../parser').CalcNode} + */ +function applyNumberMultiplication(node, multiplier) { + if (isValueType(node)) { + node.value *= multiplier; + return node; + } + if (node.type === 'MathExpression' && isAddSubOperator(node.operator)) { + // turn (a + b) * num into a*num + b*num + // is good for further reduction + // checkout the test case + // "should reduce multiplication before reducing additions" + return { + type: 'MathExpression', + operator: node.operator, + left: applyNumberMultiplication(node.left, multiplier), + right: applyNumberMultiplication(node.right, multiplier), + }; + } + // it is impossible to reduce it into a single value + // .e.g the node contains css variable + // so we just preserve the division and let browser do it + return { + type: 'MathExpression', + operator: '*', + left: node, + right: { + type: 'Number', + value: multiplier, + }, + }; +} + +/** + * @param {import('../parser').ValueExpression} left + * @param {import('../parser').ValueExpression} right + * @param {number} precision + */ +function convertNodesUnits(left, right, precision) { + switch (left.type) { + case 'LengthValue': + case 'AngleValue': + case 'TimeValue': + case 'FrequencyValue': + case 'ResolutionValue': + if (right.type === left.type && right.unit && left.unit) { + const converted = convertUnit( + right.value, + right.unit, + left.unit, + precision + ); + + right = { + type: left.type, + value: converted, + unit: left.unit, + }; + } + + return { left, right }; + default: + return { left, right }; + } +} + +/** + * @param {import('../parser').ParenthesizedExpression} node + */ +function includesNoCssProperties(node) { + return ( + node.content.type !== 'Function' && + (node.content.type !== 'MathExpression' || + (node.content.right.type !== 'Function' && + node.content.left.type !== 'Function')) + ); +} +/** + * @param {import('../parser').CalcNode} node + * @param {number} precision + * @return {import('../parser').CalcNode} + */ +function reduce(node, precision) { + if (node.type === 'MathExpression') { + if (isAddSubOperator(node.operator)) { + // reduceAddSubExpression will call reduce recursively + return reduceAddSubExpression(node, precision); + } + node.left = reduce(node.left, precision); + node.right = reduce(node.right, precision); + switch (node.operator) { + case '/': + return reduceDivisionExpression(node); + case '*': + return reduceMultiplicationExpression(node); + } + + return node; + } + + if (node.type === 'ParenthesizedExpression') { + if (includesNoCssProperties(node)) { + return reduce(node.content, precision); + } + } + + return node; +} + +module.exports = reduce; diff --git a/node_modules/postcss-calc/src/lib/stringifier.js b/node_modules/postcss-calc/src/lib/stringifier.js new file mode 100644 index 00000000..50195bb5 --- /dev/null +++ b/node_modules/postcss-calc/src/lib/stringifier.js @@ -0,0 +1,98 @@ +'use strict'; +const order = { + '*': 0, + '/': 0, + '+': 1, + '-': 1, +}; + +/** + * @param {number} value + * @param {number | false} prec + */ +function round(value, prec) { + if (prec !== false) { + const precision = Math.pow(10, prec); + return Math.round(value * precision) / precision; + } + return value; +} + +/** + * @param {number | false} prec + * @param {import('../parser').CalcNode} node + * + * @return {string} + */ +function stringify(node, prec) { + switch (node.type) { + case 'MathExpression': { + const { left, right, operator: op } = node; + let str = ''; + if (left.type === 'MathExpression' && order[op] < order[left.operator]) { + str += `(${stringify(left, prec)})`; + } else { + str += stringify(left, prec); + } + + str += order[op] ? ` ${node.operator} ` : node.operator; + + if ( + right.type === 'MathExpression' && + order[op] < order[right.operator] + ) { + str += `(${stringify(right, prec)})`; + } else { + str += stringify(right, prec); + } + + return str; + } + case 'Number': + return round(node.value, prec).toString(); + case 'Function': + return node.value.toString(); + case 'ParenthesizedExpression': + return `(${stringify(node.content, prec)})`; + default: + return round(node.value, prec) + node.unit; + } +} + +/** + * @param {string} calc + * @param {import('../parser').CalcNode} node + * @param {string} originalValue + * @param {{precision: number | false, warnWhenCannotResolve: boolean}} options + * @param {import("postcss").Result} result + * @param {import("postcss").ChildNode} item + * + * @returns {string} + */ +module.exports = function (calc, node, originalValue, options, result, item) { + let str = stringify(node, options.precision); + + const shouldPrintCalc = + node.type === 'MathExpression' || node.type === 'Function' || + node.type === 'ParenthesizedExpression'; + + if (shouldPrintCalc) { + // if calc expression couldn't be resolved to a single value, re-wrap it as + // a calc() + if (node.type === 'ParenthesizedExpression') { + str = `${calc}${str}`; + } else { + str = `${calc}(${str})`; + } + + // if the warnWhenCannotResolve option is on, inform the user that the calc + // expression could not be resolved to a single value + if (options.warnWhenCannotResolve) { + result.warn('Could not reduce expression: ' + originalValue, { + plugin: 'postcss-calc', + node: item, + }); + } + } + return str; +}; diff --git a/node_modules/postcss-calc/src/lib/transform.js b/node_modules/postcss-calc/src/lib/transform.js new file mode 100644 index 00000000..78bff2ef --- /dev/null +++ b/node_modules/postcss-calc/src/lib/transform.js @@ -0,0 +1,109 @@ +'use strict'; +const selectorParser = require('postcss-selector-parser'); +const valueParser = require('postcss-value-parser'); + +const { parser } = require('../parser.js'); + +const reducer = require('./reducer.js'); +const stringifier = require('./stringifier.js'); + +const MATCH_CALC = /((?:-(moz|webkit)-)?calc)/i; + +/** + * @param {string} value + * @param {{precision: number, warnWhenCannotResolve: boolean}} options + * @param {import("postcss").Result} result + * @param {import("postcss").ChildNode} item + */ +function transformValue(value, options, result, item) { + return valueParser(value) + .walk((node) => { + // skip anything which isn't a calc() function + if (node.type !== 'function' || !MATCH_CALC.test(node.value)) { + return; + } + + // stringify calc expression and produce an AST + const contents = valueParser.stringify(node.nodes); + const ast = parser.parse(contents); + + // reduce AST to its simplest form, that is, either to a single value + // or a simplified calc expression + const reducedAst = reducer(ast, options.precision); + + // stringify AST and write it back + /** @type {valueParser.Node} */ (node).type = 'word'; + node.value = stringifier( + node.value, + reducedAst, + value, + options, + result, + item + ); + + return false; + }) + .toString(); +} +/** + * @param {import("postcss-selector-parser").Selectors} value + * @param {{precision: number, warnWhenCannotResolve: boolean}} options + * @param {import("postcss").Result} result + * @param {import("postcss").ChildNode} item + */ +function transformSelector(value, options, result, item) { + return selectorParser((selectors) => { + selectors.walk((node) => { + // attribute value + // e.g. the "calc(3*3)" part of "div[data-size="calc(3*3)"]" + if (node.type === 'attribute' && node.value) { + node.setValue(transformValue(node.value, options, result, item)); + } + + // tag value + // e.g. the "calc(3*3)" part of "div:nth-child(2n + calc(3*3))" + if (node.type === 'tag') { + node.value = transformValue(node.value, options, result, item); + } + + return; + }); + }).processSync(value); +} + +/** + * @param {any} node + * @param {{precision: number, preserve: boolean, warnWhenCannotResolve: boolean}} options + * @param {'value'|'params'|'selector'} property + * @param {import("postcss").Result} result + */ +module.exports = (node, property, options, result) => { + let value = node[property]; + + try { + value = + property === 'selector' + ? transformSelector(node[property], options, result, node) + : transformValue(node[property], options, result, node); + } catch (error) { + if (error instanceof Error) { + result.warn(error.message, { node }); + } else { + result.warn('Error', { node }); + } + return; + } + + // if the preserve option is enabled and the value has changed, write the + // transformed value into a cloned node which is inserted before the current + // node, preserving the original value. Otherwise, overwrite the original + // value. + if (options.preserve && node[property] !== value) { + const clone = node.clone(); + clone[property] = value; + node.parent.insertBefore(node, clone); + } else { + node[property] = value; + } +}; |
