| // Pre-simplify args once, route by name. Leaf folds receive simplified | |
| // args so they don't need to recurse into `simplify` themselves. | |
| import { simplifyMinMax } from './min-max.js'; | |
| import { simplifyClamp } from './clamp.js'; | |
| import { simplifyAbs } from './abs.js'; | |
| import { simplifySign } from './sign.js'; | |
| import { simplifyModRem } from './mod-rem.js'; | |
| import { simplifyRound } from './round.js'; | |
| import { simplifyTrig } from './trig.js'; | |
| import { simplifyInverseTrig } from './inverse-trig.js'; | |
| import { simplifyAtan2 } from './atan2.js'; | |
| import { simplifyPow } from './pow.js'; | |
| import { simplifySqrt } from './sqrt.js'; | |
| import { simplifyExp } from './exp.js'; | |
| import { simplifyLog } from './log.js'; | |
| import { simplifyHypot } from './hypot.js'; | |
| import { call } from '../node.js'; | |
| /** @typedef {import('../node.js').Node} Node */ | |
| /** @typedef {import('../simplify.js').SimplifyFn} SimplifyFn */ | |
| /** @typedef {(name: string, args: Node[]) => Node} MathSimplifier */ | |
| // Bare CSS math functions with implemented simplification semantics, keyed | |
| // by lowercase name. calc() and its vendor-prefixed forms are handled | |
| // separately as wrappers in simplifyCall. This map is the single source of | |
| // truth for dispatch, `isSupportedMathFunction`, and `QUICK_MATH_TEST`. | |
| /** @type {Map<string, MathSimplifier>} */ | |
| const MATH_SIMPLIFIERS = new Map([ | |
| ['min', simplifyMinMax], | |
| ['max', simplifyMinMax], | |
| ['clamp', (_name, args) => simplifyClamp(args)], | |
| ['abs', (_name, args) => simplifyAbs(args)], | |
| ['sign', (_name, args) => simplifySign(args)], | |
| ['mod', (_name, args) => simplifyModRem('mod', args)], | |
| ['rem', (_name, args) => simplifyModRem('rem', args)], | |
| ['round', (_name, args) => simplifyRound(args)], | |
| ['sin', (_name, args) => simplifyTrig('sin', args)], | |
| ['cos', (_name, args) => simplifyTrig('cos', args)], | |
| ['tan', (_name, args) => simplifyTrig('tan', args)], | |
| ['asin', (_name, args) => simplifyInverseTrig('asin', args)], | |
| ['acos', (_name, args) => simplifyInverseTrig('acos', args)], | |
| ['atan', (_name, args) => simplifyInverseTrig('atan', args)], | |
| ['atan2', (_name, args) => simplifyAtan2(args)], | |
| ['pow', (_name, args) => simplifyPow(args)], | |
| ['sqrt', (_name, args) => simplifySqrt(args)], | |
| ['hypot', (_name, args) => simplifyHypot(args)], | |
| ['log', (_name, args) => simplifyLog(args)], | |
| ['exp', (_name, args) => simplifyExp(args)], | |
| ]); | |
| const mathFnNames = [...MATH_SIMPLIFIERS.keys()].sort( | |
| (a, b) => b.length - a.length | |
| ); | |
| const QUICK_MATH_TEST = new RegExp( | |
| `(?:-(?:webkit|moz)-)?(?:calc|${mathFnNames.join('|')})\\(`, | |
| 'i' | |
| ); | |
| /** | |
| * Fast check to determine whether a CSS component value could contain | |
| * a supported calculation or math function call (or an escape sequence | |
| * that could decode to one). | |
| * | |
| * @param {string} value | |
| * @return {boolean} | |
| */ | |
| function hasPotentialMathFunction(value) { | |
| return ( | |
| value.includes('(') && (QUICK_MATH_TEST.test(value) || value.includes('\\')) | |
| ); | |
| } | |
| /** | |
| * Whether a bare CSS math function has an implemented simplifier. | |
| * | |
| * @param {string} name | |
| * @return {boolean} | |
| */ | |
| function isSupportedMathFunction(name) { | |
| return MATH_SIMPLIFIERS.has(name.toLowerCase()); | |
| } | |
| /** | |
| * @param {Extract<Node, { type: 'Call' }>} node | |
| * @param {SimplifyFn} simplify | |
| * @return {Node} | |
| */ | |
| function simplifyCall(node, simplify) { | |
| const name = node.name.toLowerCase(); | |
| if (name === 'calc' || name === '-webkit-calc' || name === '-moz-calc') { | |
| if (node.args.length !== 1) { | |
| throw new Error(`${node.name}() takes exactly one argument`); | |
| } | |
| return simplify(node.args[0]); | |
| } | |
| const args = node.args.map((a) => simplify(a)); | |
| const simplifier = MATH_SIMPLIFIERS.get(name); | |
| if (simplifier) { | |
| // min/max preserve the call's original casing in their opaque-args | |
| // fallback; the rest normalize to lowercase internally. | |
| return simplifier( | |
| name === 'min' || name === 'max' ? node.name : name, | |
| args | |
| ); | |
| } | |
| return call(node.name, args); | |
| } | |
| export { | |
| isSupportedMathFunction, | |
| simplifyCall, | |
| hasPotentialMathFunction, | |
| QUICK_MATH_TEST, | |
| }; |