diff --git a/packages/less/lib/less/functions/number.js b/packages/less/lib/less/functions/number.js index ade87303c2..3014b1473e 100644 --- a/packages/less/lib/less/functions/number.js +++ b/packages/less/lib/less/functions/number.js @@ -57,17 +57,55 @@ const minMax = function (isMin, args) { return new Anonymous(`${isMin ? 'min' : 'max'}(${args})`); }; -export default { - min: function(...args) { - try { - return minMax.call(this, true, args); - } catch (e) {} - }, - max: function(...args) { +/** + * Evaluate min/max arguments lazily. When an argument contains CSS-native + * constructs (e.g. var()) that cannot participate in Less math, re-evaluate + * it in calc context so Less variables resolve while operations pass through, + * and emit the CSS min()/max() call (#3777). + */ +function minMaxLazy(context, isMin, args) { + const evaluated = []; + for (let i = 0; i < args.length; i++) { try { - return minMax.call(this, false, args); - } catch (e) {} - }, + evaluated.push(args[i].eval(context)); + } catch (e) { + if (e && e.type === 'Operation') { + const prevMathOn = context.mathOn; + context.mathOn = false; + try { + evaluated.push(args[i].eval(context)); + } finally { + context.mathOn = prevMathOn; + } + } else { + throw e; + } + } + } + try { + return minMax.call({ context }, isMin, evaluated); + } catch (e) { + if (e && e.type === 'Argument' && e.message === 'incompatible types') { + const rendered = evaluated.map(a => a.toCSS(context)).join(context.compress ? ',' : ', '); + return new Anonymous(`${isMin ? 'min' : 'max'}(${rendered})`); + } + throw e; + } +} + +function min(context, ...args) { + return minMaxLazy(context, true, args); +} +min.evalArgs = false; + +function max(context, ...args) { + return minMaxLazy(context, false, args); +} +max.evalArgs = false; + +export default { + min, + max, convert: function (val, unit) { return val.convertTo(unit.value); }, diff --git a/packages/test-data/tests-unit/math-css-vars/math-css-vars.css b/packages/test-data/tests-unit/math-css-vars/math-css-vars.css index c1b97d6d33..8e4eafe3f1 100644 --- a/packages/test-data/tests-unit/math-css-vars/math-css-vars.css +++ b/packages/test-data/tests-unit/math-css-vars/math-css-vars.css @@ -8,3 +8,8 @@ a: 0.5; b: 5; } +.min-max-css-vars { + a: min(100% - var(--some-var), 10px); + b: max(100% - var(--x), 10px); + c: min(15px, var(--x)); +} diff --git a/packages/test-data/tests-unit/math-css-vars/math-css-vars.less b/packages/test-data/tests-unit/math-css-vars/math-css-vars.less index 7e5da60ba6..0064a1bed2 100644 --- a/packages/test-data/tests-unit/math-css-vars/math-css-vars.less +++ b/packages/test-data/tests-unit/math-css-vars/math-css-vars.less @@ -12,3 +12,11 @@ a: sin(30deg); b: ceil(4.2); } + +// min()/max() with calc-like args containing var() must pass through (#3777). +.min-max-css-vars { + @limit: 10px; + a: min(100% - var(--some-var), 10px); + b: max(100% - var(--x), @limit); + c: min(@limit + 5px, var(--x)); +}