From 6b9351852f4225d486d821d01000ed6692b86d21 Mon Sep 17 00:00:00 2001 From: Aman Awasthi Date: Fri, 25 Sep 2026 07:53:18 +0000 Subject: [PATCH 1/2] fix(functions): leave min()/max() intact when args contain CSS vars Evaluating Operation args before min/max caused "Operation on an invalid type" for calc-like expressions with var(). Evaluate args lazily (evalArgs: false) and fall back to emitting the CSS min()/max() call when Less math cannot resolve an argument. Fixes #3777 --- packages/less/lib/less/functions/number.js | 46 +++++++++++++++---- .../math-css-vars/math-css-vars.css | 4 ++ .../math-css-vars/math-css-vars.less | 6 +++ 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/packages/less/lib/less/functions/number.js b/packages/less/lib/less/functions/number.js index ade87303c2..8c552c4a0e 100644 --- a/packages/less/lib/less/functions/number.js +++ b/packages/less/lib/less/functions/number.js @@ -57,17 +57,43 @@ 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, fall back to + * emitting the CSS min()/max() call unchanged (#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) { + // Incompatible with Less evaluation — leave as a CSS function. + const rendered = args.map(a => a.toCSS(context)).join(context.compress ? ',' : ', '); + return new Anonymous(`${isMin ? 'min' : 'max'}(${rendered})`); + } + } + try { + return minMax.call({ context }, isMin, evaluated); + } catch (e) { + const rendered = args.map(a => a.toCSS(context)).join(context.compress ? ',' : ', '); + return new Anonymous(`${isMin ? 'min' : 'max'}(${rendered})`); + } +} + +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..cfee179fd8 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,7 @@ a: 0.5; b: 5; } +.min-max-css-vars { + a: min(100% - var(--some-var), 10px); + b: max(100% - var(--x), 10px); +} 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..a8fbaf322b 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,9 @@ a: sin(30deg); b: ceil(4.2); } + +// min()/max() with calc-like args containing var() must pass through (#3777). +.min-max-css-vars { + a: min(100% - var(--some-var), 10px); + b: max(100% - var(--x), 10px); +} From 4b4b517d93df75d2dba63e996091f6a11e7b2b07 Mon Sep 17 00:00:00 2001 From: Aman Awasthi Date: Fri, 23 Oct 2026 21:16:32 +0530 Subject: [PATCH 2/2] fix(functions): preserve evaluated Less values and rethrow errors in min/max fallback --- packages/less/lib/less/functions/number.js | 26 ++++++++++++++----- .../math-css-vars/math-css-vars.css | 1 + .../math-css-vars/math-css-vars.less | 4 ++- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/less/lib/less/functions/number.js b/packages/less/lib/less/functions/number.js index 8c552c4a0e..3014b1473e 100644 --- a/packages/less/lib/less/functions/number.js +++ b/packages/less/lib/less/functions/number.js @@ -59,8 +59,9 @@ const minMax = function (isMin, args) { /** * Evaluate min/max arguments lazily. When an argument contains CSS-native - * constructs (e.g. var()) that cannot participate in Less math, fall back to - * emitting the CSS min()/max() call unchanged (#3777). + * 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 = []; @@ -68,16 +69,27 @@ function minMaxLazy(context, isMin, args) { try { evaluated.push(args[i].eval(context)); } catch (e) { - // Incompatible with Less evaluation — leave as a CSS function. - const rendered = args.map(a => a.toCSS(context)).join(context.compress ? ',' : ', '); - return new Anonymous(`${isMin ? 'min' : 'max'}(${rendered})`); + 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) { - const rendered = args.map(a => a.toCSS(context)).join(context.compress ? ',' : ', '); - return new Anonymous(`${isMin ? 'min' : 'max'}(${rendered})`); + 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; } } 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 cfee179fd8..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 @@ -11,4 +11,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 a8fbaf322b..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 @@ -15,6 +15,8 @@ // 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), 10px); + b: max(100% - var(--x), @limit); + c: min(@limit + 5px, var(--x)); }