From ddca07f889dce9744b1588c922e7c7dd2ebe314a Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sat, 19 Sep 2026 09:38:49 +0200 Subject: [PATCH 1/5] wip --- packages/css-calc/CHANGELOG.md | 14 ++ packages/css-calc/dist/index.mjs | 2 +- packages/css-calc/src/functions/calc.ts | 5 +- packages/css-calc/src/functions/hypot.ts | 7 + packages/css-calc/src/functions/log.ts | 12 ++ packages/css-calc/src/functions/mod.ts | 5 +- packages/css-calc/src/functions/pow.ts | 7 + packages/css-calc/src/functions/random.ts | 133 +++++++++++------- packages/css-calc/src/functions/rem.ts | 5 +- packages/css-calc/src/functions/round.ts | 15 +- packages/css-calc/src/functions/tan.ts | 4 +- packages/css-calc/src/util/is-none.ts | 4 +- packages/css-calc/test/additional/random.mjs | 84 ++++++++++- .../test/additional/round-line-width.mjs | 49 +++++++ .../test/additional/tan-asymptotes.mjs | 48 ++++++- .../css-calc/test/wpt/exp-log-compute.mjs | 57 ++++++++ .../test/wpt/hypot-pow-sqrt-computed.mjs | 46 ++++++ packages/css-calc/test/wpt/round-function.mjs | 62 ++++++++ .../test/wpt/round-mod-rem-computed.mjs | 21 +++ 19 files changed, 508 insertions(+), 72 deletions(-) diff --git a/packages/css-calc/CHANGELOG.md b/packages/css-calc/CHANGELOG.md index db1718b834..713c855b37 100644 --- a/packages/css-calc/CHANGELOG.md +++ b/packages/css-calc/CHANGELOG.md @@ -2,6 +2,20 @@ ### Unreleased (patch) +- Fixed `tan()` at asymptote values beyond the first period (`270deg`, `-270deg`, ...) to match the specification +- Fixed `round(line-width, ...)` to choose the non-zero candidate multiple when `A` is negative +- Fixed `round(down/up, ...)` with a negative step to choose the correct candidate multiple +- Fixed `round()`, `mod()`, and `rem()` to propagate `NaN` as specified +- Fixed `pow(NaN, 0)` to return `NaN` as specified +- Fixed `log(A, 0)` to return `NaN` as specified (only `B` values between 0 and 1, or greater than 1, are valid) +- Fixed `log(1, B)` to return `0⁺` as specified +- Fixed `random()` to not mutate the caller's `options` object +- Fixed `random(fixed , ...)` to clamp the value to the highest representable value less than 1 +- Fixed `random()` to treat `max < min` as `max = min` instead of swapping the arguments +- Fixed `random()` to not return an unreachable `max` when a `step` is given +- Fixed `random()` to return `A` (the minimum) when `A` is infinite, instead of `NaN` +- Fixed `log(A, 1)` to return `NaN` +- Fixed `hypot()` to propagate `NaN` as specified - Updated [`@csstools/css-tokenizer`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer) to [`4.0.1`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer/CHANGELOG.md#401) (patch) ### 3.4.0 diff --git a/packages/css-calc/dist/index.mjs b/packages/css-calc/dist/index.mjs index 225f2f19fc..6ad63a1abd 100644 --- a/packages/css-calc/dist/index.mjs +++ b/packages/css-calc/dist/index.mjs @@ -1 +1 @@ -import{sourceIndices as e,TokenNode as n,isTokenNode as t,isWhitespaceNode as r,isCommentNode as a,isWhiteSpaceOrCommentNode as u,isSimpleBlockNode as o,isFunctionNode as i,FunctionNode as l,WhitespaceNode as c,parseCommaSeparatedListOfComponentValues as s,walk as v}from"@csstools/css-parser-algorithms";import{isTokenDimension as f,TokenType as p,NumberType as m,mutateUnit as d,isTokenNumber as C,isTokenPercentage as g,isTokenIdent as D,isTokenNumeric as h,isTokenOpenParen as N,isTokenDelim as B,isTokenComma as A,isToken as b,tokenizer as F,tokenize as w,stringify as E,isTokenColon as S,isTokenSemicolon as y}from"@csstools/css-tokenizer";class ParseError extends Error{sourceStart;sourceEnd;constructor(e,n,t){super(e),this.name="ParseError",this.sourceStart=n,this.sourceEnd=t}}class ParseErrorWithComponentValues extends ParseError{componentValues;constructor(n,t){super(n,...e(t)),this.componentValues=t}}const I={UnexpectedAdditionOfDimensionOrPercentageWithNumber:"Unexpected addition of a dimension or percentage with a number.",UnexpectedSubtractionOfDimensionOrPercentageWithNumber:"Unexpected subtraction of a dimension or percentage with a number."},x=/[A-Z]/g;function toLowerCaseAZ(e){return e.replace(x,e=>String.fromCharCode(e.charCodeAt(0)+32))}const M={cm:"px",in:"px",mm:"px",pc:"px",pt:"px",px:"px",q:"px",deg:"deg",grad:"deg",rad:"deg",turn:"deg",ms:"s",s:"s",hz:"hz",khz:"hz"},T=new Map([["cm",e=>e],["mm",e=>10*e],["q",e=>40*e],["in",e=>e/2.54],["pc",e=>e/2.54*6],["pt",e=>e/2.54*72],["px",e=>e/2.54*96]]),P=new Map([["deg",e=>e],["grad",e=>e/.9],["rad",e=>e/180*Math.PI],["turn",e=>e/360]]),k=new Map([["deg",e=>.9*e],["grad",e=>e],["rad",e=>.9*e/180*Math.PI],["turn",e=>.9*e/360]]),W=new Map([["hz",e=>e],["khz",e=>e/1e3]]),O=new Map([["cm",e=>2.54*e],["mm",e=>25.4*e],["q",e=>25.4*e*4],["in",e=>e],["pc",e=>6*e],["pt",e=>72*e],["px",e=>96*e]]),U=new Map([["hz",e=>1e3*e],["khz",e=>e]]),L=new Map([["cm",e=>e/10],["mm",e=>e],["q",e=>4*e],["in",e=>e/25.4],["pc",e=>e/25.4*6],["pt",e=>e/25.4*72],["px",e=>e/25.4*96]]),$=new Map([["ms",e=>e],["s",e=>e/1e3]]),V=new Map([["cm",e=>e/6*2.54],["mm",e=>e/6*25.4],["q",e=>e/6*25.4*4],["in",e=>e/6],["pc",e=>e],["pt",e=>e/6*72],["px",e=>e/6*96]]),Z=new Map([["cm",e=>e/72*2.54],["mm",e=>e/72*25.4],["q",e=>e/72*25.4*4],["in",e=>e/72],["pc",e=>e/72*6],["pt",e=>e],["px",e=>e/72*96]]),z=new Map([["cm",e=>e/96*2.54],["mm",e=>e/96*25.4],["q",e=>e/96*25.4*4],["in",e=>e/96],["pc",e=>e/96*6],["pt",e=>e/96*72],["px",e=>e]]),q=new Map([["cm",e=>e/4/10],["mm",e=>e/4],["q",e=>e],["in",e=>e/4/25.4],["pc",e=>e/4/25.4*6],["pt",e=>e/4/25.4*72],["px",e=>e/4/25.4*96]]),G=new Map([["deg",e=>180*e/Math.PI],["grad",e=>180*e/Math.PI/.9],["rad",e=>e],["turn",e=>180*e/Math.PI/360]]),R=new Map([["ms",e=>1e3*e],["s",e=>e]]),j=new Map([["deg",e=>360*e],["grad",e=>360*e/.9],["rad",e=>360*e/180*Math.PI],["turn",e=>e]]),Y=new Map([["cm",T],["mm",L],["q",q],["in",O],["pc",V],["pt",Z],["px",z],["ms",$],["s",R],["deg",P],["grad",k],["rad",G],["turn",j],["hz",W],["khz",U]]);function convertUnit(e,n){if(!f(e))return n;if(!f(n))return n;const t=toLowerCaseAZ(e[4].unit),r=toLowerCaseAZ(n[4].unit);if(t===r)return n;const a=Y.get(r);if(!a)return n;const u=a.get(t);if(!u)return n;const o=u(n[4].value),i=[p.Dimension,"",n[2],n[3],{...n[4],signCharacter:o<0?"-":void 0,type:Number.isInteger(o)?m.Integer:m.Number,value:o}];return d(i,e[4].unit),i}function toCanonicalUnit(e){if(!f(e))return e;const n=toLowerCaseAZ(e[4].unit),t=M[n];if(n===t)return e;const r=Y.get(n);if(!r)return e;const a=r.get(t);if(!a)return e;const u=a(e[4].value),o=[p.Dimension,"",e[2],e[3],{...e[4],signCharacter:u<0?"-":void 0,type:Number.isInteger(u)?m.Integer:m.Number,value:u}];return d(o,t),o}function addition(e,t){if(2!==e.length)return-1;const r=e[0].value;let a=e[1].value;if(C(r)&&C(a)){const e=r[4].value+a[4].value;return new n([p.Number,e.toString(),r[2],a[3],{value:e,type:r[4].type===m.Integer&&a[4].type===m.Integer?m.Integer:m.Number}])}if(g(r)&&g(a)){const e=r[4].value+a[4].value;return new n([p.Percentage,e.toString()+"%",r[2],a[3],{value:e}])}if(f(r)&&f(a)&&(a=convertUnit(r,a),toLowerCaseAZ(r[4].unit)===toLowerCaseAZ(a[4].unit))){const e=r[4].value+a[4].value;return new n([p.Dimension,e.toString()+r[4].unit,r[2],a[3],{value:e,type:r[4].type===m.Integer&&a[4].type===m.Integer?m.Integer:m.Number,unit:r[4].unit}])}return(C(r)&&(f(a)||g(a))||C(a)&&(f(r)||g(r)))&&t.onParseError?.(new ParseErrorWithComponentValues(I.UnexpectedAdditionOfDimensionOrPercentageWithNumber,e)),-1}function division(e){if(2!==e.length)return-1;const t=e[0].value,r=e[1].value;if(C(t)&&C(r)){const e=t[4].value/r[4].value;return new n([p.Number,e.toString(),t[2],r[3],{value:e,type:Number.isInteger(e)?m.Integer:m.Number}])}if(g(t)&&C(r)){const e=t[4].value/r[4].value;return new n([p.Percentage,e.toString()+"%",t[2],r[3],{value:e}])}if(f(t)&&C(r)){const e=t[4].value/r[4].value;return new n([p.Dimension,e.toString()+t[4].unit,t[2],r[3],{value:e,type:Number.isInteger(e)?m.Integer:m.Number,unit:t[4].unit}])}return-1}function isCalculation(e){return!!e&&"object"==typeof e&&"inputs"in e&&Array.isArray(e.inputs)&&"operation"in e}function solve(e,n){if(-1===e)return-1;const r=[];for(let a=0;aconvertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;const o=u.map(e=>e[4].value),i=Math.hypot(...o);return resultToCalculation(e,a,i)}function solveMax(e,n,r){if(!n.every(t))return-1;const a=n[0].value;if(!h(a))return-1;if(!r.rawPercentages&&g(a))return-1;const u=n.map(e=>convertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;const o=u.map(e=>e[4].value),i=Math.max(...o);return resultToCalculation(e,a,i)}function solveMin(e,n,r){if(!n.every(t))return-1;const a=n[0].value;if(!h(a))return-1;if(!r.rawPercentages&&g(a))return-1;const u=n.map(e=>convertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;const o=u.map(e=>e[4].value),i=Math.min(...o);return resultToCalculation(e,a,i)}function solveMod(e,n,t){const r=n.value;if(!h(r))return-1;const a=convertUnit(r,t.value);if(!twoOfSameNumeric(r,a))return-1;let u;return u=0===a[4].value?Number.NaN:Number.isFinite(r[4].value)&&(Number.isFinite(a[4].value)||(a[4].value!==Number.POSITIVE_INFINITY||r[4].value!==Number.NEGATIVE_INFINITY&&!Object.is(0*r[4].value,-0))&&(a[4].value!==Number.NEGATIVE_INFINITY||r[4].value!==Number.POSITIVE_INFINITY&&!Object.is(0*r[4].value,0)))?Number.isFinite(a[4].value)?(r[4].value%a[4].value+a[4].value)%a[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,u)}function solvePow(e,n,t){const r=n.value,a=t.value;if(!C(r))return-1;if(!twoOfSameNumeric(r,a))return-1;return numberToCalculation(e,Math.pow(r[4].value,a[4].value))}function solveRem(e,n,t){const r=n.value;if(!h(r))return-1;const a=convertUnit(r,t.value);if(!twoOfSameNumeric(r,a))return-1;let u;return u=0===a[4].value?Number.NaN:Number.isFinite(r[4].value)?Number.isFinite(a[4].value)?r[4].value%a[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,u)}function snapAsBorderWidth(e,n,t){if(!f(n))return-1;const r=t.devicePixelLength??1,a=[p.Dimension,`${r}px`,n[2],n[3],{value:r,type:m.Integer,unit:"px"}],u=convertUnit(a,n);if(!twoOfSameNumeric(u,a))return-1;if(Number.isInteger(u[4].value/r))return resultToCalculation(e,n,n[4].value);if(u[4].value>0){if(u[4].value0?1/0:Object.is(0,0*u[4].value)?0:-0;break;default:i=Object.is(0,0*u[4].value)?0:-0}else switch(n){case"down":i=Math.floor(u[4].value/o[4].value)*o[4].value;break;case"up":i=Math.ceil(u[4].value/o[4].value)*o[4].value;break;case"to-zero":i=Math.trunc(u[4].value/o[4].value)*o[4].value;break;default:{let t=Math.floor(u[4].value/o[4].value)*o[4].value,r=Math.ceil(u[4].value/o[4].value)*o[4].value;if(t>r){const e=t;t=r,r=e}const l=Math.abs(u[4].value-t),c=Math.abs(u[4].value-r);if(i="line-width"===n&&u[4].value>=0&&(0===r||0===t)?0!==r?r:t:l===c?r:l0?1/0:-1/0:Math.tan(u),numberToCalculation(e,u)}function subtraction(e,t){if(2!==e.length)return-1;const r=e[0].value;let a=e[1].value;if(C(r)&&C(a)){const e=r[4].value-a[4].value;return new n([p.Number,e.toString(),r[2],a[3],{value:e,type:r[4].type===m.Integer&&a[4].type===m.Integer?m.Integer:m.Number}])}if(g(r)&&g(a)){const e=r[4].value-a[4].value;return new n([p.Percentage,e.toString()+"%",r[2],a[3],{value:e}])}if(f(r)&&f(a)&&(a=convertUnit(r,a),toLowerCaseAZ(r[4].unit)===toLowerCaseAZ(a[4].unit))){const e=r[4].value-a[4].value;return new n([p.Dimension,e.toString()+r[4].unit,r[2],a[3],{value:e,type:r[4].type===m.Integer&&a[4].type===m.Integer?m.Integer:m.Number,unit:r[4].unit}])}return(C(r)&&(f(a)||g(a))||C(a)&&(f(r)||g(r)))&&t.onParseError?.(new ParseErrorWithComponentValues(I.UnexpectedSubtractionOfDimensionOrPercentageWithNumber,e)),-1}function solveLog(e,n){if(1===n.length){const r=n[0];if(!r||!t(r))return-1;const a=r.value;if(!C(a))return-1;return numberToCalculation(e,Math.log(a[4].value))}if(2===n.length){const r=n[0];if(!r||!t(r))return-1;const a=r.value;if(!C(a))return-1;const u=n[1];if(!u||!t(u))return-1;const o=u.value;if(!C(o))return-1;return numberToCalculation(e,Math.log(a[4].value)/Math.log(o[4].value))}return-1}const _=/^none$/i;function isNone(e){if(Array.isArray(e)){const n=e.filter(e=>!(r(e)&&a(e)));return 1===n.length&&isNone(n[0])}if(!t(e))return!1;const n=e.value;return!!D(n)&&_.test(n[4].value)}const H=String.fromCodePoint(0);function solveRandom(e,n,t,r,a,u){if(-1===n.fixed&&!u.randomCaching)return-1;u.randomCaching||(u.randomCaching={propertyName:"",propertyN:0,elementID:"",documentID:""}),u.randomCaching&&!u.randomCaching.propertyN&&(u.randomCaching.propertyN=0);const o=t.value;if(!h(o))return-1;const i=convertUnit(o,r.value);if(!twoOfSameNumeric(o,i))return-1;let l=null;if(a&&(l=convertUnit(o,a.value),!twoOfSameNumeric(o,l)))return-1;if(!Number.isFinite(o[4].value))return resultToCalculation(e,o,Number.NaN);if(!Number.isFinite(i[4].value))return resultToCalculation(e,o,Number.NaN);if(!Number.isFinite(i[4].value-o[4].value))return resultToCalculation(e,o,Number.NaN);if(l&&!Number.isFinite(l[4].value))return resultToCalculation(e,o,o[4].value);const c=-1===n.fixed?sfc32(crc32([n.dashedIdent?n.dashedIdent:"",n.elementScoped?u.randomCaching.elementID:"",n.propertyScoped||n.propertyIndexScoped?u.randomCaching.propertyName:"",n.propertyIndexScoped?u.randomCaching.propertyN:"",u.randomCaching.documentID].join(H))):()=>n.fixed;let s=o[4].value,v=i[4].value;if(s>v&&([s,v]=[v,s]),l&&(l[4].value<=0||Math.abs(s-v)/l[4].value>1e10)&&(l=null),l){const n=Math.max(l[4].value/1e3,1e-9),t=[s];let r=0;for(;;){r+=l[4].value;const e=s+r;if(!(e+nv)break}const a=c();return resultToCalculation(e,o,Number(t[Math.floor(t.length*a)].toFixed(5)))}const f=c();return resultToCalculation(e,o,Number((f*(v-s)+s).toFixed(5)))}function sfc32(e=.34944106645296036,n=.19228640875738723,t=.8784393832007205,r=.04850964319275053){return()=>{const a=((e|=0)+(n|=0)|0)+(r|=0)|0;return r=r+1|0,e=n^n>>>9,n=(t|=0)+(t<<3)|0,t=(t=t<<21|t>>>11)+a|0,(a>>>0)/4294967296}}function crc32(e){let n,t,r=0;r^=-1;for(let a=0,u=e.length;a>>8^n;return(-1^r)>>>0}function solveCalcMix(e,n){const t=n[0].calcSum.value;if(!h(t))return-1;const r=n.map(e=>({calcSum:convertUnit(t,e.calcSum.value),percentage:e.percentage}));if(!arrayOfSameNumeric(r.map(e=>e.calcSum)))return-1;const{items:a}=normalizeMixPercentages(r,!1),u=a.map(e=>e.calcSum[4].value*e.percentage/100);let o=0;for(let e=0;e100||u>0&&n)for(const e of a)e.percentage=e.percentage*(100/u);let o=0;return u<100&&(o=100-u),{items:a,leftover:o}}const J=new Map([["abs",function abs(e,n,t){return singleNodeSolver(e,n,t,solveAbs)}],["acos",function acos(e,n,t){return singleNodeSolver(e,n,t,solveACos)}],["asin",function asin(e,n,t){return singleNodeSolver(e,n,t,solveASin)}],["atan",function atan(e,n,t){return singleNodeSolver(e,n,t,solveATan)}],["atan2",function atan2(e,n,t){return twoCommaSeparatedNodesSolver(e,n,t,solveATan2)}],["calc",calc$1],["calc-mix",function calcMix(e,n,t){const r=variadicArgumentsCalcMix(e,e.value,n,t);if(-1===r)return-1;return solveCalcMix(e,r)}],["clamp",function clamp(r,a,o){const i=resolveGlobalsAndConstants([...r.value.filter(e=>!u(e))],a),c=[],s=[],v=[];{let e=c;for(let n=0;n!u(e)),n,t);if(-1===r)return-1;const[a,o]=r,i=variadicArguments(e,o,n,t);if(-1===i)return-1;const[l,c,s]=i;if(!l||!c)return-1;return solveRandom(e,a,l,c,s,t)}],["rem",function rem(e,n,t){return twoCommaSeparatedNodesSolver(e,n,t,solveRem)}],["round",function round(e,r,a){const o=resolveGlobalsAndConstants([...e.value.filter(e=>!u(e))],r);let i="",l=!1;const c=[],s=[];{let e=c;for(let n=0;n!u(e))],n);if(1===a.length&&t(a[0]))return{inputs:[a[0]],operation:unary};let l=0;for(;l!u(e))],n),a=solve(calc$1(calcWrapper(e,r),n,t),t);return-1===a?-1:a}function twoCommaSeparatedNodesSolver(e,n,t,r){const a=twoCommaSeparatedArguments(e,n,t);if(-1===a)return-1;const[u,o]=a;return r(e,u,o,t)}function twoCommaSeparatedArguments(e,n,r){const a=resolveGlobalsAndConstants([...e.value.filter(e=>!u(e))],n),o=[],i=[];{let e=o;for(let n=0;n!u(e))],r),i=[];{const n=[];let u=[];for(let e=0;e!u(e))],r),i=[];{const n=[];let l=[];for(let e=0;e=0;i--){if(u(n[o][i]))continue;if(l=solve(calc$1(calcWrapper(e,n[o].slice(0,i+1)),r,a),a),-1===l)continue;const s=n[o].slice(i+1).filter(e=>!u(e));if(s.length)if(1===s.length&&t(s[0])&&g(s[0].value)){if(c=s[0].value[4].value,c<0||c>100)return-1}else{const n=solve(calc$1(calcWrapper(e,s),r,a),a);if(-1===n)return-1;if(!g(n.value))return-1;c=n.value[4].value,c=Math.min(100,Math.max(0,c))}break}if(-1===l)return-1;i.push({calcSum:l,percentage:c})}}return i}const K=new Set(["nearest","line-width","up","down","to-zero"]);function parseRandomValueSharing(e,n,r,a){const u={dashedIdent:"",fixed:-1,elementScoped:!1,propertyScoped:!1,propertyIndexScoped:!1};let o=!1;const i=n[0];if(!t(i)||!D(i.value))return[u,n];for(let i=0;i1)return-1;u.fixed=Math.max(0,Math.min(l.value[4].value,1-1e-9));continue}if("auto"!==c){if(!c.startsWith("--"))return-1;if(-1!==u.fixed||o||u.dashedIdent)return-1;u.dashedIdent=c}else{if(-1!==u.fixed||o||u.dashedIdent||u.elementScoped||u.propertyScoped||u.propertyIndexScoped)return-1;u.elementScoped=!0,u.propertyIndexScoped=!0,o=!0}}else{if(-1!==u.fixed||o||u.propertyScoped||u.propertyIndexScoped)return-1;u.propertyIndexScoped=!0}else{if(-1!==u.fixed||o||u.propertyScoped||u.propertyIndexScoped)return-1;u.propertyScoped=!0}else{if(-1!==u.fixed||o||u.elementScoped)return-1;u.elementScoped=!0}}return-1}function calcWrapper(e,n){return new l([p.Function,"calc(",e.name[2],e.name[3],{value:"calc"}],[p.CloseParen,")",e.endToken[2],e.endToken[3],void 0],n)}function maxWrapper(t,r,a){return new l([p.Function,"max(",t.name[2],t.name[3],{value:"max"}],[p.CloseParen,")",t.endToken[2],t.endToken[3],void 0],[r,new n([p.Comma,",",...e(r),void 0]),a])}function patchNaN(e){if(-1===e)return-1;if(i(e))return e;const t=e.value;return h(t)&&Number.isNaN(t[4].value)?C(t)?new l([p.Function,"calc(",t[2],t[3],{value:"calc"}],[p.CloseParen,")",t[2],t[3],void 0],[new n([p.Ident,"NaN",t[2],t[3],{value:"NaN"}])]):f(t)?new l([p.Function,"calc(",t[2],t[3],{value:"calc"}],[p.CloseParen,")",t[2],t[3],void 0],[new n([p.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new c([[p.Whitespace," ",t[2],t[3],void 0]]),new n([p.Delim,"*",t[2],t[3],{value:"*"}]),new c([[p.Whitespace," ",t[2],t[3],void 0]]),new n([p.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:m.Integer,unit:t[4].unit}])]):g(t)?new l([p.Function,"calc(",t[2],t[3],{value:"calc"}],[p.CloseParen,")",t[2],t[3],void 0],[new n([p.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new c([[p.Whitespace," ",t[2],t[3],void 0]]),new n([p.Delim,"*",t[2],t[3],{value:"*"}]),new c([[p.Whitespace," ",t[2],t[3],void 0]]),new n([p.Percentage,"1%",t[2],t[3],{value:1}])]):-1:e}function patchInfinity(e){if(-1===e)return-1;if(i(e))return e;const t=e.value;if(!h(t))return e;if(Number.isFinite(t[4].value)||Number.isNaN(t[4].value))return e;let r="";return Number.NEGATIVE_INFINITY===t[4].value&&(r="-"),C(t)?new l([p.Function,"calc(",t[2],t[3],{value:"calc"}],[p.CloseParen,")",t[2],t[3],void 0],[new n([p.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}])]):f(t)?new l([p.Function,"calc(",t[2],t[3],{value:"calc"}],[p.CloseParen,")",t[2],t[3],void 0],[new n([p.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new c([[p.Whitespace," ",t[2],t[3],void 0]]),new n([p.Delim,"*",t[2],t[3],{value:"*"}]),new c([[p.Whitespace," ",t[2],t[3],void 0]]),new n([p.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:m.Integer,unit:t[4].unit}])]):new l([p.Function,"calc(",t[2],t[3],{value:"calc"}],[p.CloseParen,")",t[2],t[3],void 0],[new n([p.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new c([[p.Whitespace," ",t[2],t[3],void 0]]),new n([p.Delim,"*",t[2],t[3],{value:"*"}]),new c([[p.Whitespace," ",t[2],t[3],void 0]]),new n([p.Percentage,"1%",t[2],t[3],{value:1}])])}function patchMinusZero(e){if(-1===e)return-1;if(i(e))return e;const n=e.value;return h(n)&&Object.is(-0,n[4].value)?("-0"===n[1]||(g(n)?n[1]="-0%":f(n)?n[1]="-0"+n[4].unit:n[1]="-0"),e):e}function patchPrecision(e,n=13){if(-1===e)return-1;if(n<=0)return e;if(i(e))return e;const t=e.value;if(!h(t))return e;if(Number.isInteger(t[4].value))return e;const r=Number(t[4].value.toFixed(n)).toString();return C(t)?t[1]=r:g(t)?t[1]=r+"%":f(t)&&(t[1]=r+t[4].unit),e}function patchCanonicalUnit(e){return-1===e?-1:i(e)?e:f(e.value)?(e.value=toCanonicalUnit(e.value),e):e}function patchCalcResult(e,n){let t=e;return n?.toCanonicalUnits&&(t=patchCanonicalUnit(t)),t=patchPrecision(t,n?.precision),t=patchMinusZero(t),n?.censorIntoStandardRepresentableValues||(t=patchNaN(t),t=patchInfinity(t)),t}function tokenizeGlobals(e){const n=new Map;if(!e)return n;for(const[t,r]of e)if(b(r))n.set(t,r);else if("string"==typeof r){const e=F({css:r}),a=e.nextToken();if(e.nextToken(),!e.endOfFile())continue;if(!h(a))continue;n.set(t,a);continue}return n}function calc(e,n){return calcFromComponentValues(s(w({css:e}),{}),n).map(e=>e.map(e=>E(...e.tokens())).join("")).join(",")}function calcFromComponentValues(e,n){const t=tokenizeGlobals(n?.globals);return replaceComponentValues(e,e=>{if(!i(e))return;const r=J.get(e.getName().toLowerCase());if(!r)return;const a=patchCalcResult(solve(r(e,t,n??{}),n??{}),n);return-1!==a?n?.calcWrapper?calcWrapper(e,[a]):a:void 0})}function replaceComponentValues(n,r){for(let a=0;a{if("number"!=typeof a)return;const o=r(n.node);if(!o)return;const i=[o],l=n.parent.value[a-1];t(l)&&B(l.value)&&("-"===l.value[4].value||"+"===l.value[4].value)&&i.splice(0,0,new c([[p.Whitespace," ",...e(n.node),void 0]]));const s=n.parent.value[a+1];!s||u(s)||t(s)&&(A(s.value)||S(s.value)||y(s.value)||B(s.value)&&"-"!==s.value[4].value&&"+"!==s.value[4].value)||i.push(new c([[p.Whitespace," ",...e(n.node),void 0]])),n.parent.value.splice(a,1,...i)})}return n}const Q=new Set(J.keys());export{ParseError,I as ParseErrorMessage,ParseErrorWithComponentValues,calc,calcFromComponentValues,Q as mathFunctionNames}; +import{sourceIndices as e,TokenNode as n,isTokenNode as t,isWhiteSpaceOrCommentNode as r,isSimpleBlockNode as a,isFunctionNode as u,FunctionNode as o,WhitespaceNode as i,parseCommaSeparatedListOfComponentValues as l,walk as c}from"@csstools/css-parser-algorithms";import{isTokenDimension as s,TokenType as v,NumberType as f,mutateUnit as p,isTokenNumber as m,isTokenPercentage as d,isTokenIdent as C,isTokenNumeric as g,isTokenOpenParen as N,isTokenDelim as h,isTokenComma as D,isToken as B,tokenizer as b,tokenize as A,stringify as F,isTokenColon as w,isTokenSemicolon as E}from"@csstools/css-tokenizer";class ParseError extends Error{sourceStart;sourceEnd;constructor(e,n,t){super(e),this.name="ParseError",this.sourceStart=n,this.sourceEnd=t}}class ParseErrorWithComponentValues extends ParseError{componentValues;constructor(n,t){super(n,...e(t)),this.componentValues=t}}const S={UnexpectedAdditionOfDimensionOrPercentageWithNumber:"Unexpected addition of a dimension or percentage with a number.",UnexpectedSubtractionOfDimensionOrPercentageWithNumber:"Unexpected subtraction of a dimension or percentage with a number."},I=/[A-Z]/g;function toLowerCaseAZ(e){return e.replace(I,e=>String.fromCharCode(e.charCodeAt(0)+32))}const y={cm:"px",in:"px",mm:"px",pc:"px",pt:"px",px:"px",q:"px",deg:"deg",grad:"deg",rad:"deg",turn:"deg",ms:"s",s:"s",hz:"hz",khz:"hz"},M=new Map([["cm",e=>e],["mm",e=>10*e],["q",e=>40*e],["in",e=>e/2.54],["pc",e=>e/2.54*6],["pt",e=>e/2.54*72],["px",e=>e/2.54*96]]),x=new Map([["deg",e=>e],["grad",e=>e/.9],["rad",e=>e/180*Math.PI],["turn",e=>e/360]]),T=new Map([["deg",e=>.9*e],["grad",e=>e],["rad",e=>.9*e/180*Math.PI],["turn",e=>.9*e/360]]),P=new Map([["hz",e=>e],["khz",e=>e/1e3]]),k=new Map([["cm",e=>2.54*e],["mm",e=>25.4*e],["q",e=>25.4*e*4],["in",e=>e],["pc",e=>6*e],["pt",e=>72*e],["px",e=>96*e]]),W=new Map([["hz",e=>1e3*e],["khz",e=>e]]),O=new Map([["cm",e=>e/10],["mm",e=>e],["q",e=>4*e],["in",e=>e/25.4],["pc",e=>e/25.4*6],["pt",e=>e/25.4*72],["px",e=>e/25.4*96]]),U=new Map([["ms",e=>e],["s",e=>e/1e3]]),L=new Map([["cm",e=>e/6*2.54],["mm",e=>e/6*25.4],["q",e=>e/6*25.4*4],["in",e=>e/6],["pc",e=>e],["pt",e=>e/6*72],["px",e=>e/6*96]]),$=new Map([["cm",e=>e/72*2.54],["mm",e=>e/72*25.4],["q",e=>e/72*25.4*4],["in",e=>e/72],["pc",e=>e/72*6],["pt",e=>e],["px",e=>e/72*96]]),V=new Map([["cm",e=>e/96*2.54],["mm",e=>e/96*25.4],["q",e=>e/96*25.4*4],["in",e=>e/96],["pc",e=>e/96*6],["pt",e=>e/96*72],["px",e=>e]]),Z=new Map([["cm",e=>e/4/10],["mm",e=>e/4],["q",e=>e],["in",e=>e/4/25.4],["pc",e=>e/4/25.4*6],["pt",e=>e/4/25.4*72],["px",e=>e/4/25.4*96]]),z=new Map([["deg",e=>180*e/Math.PI],["grad",e=>180*e/Math.PI/.9],["rad",e=>e],["turn",e=>180*e/Math.PI/360]]),q=new Map([["ms",e=>1e3*e],["s",e=>e]]),G=new Map([["deg",e=>360*e],["grad",e=>360*e/.9],["rad",e=>360*e/180*Math.PI],["turn",e=>e]]),R=new Map([["cm",M],["mm",O],["q",Z],["in",k],["pc",L],["pt",$],["px",V],["ms",U],["s",q],["deg",x],["grad",T],["rad",z],["turn",G],["hz",P],["khz",W]]);function convertUnit(e,n){if(!s(e))return n;if(!s(n))return n;const t=toLowerCaseAZ(e[4].unit),r=toLowerCaseAZ(n[4].unit);if(t===r)return n;const a=R.get(r);if(!a)return n;const u=a.get(t);if(!u)return n;const o=u(n[4].value),i=[v.Dimension,"",n[2],n[3],{...n[4],signCharacter:o<0?"-":void 0,type:Number.isInteger(o)?f.Integer:f.Number,value:o}];return p(i,e[4].unit),i}function toCanonicalUnit(e){if(!s(e))return e;const n=toLowerCaseAZ(e[4].unit),t=y[n];if(n===t)return e;const r=R.get(n);if(!r)return e;const a=r.get(t);if(!a)return e;const u=a(e[4].value),o=[v.Dimension,"",e[2],e[3],{...e[4],signCharacter:u<0?"-":void 0,type:Number.isInteger(u)?f.Integer:f.Number,value:u}];return p(o,t),o}function addition(e,t){if(2!==e.length)return-1;const r=e[0].value;let a=e[1].value;if(m(r)&&m(a)){const e=r[4].value+a[4].value;return new n([v.Number,e.toString(),r[2],a[3],{value:e,type:r[4].type===f.Integer&&a[4].type===f.Integer?f.Integer:f.Number}])}if(d(r)&&d(a)){const e=r[4].value+a[4].value;return new n([v.Percentage,e.toString()+"%",r[2],a[3],{value:e}])}if(s(r)&&s(a)&&(a=convertUnit(r,a),toLowerCaseAZ(r[4].unit)===toLowerCaseAZ(a[4].unit))){const e=r[4].value+a[4].value;return new n([v.Dimension,e.toString()+r[4].unit,r[2],a[3],{value:e,type:r[4].type===f.Integer&&a[4].type===f.Integer?f.Integer:f.Number,unit:r[4].unit}])}return(m(r)&&(s(a)||d(a))||m(a)&&(s(r)||d(r)))&&t.onParseError?.(new ParseErrorWithComponentValues(S.UnexpectedAdditionOfDimensionOrPercentageWithNumber,e)),-1}function division(e){if(2!==e.length)return-1;const t=e[0].value,r=e[1].value;if(m(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Number,e.toString(),t[2],r[3],{value:e,type:Number.isInteger(e)?f.Integer:f.Number}])}if(d(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Percentage,e.toString()+"%",t[2],r[3],{value:e}])}if(s(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Dimension,e.toString()+t[4].unit,t[2],r[3],{value:e,type:Number.isInteger(e)?f.Integer:f.Number,unit:t[4].unit}])}return-1}function isCalculation(e){return!!e&&"object"==typeof e&&"inputs"in e&&Array.isArray(e.inputs)&&"operation"in e}function solve(e,n){if(-1===e)return-1;const r=[];for(let a=0;aconvertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;const o=u.map(e=>e[4].value);if(o.some(Number.isNaN))return resultToCalculation(e,a,Number.NaN);const i=Math.hypot(...o);return resultToCalculation(e,a,i)}function solveMax(e,n,r){if(!n.every(t))return-1;const a=n[0].value;if(!g(a))return-1;if(!r.rawPercentages&&d(a))return-1;const u=n.map(e=>convertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;const o=u.map(e=>e[4].value),i=Math.max(...o);return resultToCalculation(e,a,i)}function solveMin(e,n,r){if(!n.every(t))return-1;const a=n[0].value;if(!g(a))return-1;if(!r.rawPercentages&&d(a))return-1;const u=n.map(e=>convertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;const o=u.map(e=>e[4].value),i=Math.min(...o);return resultToCalculation(e,a,i)}function solveMod(e,n,t){const r=n.value;if(!g(r))return-1;const a=convertUnit(r,t.value);if(!twoOfSameNumeric(r,a))return-1;let u;return u=Number.isNaN(r[4].value)||Number.isNaN(a[4].value)||0===a[4].value?Number.NaN:Number.isFinite(r[4].value)&&(Number.isFinite(a[4].value)||(a[4].value!==Number.POSITIVE_INFINITY||r[4].value!==Number.NEGATIVE_INFINITY&&!Object.is(0*r[4].value,-0))&&(a[4].value!==Number.NEGATIVE_INFINITY||r[4].value!==Number.POSITIVE_INFINITY&&!Object.is(0*r[4].value,0)))?Number.isFinite(a[4].value)?(r[4].value%a[4].value+a[4].value)%a[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,u)}function solvePow(e,n,t){const r=n.value,a=t.value;if(!m(r))return-1;if(!twoOfSameNumeric(r,a))return-1;if(Number.isNaN(r[4].value)||Number.isNaN(a[4].value))return numberToCalculation(e,Number.NaN);return numberToCalculation(e,Math.pow(r[4].value,a[4].value))}function solveRem(e,n,t){const r=n.value;if(!g(r))return-1;const a=convertUnit(r,t.value);if(!twoOfSameNumeric(r,a))return-1;let u;return u=Number.isNaN(r[4].value)||Number.isNaN(a[4].value)||0===a[4].value?Number.NaN:Number.isFinite(r[4].value)?Number.isFinite(a[4].value)?r[4].value%a[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,u)}function snapAsBorderWidth(e,n,t){if(!s(n))return-1;const r=t.devicePixelLength??1,a=[v.Dimension,`${r}px`,n[2],n[3],{value:r,type:f.Integer,unit:"px"}],u=convertUnit(a,n);if(!twoOfSameNumeric(u,a))return-1;if(Number.isInteger(u[4].value/r))return resultToCalculation(e,n,n[4].value);if(u[4].value>0){if(u[4].value0?1/0:Object.is(0,0*u[4].value)?0:-0;break;default:i=Object.is(0,0*u[4].value)?0:-0}else switch(n){case"down":i=(o[4].value>0?Math.floor:Math.ceil)(u[4].value/o[4].value)*o[4].value;break;case"up":i=(o[4].value>0?Math.ceil:Math.floor)(u[4].value/o[4].value)*o[4].value;break;case"to-zero":i=Math.trunc(u[4].value/o[4].value)*o[4].value;break;default:{let t=Math.floor(u[4].value/o[4].value)*o[4].value,r=Math.ceil(u[4].value/o[4].value)*o[4].value;if(t>r){const e=t;t=r,r=e}const l=Math.abs(u[4].value-t),c=Math.abs(u[4].value-r);if(i="line-width"!==n||0!==r&&0!==t?l===c?r:l!r(e));return 1===n.length&&isNone(n[0])}if(!t(e))return!1;const n=e.value;return!!C(n)&&j.test(n[4].value)}const Y=String.fromCodePoint(0);function solveRandom(e,n,t,r,a,u){if(-1===n.fixed&&!u.randomCaching)return-1;const o=t.value;if(!g(o))return-1;const i=convertUnit(o,r.value);if(!twoOfSameNumeric(o,i))return-1;let l=null;if(a&&(l=convertUnit(o,a.value),!twoOfSameNumeric(o,l)))return-1;if(Number.isNaN(o[4].value)||Number.isNaN(i[4].value))return resultToCalculation(e,o,Number.NaN);if(!Number.isFinite(o[4].value))return resultToCalculation(e,o,o[4].value);if(!Number.isFinite(i[4].value)||!Number.isFinite(i[4].value-o[4].value))return resultToCalculation(e,o,Number.NaN);if(l){if(Number.isNaN(l[4].value))return resultToCalculation(e,o,Number.NaN);if(!Number.isFinite(l[4].value))return resultToCalculation(e,o,o[4].value)}const c=-1!==n.fixed?()=>n.fixed:(u.randomCaching.propertyN||(u.randomCaching.propertyN=0),sfc32(crc32([n.dashedIdent?n.dashedIdent:"",n.elementScoped?u.randomCaching.elementID:"",n.propertyScoped||n.propertyIndexScoped?u.randomCaching.propertyName:"",n.propertyIndexScoped?u.randomCaching.propertyN:"",u.randomCaching.documentID].join(Y)))),s=o[4].value;let v=i[4].value;if(v1e10)&&(l=null),l){const n=l[4].value,t=Math.max(n/1e3,1e-9);let r=Math.floor((v-s)/n);Math.abs(s+r*n-v)>=t&&Math.abs(s+(r+1)*n-v){const a=((e|=0)+(n|=0)|0)+(r|=0)|0;return r=r+1|0,e=n^n>>>9,n=(t|=0)+(t<<3)|0,t=(t=t<<21|t>>>11)+a|0,(a>>>0)/4294967296}}function crc32(e){let n,t,r=0;r^=-1;for(let a=0,u=e.length;a>>8^n;return(-1^r)>>>0}function solveCalcMix(e,n){const t=n[0].calcSum.value;if(!g(t))return-1;const r=n.map(e=>({calcSum:convertUnit(t,e.calcSum.value),percentage:e.percentage}));if(!arrayOfSameNumeric(r.map(e=>e.calcSum)))return-1;const{items:a}=normalizeMixPercentages(r,!1),u=a.map(e=>e.calcSum[4].value*e.percentage/100);let o=0;for(let e=0;e100||u>0&&n)for(const e of a)e.percentage=e.percentage*(100/u);let o=0;return u<100&&(o=100-u),{items:a,leftover:o}}const _=new Map([["abs",function abs(e,n,t){return singleNodeSolver(e,n,t,solveAbs)}],["acos",function acos(e,n,t){return singleNodeSolver(e,n,t,solveACos)}],["asin",function asin(e,n,t){return singleNodeSolver(e,n,t,solveASin)}],["atan",function atan(e,n,t){return singleNodeSolver(e,n,t,solveATan)}],["atan2",function atan2(e,n,t){return twoCommaSeparatedNodesSolver(e,n,t,solveATan2)}],["calc",calc$1],["calc-mix",function calcMix(e,n,t){const r=variadicArgumentsCalcMix(e,e.value,n,t);if(-1===r)return-1;return solveCalcMix(e,r)}],["clamp",function clamp(a,u,i){const l=resolveGlobalsAndConstants([...a.value.filter(e=>!r(e))],u),c=[],s=[],f=[];{let e=c;for(let n=0;n!r(e)),n,t);if(-1===a)return-1;const[u,o]=a,i=variadicArguments(e,o,n,t);if(-1===i)return-1;const[l,c,s]=i;if(!l||!c)return-1;return solveRandom(e,u,l,c,s,t)}],["rem",function rem(e,n,t){return twoCommaSeparatedNodesSolver(e,n,t,solveRem)}],["round",function round(e,a,u){const o=resolveGlobalsAndConstants([...e.value.filter(e=>!r(e))],a);let i="",l=!1;const c=[],p=[];{let e=c;for(let n=0;n!r(e))],n);if(1===i.length&&t(i[0]))return{inputs:[i[0]],operation:unary};let l=0;for(;l!r(e))],n),u=solve(calc$1(calcWrapper(e,a),n,t),t);return-1===u?-1:u}function twoCommaSeparatedNodesSolver(e,n,t,r){const a=twoCommaSeparatedArguments(e,n,t);if(-1===a)return-1;const[u,o]=a;return r(e,u,o,t)}function twoCommaSeparatedArguments(e,n,a){const u=resolveGlobalsAndConstants([...e.value.filter(e=>!r(e))],n),o=[],i=[];{let e=o;for(let n=0;n!r(e))],a),i=[];{const n=[];let r=[];for(let e=0;e!r(e))],a),i=[];{const n=[];let l=[];for(let e=0;e=0;i--){if(r(n[o][i]))continue;if(l=solve(calc$1(calcWrapper(e,n[o].slice(0,i+1)),a,u),u),-1===l)continue;const s=n[o].slice(i+1).filter(e=>!r(e));if(s.length)if(1===s.length&&t(s[0])&&d(s[0].value)){if(c=s[0].value[4].value,c<0||c>100)return-1}else{const n=solve(calc$1(calcWrapper(e,s),a,u),u);if(-1===n)return-1;if(!d(n.value))return-1;c=n.value[4].value,c=Math.min(100,Math.max(0,c))}break}if(-1===l)return-1;i.push({calcSum:l,percentage:c})}}return i}const H=new Set(["nearest","line-width","up","down","to-zero"]);function parseRandomValueSharing(e,n,r,a){const u={dashedIdent:"",fixed:-1,elementScoped:!1,propertyScoped:!1,propertyIndexScoped:!1};let o=!1;const i=n[0];if(!t(i)||!C(i.value))return[u,n];for(let i=0;i1)return-1;u.fixed=Math.max(0,Math.min(l.value[4].value,1-Number.EPSILON/2));continue}if("auto"!==c){if(!c.startsWith("--"))return-1;if(-1!==u.fixed||o||u.dashedIdent)return-1;u.dashedIdent=c}else{if(-1!==u.fixed||o||u.dashedIdent||u.elementScoped||u.propertyScoped||u.propertyIndexScoped)return-1;u.elementScoped=!0,u.propertyIndexScoped=!0,o=!0}}else{if(-1!==u.fixed||o||u.propertyScoped||u.propertyIndexScoped)return-1;u.propertyIndexScoped=!0}else{if(-1!==u.fixed||o||u.propertyScoped||u.propertyIndexScoped)return-1;u.propertyScoped=!0}else{if(-1!==u.fixed||o||u.elementScoped)return-1;u.elementScoped=!0}}return-1}function calcWrapper(e,n){return new o([v.Function,"calc(",e.name[2],e.name[3],{value:"calc"}],[v.CloseParen,")",e.endToken[2],e.endToken[3],void 0],n)}function maxWrapper(t,r,a){return new o([v.Function,"max(",t.name[2],t.name[3],{value:"max"}],[v.CloseParen,")",t.endToken[2],t.endToken[3],void 0],[r,new n([v.Comma,",",...e(r),void 0]),a])}function patchNaN(e){if(-1===e)return-1;if(u(e))return e;const t=e.value;return g(t)&&Number.isNaN(t[4].value)?m(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}])]):s(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:f.Integer,unit:t[4].unit}])]):d(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Percentage,"1%",t[2],t[3],{value:1}])]):-1:e}function patchInfinity(e){if(-1===e)return-1;if(u(e))return e;const t=e.value;if(!g(t))return e;if(Number.isFinite(t[4].value)||Number.isNaN(t[4].value))return e;let r="";return Number.NEGATIVE_INFINITY===t[4].value&&(r="-"),m(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}])]):s(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:f.Integer,unit:t[4].unit}])]):new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Percentage,"1%",t[2],t[3],{value:1}])])}function patchMinusZero(e){if(-1===e)return-1;if(u(e))return e;const n=e.value;return g(n)&&Object.is(-0,n[4].value)?("-0"===n[1]||(d(n)?n[1]="-0%":s(n)?n[1]="-0"+n[4].unit:n[1]="-0"),e):e}function patchPrecision(e,n=13){if(-1===e)return-1;if(n<=0)return e;if(u(e))return e;const t=e.value;if(!g(t))return e;if(Number.isInteger(t[4].value))return e;const r=Number(t[4].value.toFixed(n)).toString();return m(t)?t[1]=r:d(t)?t[1]=r+"%":s(t)&&(t[1]=r+t[4].unit),e}function patchCanonicalUnit(e){return-1===e?-1:u(e)?e:s(e.value)?(e.value=toCanonicalUnit(e.value),e):e}function patchCalcResult(e,n){let t=e;return n?.toCanonicalUnits&&(t=patchCanonicalUnit(t)),t=patchPrecision(t,n?.precision),t=patchMinusZero(t),n?.censorIntoStandardRepresentableValues||(t=patchNaN(t),t=patchInfinity(t)),t}function tokenizeGlobals(e){const n=new Map;if(!e)return n;for(const[t,r]of e)if(B(r))n.set(t,r);else if("string"==typeof r){const e=b({css:r}),a=e.nextToken();if(e.nextToken(),!e.endOfFile())continue;if(!g(a))continue;n.set(t,a);continue}return n}function calc(e,n){return calcFromComponentValues(l(A({css:e}),{}),n).map(e=>e.map(e=>F(...e.tokens())).join("")).join(",")}function calcFromComponentValues(e,n){const t=tokenizeGlobals(n?.globals);return replaceComponentValues(e,e=>{if(!u(e))return;const r=_.get(e.getName().toLowerCase());if(!r)return;const a=patchCalcResult(solve(r(e,t,n??{}),n??{}),n);return-1!==a?n?.calcWrapper?calcWrapper(e,[a]):a:void 0})}function replaceComponentValues(n,a){for(let u=0;u{if("number"!=typeof u)return;const o=a(n.node);if(!o)return;const l=[o],c=n.parent.value[u-1];t(c)&&h(c.value)&&("-"===c.value[4].value||"+"===c.value[4].value)&&l.splice(0,0,new i([[v.Whitespace," ",...e(n.node),void 0]]));const s=n.parent.value[u+1];!s||r(s)||t(s)&&(D(s.value)||w(s.value)||E(s.value)||h(s.value)&&"-"!==s.value[4].value&&"+"!==s.value[4].value)||l.push(new i([[v.Whitespace," ",...e(n.node),void 0]])),n.parent.value.splice(u,1,...l)})}return n}const J=new Set(_.keys());export{ParseError,S as ParseErrorMessage,ParseErrorWithComponentValues,calc,calcFromComponentValues,J as mathFunctionNames}; diff --git a/packages/css-calc/src/functions/calc.ts b/packages/css-calc/src/functions/calc.ts index 709be65d35..af55718f14 100644 --- a/packages/css-calc/src/functions/calc.ts +++ b/packages/css-calc/src/functions/calc.ts @@ -808,7 +808,10 @@ function parseRandomValueSharing(fnNode: FunctionNode, nodes: Array x[4].value); + + // https://drafts.csswg.org/css-values-4/#exponent-ranges + // NaN is infectious, forcing the function to return NaN if any argument calculation is NaN. + if (values.some(Number.isNaN)) { + return resultToCalculation(hypotNode, firstSolvedToken, Number.NaN); + } + const result = Math.hypot(...values); return resultToCalculation(hypotNode, firstSolvedToken, result); diff --git a/packages/css-calc/src/functions/log.ts b/packages/css-calc/src/functions/log.ts index 28070f3f66..03fd4f53a3 100644 --- a/packages/css-calc/src/functions/log.ts +++ b/packages/css-calc/src/functions/log.ts @@ -42,6 +42,18 @@ export function solveLog(logNode: FunctionNode, solvedNodes: Array { return randomValueSharing.fixed; }; - - let min = aToken[4].value; + const rnd = ((): () => number => { + if (randomValueSharing.fixed !== -1) { + return (): number => { + return randomValueSharing.fixed; + }; + } + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + if (!options.randomCaching!.propertyN) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + options.randomCaching!.propertyN = 0; + } + + return sfc32( + crc32( + [ + randomValueSharing.dashedIdent ? randomValueSharing.dashedIdent : "", + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + randomValueSharing.elementScoped ? options.randomCaching!.elementID : "", + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + (randomValueSharing.propertyScoped || randomValueSharing.propertyIndexScoped) ? options.randomCaching!.propertyName : "", + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + randomValueSharing.propertyIndexScoped ? options.randomCaching!.propertyN : "", + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + options.randomCaching!.documentID, + ].join(NULL_CHAR), + ), + ); + })(); + + // If the maximum value is less than the minimum value, it behaves as if it's equal to the minimum value. + const min = aToken[4].value; let max = bToken[4].value; - if (min > max) { - [min, max] = [max, min]; + if (max < min) { + max = min; } + // If C is negative, zero, or positive but close enough to zero that the range for the step multiplier would be infinite, the step must be ignored. if ( stepValueToken && ( (stepValueToken[4].value <= 0) || @@ -97,31 +116,37 @@ export function solveRandom(randomNode: FunctionNode, randomValueSharing: Random } if (stepValueToken) { - const err = Math.max(stepValueToken[4].value / 1000, 0.000_000_001); - - const steps = [min]; - let lastStep = 0; - while (true) { - lastStep += stepValueToken[4].value; - - const stepResult = min + lastStep; - if ((stepResult + err) < max) { - steps.push(stepResult); - } else { - steps.push(max); - break; - } - - if ((stepResult + stepValueToken[4].value - err) > max) { - break; - } + const step = stepValueToken[4].value; + + // Let epsilon be step / 1000, or the smallest representable value greater than zero if epsilon would round to zero. + const epsilon = Math.max(step / 1000, 0.000_000_001); + + // Let N be the largest integer such that min + N * step is less than or equal to max. + let N = Math.floor((max - min) / step); + + // If N produces a value that is not within epsilon of max, but N+1 would produce a value within epsilon of max, set N to N+1. + if ( + Math.abs(min + N * step - max) >= epsilon && + Math.abs(min + (N + 1) * step - max) < epsilon + ) { + N = N + 1; + } + + // Let step index be a random integer less than N+1, given R. + const stepIndex = Math.floor(rnd() * (N + 1)); + + // Let value be min + step index * step. + let value = min + stepIndex * step; + + // If step index is N and value is within epsilon of max, return max. + if (stepIndex === N && Math.abs(value - max) < epsilon) { + value = max; } - const randomValue = rnd(); return resultToCalculation( randomNode, aToken, - Number(steps[Math.floor(steps.length * randomValue)].toFixed(5)) + Number(value.toFixed(5)) ); } diff --git a/packages/css-calc/src/functions/rem.ts b/packages/css-calc/src/functions/rem.ts index 4467beb4e2..8228837ab5 100644 --- a/packages/css-calc/src/functions/rem.ts +++ b/packages/css-calc/src/functions/rem.ts @@ -17,7 +17,10 @@ export function solveRem(remNode: FunctionNode, a: TokenNode, b: TokenNode): Cal } let result; - if (bToken[4].value === 0) { + if (Number.isNaN(aToken[4].value) || Number.isNaN(bToken[4].value)) { + // NaN is infectious, forcing the function to return NaN if any argument calculation is NaN. + result = Number.NaN; + } else if (bToken[4].value === 0) { result = Number.NaN; } else if (!Number.isFinite(aToken[4].value)) { result = Number.NaN; diff --git a/packages/css-calc/src/functions/round.ts b/packages/css-calc/src/functions/round.ts index 4c6eda48e3..fcafc3cc25 100644 --- a/packages/css-calc/src/functions/round.ts +++ b/packages/css-calc/src/functions/round.ts @@ -28,7 +28,10 @@ export function solveRound(roundNode: FunctionNode, roundingStrategy: string, a: let result; // https://drafts.csswg.org/css-values-4/#round-infinities - if (bToken[4].value === 0) { + // NaN is infectious, forcing the function to return NaN if any argument calculation is NaN. + if (Number.isNaN(aToken[4].value) || Number.isNaN(bToken[4].value)) { + result = Number.NaN; + } else if (bToken[4].value === 0) { // In round(A, B), if B is 0, the result is NaN. result = Number.NaN; } else if (!Number.isFinite(aToken[4].value) && !Number.isFinite(bToken[4].value)) { @@ -75,10 +78,14 @@ export function solveRound(roundNode: FunctionNode, roundingStrategy: string, a: } else { switch (roundingStrategy) { case 'down': - result = Math.floor(aToken[4].value / bToken[4].value) * bToken[4].value; + // "Choose lower B" (the integer multiple of B closer to −∞). + // For a negative step, `Math.floor`/`Math.ceil` must be swapped. + result = (bToken[4].value > 0 ? Math.floor : Math.ceil)(aToken[4].value / bToken[4].value) * bToken[4].value; break; case 'up': - result = Math.ceil(aToken[4].value / bToken[4].value) * bToken[4].value; + // "Choose upper B" (the integer multiple of B closer to +∞). + // For a negative step, `Math.floor`/`Math.ceil` must be swapped. + result = (bToken[4].value > 0 ? Math.ceil : Math.floor)(aToken[4].value / bToken[4].value) * bToken[4].value; break; case 'to-zero': result = Math.trunc(aToken[4].value / bToken[4].value) * bToken[4].value; @@ -97,7 +104,7 @@ export function solveRound(roundNode: FunctionNode, roundingStrategy: string, a: const downDiff = Math.abs(aToken[4].value - down); const upDiff = Math.abs(aToken[4].value - up); - if (roundingStrategy === 'line-width' && aToken[4].value >= 0 && (up === 0 || down === 0)) { + if (roundingStrategy === 'line-width' && (up === 0 || down === 0)) { result = up !== 0 ? up : down; } else if (downDiff === upDiff) { result = up; diff --git a/packages/css-calc/src/functions/tan.ts b/packages/css-calc/src/functions/tan.ts index 8fa31b18b6..b208f81c74 100644 --- a/packages/css-calc/src/functions/tan.ts +++ b/packages/css-calc/src/functions/tan.ts @@ -52,7 +52,9 @@ export function solveTan(tanNode: FunctionNode, a: TokenNode): Calculation | -1 const isAsymptote = isNinetyMultiple && timesNinety % 2 !== 0; if (isAsymptote) { - result = timesNinety > 0 ? Infinity : -Infinity; + // https://drafts.csswg.org/css-values-4/#trig-infinities + // +∞ for the asymptotes at 90deg + N*360deg, and −∞ for the asymptotes at -90deg + N*360deg. + result = ((timesNinety % 4) + 4) % 4 === 1 ? Infinity : -Infinity; } else { result = Math.tan(result); } diff --git a/packages/css-calc/src/util/is-none.ts b/packages/css-calc/src/util/is-none.ts index ac74f5401c..2ce00fd4fd 100644 --- a/packages/css-calc/src/util/is-none.ts +++ b/packages/css-calc/src/util/is-none.ts @@ -1,12 +1,12 @@ import type { ComponentValue } from '@csstools/css-parser-algorithms'; -import { isCommentNode, isTokenNode, isWhitespaceNode } from '@csstools/css-parser-algorithms'; +import { isTokenNode, isWhiteSpaceOrCommentNode } from '@csstools/css-parser-algorithms'; import { isTokenIdent } from '@csstools/css-tokenizer'; const IS_NONE_REGEX = /^none$/i; export function isNone(v: Array | ComponentValue): boolean { if (Array.isArray(v)) { - const relevantNodes = v.filter((x) => !(isWhitespaceNode(x) && isCommentNode(x))); + const relevantNodes = v.filter((x) => !isWhiteSpaceOrCommentNode(x)); if (relevantNodes.length === 1) { return isNone(relevantNodes[0]); } diff --git a/packages/css-calc/test/additional/random.mjs b/packages/css-calc/test/additional/random.mjs index b7f89f2a6b..67054f8115 100644 --- a/packages/css-calc/test/additional/random.mjs +++ b/packages/css-calc/test/additional/random.mjs @@ -296,12 +296,12 @@ assert.strictEqual( assert.strictEqual( calc('random(-10px, 20px, 50px)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), - '20px', + '-10px', ); assert.strictEqual( calc('random(--foo, -10px, 20px, 50px)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), - '20px', + '-10px', ); assert.strictEqual( @@ -346,7 +346,7 @@ assert.strictEqual( assert.strictEqual( calc('random(calc(10px * infinity), 20px)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), - 'calc(NaN * 1px)', + 'calc(infinity * 1px)', ); assert.strictEqual( @@ -440,3 +440,81 @@ for (let i = 0; i < 100; i++) { assert.ok(result <= 190); assert.ok(result === 100 || result === 130 || result === 160 || result === 190); } + +// If the maximum value is less than the minimum value, it behaves as if it's equal to the minimum value. +{ + for (let i = 0; i < 100; i++) { + const result = calc(`random(--minmax${i}, 500px, 100px)`, { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }); + + assert.strictEqual(result, '500px'); + } + + assert.strictEqual( + calc('random(500px, 100px)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + '500px', + ); + + assert.strictEqual( + calc('random(500, 100)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + '500', + ); +} + +// The max value might not actually show up as a possible value. +// https://drafts.csswg.org/css-values-5/#randomness +{ + for (let i = 0; i < 100; i++) { + const result = Number(calc(`random(--unreachable${i}, 100, 200, 30)`, { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } })); + + assert.ok(result === 100 || result === 130 || result === 160 || result === 190, result.toString()); + } + + for (let i = 0; i < 100; i++) { + const result = Number(calc(`random(--unreachable2${i}, 100, 120, 30)`, { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } })); + + assert.ok(result === 100, result.toString()); + } +} + +// When the max is an integer multiple of the step, it is reachable. +{ + for (let i = 0; i < 100; i++) { + const result = Number(calc(`random(--reachable${i}, 100, 500, 10)`, { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } })); + + assert.ok(result >= 100, result.toString()); + assert.ok(result <= 500, result.toString()); + assert.ok((result % 10) === 0, result.toString()); + } +} + +// Reusing an options object must not leak `randomCaching` between calls. +{ + const options = {}; + + // Without `randomCaching`, a non-fixed random() can not be computed. + assert.strictEqual( + calc('random(100px, 500px)', options), + 'random(100px, 500px)', + ); + + assert.strictEqual( + calc('random(fixed 0.5, 100px, 500px)', options), + '300px', + ); +} + +// Reusing an options object must not leak `randomCaching` between calls. +{ + const options = {}; + + assert.strictEqual( + calc('random(fixed 0.5, 100px, 500px)', options), + '300px', + ); + + // Without `randomCaching`, a non-fixed random() can not be computed. + assert.strictEqual( + calc('random(100px, 500px)', options), + 'random(100px, 500px)', + ); +} diff --git a/packages/css-calc/test/additional/round-line-width.mjs b/packages/css-calc/test/additional/round-line-width.mjs index b5049f5fc0..f536cbed77 100644 --- a/packages/css-calc/test/additional/round-line-width.mjs +++ b/packages/css-calc/test/additional/round-line-width.mjs @@ -235,3 +235,52 @@ assert.strictEqual( calc('round(line-width, 11px, 1)', { devicePixelLength: 0.5 }), 'round(line-width, 11px, 1)', ); + +// When one of the candidate multiples is zero, the non-zero one is chosen, regardless of the sign of A. +// https://github.com/w3c/csswg-drafts/issues/13794 +// https://drafts.csswg.org/css-values-4/#round-func +assert.strictEqual( + calc('round(line-width, 0.4px, 1px)'), + '1px', +); + +assert.strictEqual( + calc('round(line-width, -0.1px, 1px)'), + '-1px', +); + +assert.strictEqual( + calc('round(line-width, -0.3px, 1px)'), + '-1px', +); + +assert.strictEqual( + calc('round(line-width, -0.4px, 1px)'), + '-1px', +); + +assert.strictEqual( + calc('round(line-width, -0.4px, 2px)'), + '-2px', +); + +assert.strictEqual( + calc('round(line-width, -0.4px, 1px)', { devicePixelLength: 0.5 }), + '-1px', +); + +// No zero candidate, so these round as for nearest. +assert.strictEqual( + calc('round(line-width, -0.6px, 1px)'), + '-1px', +); + +assert.strictEqual( + calc('round(line-width, -1.4px, 1px)'), + '-1px', +); + +assert.strictEqual( + calc('round(line-width, -1.6px, 1px)'), + '-2px', +); diff --git a/packages/css-calc/test/additional/tan-asymptotes.mjs b/packages/css-calc/test/additional/tan-asymptotes.mjs index 4f53924aec..2b298a933c 100644 --- a/packages/css-calc/test/additional/tan-asymptotes.mjs +++ b/packages/css-calc/test/additional/tan-asymptotes.mjs @@ -12,17 +12,42 @@ assert.strictEqual( ); assert.strictEqual( - calc('tan(270deg)'), + calc('tan(450deg)'), 'calc(infinity)', ); assert.strictEqual( - calc('tan(300grad)'), + calc('tan(500grad)'), 'calc(infinity)', ); assert.strictEqual( - calc('tan(-270deg)'), + calc('tan(0.25turn)'), + 'calc(infinity)', +); + +assert.strictEqual( + calc('tan(270deg)'), + 'calc(-infinity)', +); + +assert.strictEqual( + calc('tan(300grad)'), + 'calc(-infinity)', +); + +assert.strictEqual( + calc('tan(630deg)'), + 'calc(-infinity)', +); + +assert.strictEqual( + calc('tan(700grad)'), + 'calc(-infinity)', +); + +assert.strictEqual( + calc('tan(-90deg)'), 'calc(-infinity)', ); @@ -36,7 +61,22 @@ assert.strictEqual( 'calc(-infinity)', ); +assert.strictEqual( + calc('tan(-270deg)'), + 'calc(infinity)', +); + assert.strictEqual( calc('tan(-300grad)'), - 'calc(-infinity)', + 'calc(infinity)', +); + +assert.strictEqual( + calc('tan(-630deg)'), + 'calc(infinity)', +); + +assert.strictEqual( + calc('tan(-0.75turn)'), + 'calc(infinity)', ); diff --git a/packages/css-calc/test/wpt/exp-log-compute.mjs b/packages/css-calc/test/wpt/exp-log-compute.mjs index 502e719239..e9af4770bc 100644 --- a/packages/css-calc/test/wpt/exp-log-compute.mjs +++ b/packages/css-calc/test/wpt/exp-log-compute.mjs @@ -85,3 +85,60 @@ assert.strictEqual( calc('calc(log(exp(log(e))))'), '1', ); + +assert.strictEqual( + calc('log(2, 1)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('log(5, 1)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('log(0, 1)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('log(1, 1)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('log(2, -1)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('log(2, 2)'), + '1', +); + +// B values between 0 and 1 (exclusive), or greater than 1, are valid. +// B is 0, 1, or negative => NaN. +assert.strictEqual( + calc('log(10, 0)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('log(2, 0)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('log(2, 0.5)'), + '-1', +); + +assert.strictEqual( + calc('log(1, 2)'), + '0', +); + +assert.strictEqual( + calc('log(1, 0.5)'), + '0', +); diff --git a/packages/css-calc/test/wpt/hypot-pow-sqrt-computed.mjs b/packages/css-calc/test/wpt/hypot-pow-sqrt-computed.mjs index d251c4426b..9e305bd169 100644 --- a/packages/css-calc/test/wpt/hypot-pow-sqrt-computed.mjs +++ b/packages/css-calc/test/wpt/hypot-pow-sqrt-computed.mjs @@ -190,3 +190,49 @@ assert.strictEqual( calc('hypot(1turn)'), '1turn', ); + +assert.strictEqual( + calc('hypot(NaN, 5)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('hypot(infinity, NaN)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('hypot(NaN, infinity)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('hypot(1, NaN)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('hypot(infinity, 5)'), + 'calc(infinity)', +); + +assert.strictEqual( + calc('hypot(5, infinity)'), + 'calc(infinity)', +); + +// NaN is infectious, even for `pow(NaN, 0)` where `Math.pow` would return 1. +assert.strictEqual( + calc('pow(NaN, 0)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('pow(1, NaN)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('pow(NaN, NaN)'), + 'calc(NaN)', +); diff --git a/packages/css-calc/test/wpt/round-function.mjs b/packages/css-calc/test/wpt/round-function.mjs index 417125e402..014c87904f 100644 --- a/packages/css-calc/test/wpt/round-function.mjs +++ b/packages/css-calc/test/wpt/round-function.mjs @@ -371,3 +371,65 @@ assert.strictEqual( calc('round(3.7px)'), 'round(3.7px)', ); + +// 'down' and 'up' with a negative step choose lower/upper B (closer to −∞/+∞). +assert.strictEqual( + calc('round(down, 23, -10)'), + '20', +); + +assert.strictEqual( + calc('round(up, 23, -10)'), + '30', +); + +assert.strictEqual( + calc('round(down, -23, -10)'), + '-30', +); + +assert.strictEqual( + calc('round(up, -23, -10)'), + '-20', +); + +assert.strictEqual( + calc('round(down, 23px, -10px)'), + '20px', +); + +assert.strictEqual( + calc('round(up, 23px, -10px)'), + '30px', +); + +assert.strictEqual( + calc('round(down, 5, -2)'), + '4', +); + +assert.strictEqual( + calc('round(up, 5, -2)'), + '6', +); + +// NaN is infectious. +assert.strictEqual( + calc('round(5, NaN)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('round(NaN, 5)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('round(up, 5, NaN)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('round(down, NaN, 5)'), + 'calc(NaN)', +); diff --git a/packages/css-calc/test/wpt/round-mod-rem-computed.mjs b/packages/css-calc/test/wpt/round-mod-rem-computed.mjs index 7420acb0b9..cf4a63f2c1 100644 --- a/packages/css-calc/test/wpt/round-mod-rem-computed.mjs +++ b/packages/css-calc/test/wpt/round-mod-rem-computed.mjs @@ -570,3 +570,24 @@ assert.strictEqual( calc('rem(-18vw,5vw)'), '-3vw', ); + +// NaN is infectious. +assert.strictEqual( + calc('mod(5, NaN)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('mod(NaN, 5)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('rem(5, NaN)'), + 'calc(NaN)', +); + +assert.strictEqual( + calc('rem(NaN, 5)'), + 'calc(NaN)', +); From 50e8ac7b3027ea336a8713dcceb3e91b13fabd63 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sun, 20 Sep 2026 16:45:45 +0200 Subject: [PATCH 2/5] more fixes --- packages/css-calc/dist/index.mjs | 2 +- packages/css-calc/src/functions/calc.ts | 8 +++ packages/css-calc/test/additional/random.mjs | 69 ++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/packages/css-calc/dist/index.mjs b/packages/css-calc/dist/index.mjs index d3952dc7b8..c417ae06ba 100644 --- a/packages/css-calc/dist/index.mjs +++ b/packages/css-calc/dist/index.mjs @@ -1 +1 @@ -import{sourceIndices as e,TokenNode as n,isTokenNode as t,isWhiteSpaceOrCommentNode as r,isSimpleBlockNode as a,isFunctionNode as u,FunctionNode as o,WhitespaceNode as i,parseCommaSeparatedListOfComponentValues as l,walk as c}from"@csstools/css-parser-algorithms";import{isTokenDimension as s,TokenType as v,NumberType as f,mutateUnit as p,isTokenNumber as m,isTokenPercentage as d,isTokenIdent as N,isTokenNumeric as C,isTokenOpenParen as g,isTokenDelim as h,isTokenComma as D,isToken as b,tokenizer as B,tokenize as A,stringify as F,isTokenColon as w,isTokenSemicolon as E}from"@csstools/css-tokenizer";class ParseError extends Error{sourceStart;sourceEnd;constructor(e,n,t){super(e),this.name="ParseError",this.sourceStart=n,this.sourceEnd=t}}class ParseErrorWithComponentValues extends ParseError{componentValues;constructor(n,t){super(n,...e(t)),this.componentValues=t}}const S={UnexpectedAdditionOfDimensionOrPercentageWithNumber:"Unexpected addition of a dimension or percentage with a number.",UnexpectedSubtractionOfDimensionOrPercentageWithNumber:"Unexpected subtraction of a dimension or percentage with a number."},I=/[A-Z]/g;function toLowerCaseAZ(e){return e.replace(I,e=>String.fromCharCode(e.charCodeAt(0)+32))}const y={cm:"px",in:"px",mm:"px",pc:"px",pt:"px",px:"px",q:"px",deg:"deg",grad:"deg",rad:"deg",turn:"deg",ms:"s",s:"s",hz:"hz",khz:"hz"},M=new Map([["cm",e=>e],["mm",e=>10*e],["q",e=>40*e],["in",e=>e/2.54],["pc",e=>e/2.54*6],["pt",e=>e/2.54*72],["px",e=>e/2.54*96]]),x=new Map([["deg",e=>e],["grad",e=>e/.9],["rad",e=>e/180*Math.PI],["turn",e=>e/360]]),T=new Map([["deg",e=>.9*e],["grad",e=>e],["rad",e=>.9*e/180*Math.PI],["turn",e=>.9*e/360]]),P=new Map([["hz",e=>e],["khz",e=>e/1e3]]),k=new Map([["cm",e=>2.54*e],["mm",e=>25.4*e],["q",e=>25.4*e*4],["in",e=>e],["pc",e=>6*e],["pt",e=>72*e],["px",e=>96*e]]),W=new Map([["hz",e=>1e3*e],["khz",e=>e]]),O=new Map([["cm",e=>e/10],["mm",e=>e],["q",e=>4*e],["in",e=>e/25.4],["pc",e=>e/25.4*6],["pt",e=>e/25.4*72],["px",e=>e/25.4*96]]),U=new Map([["ms",e=>e],["s",e=>e/1e3]]),L=new Map([["cm",e=>e/6*2.54],["mm",e=>e/6*25.4],["q",e=>e/6*25.4*4],["in",e=>e/6],["pc",e=>e],["pt",e=>e/6*72],["px",e=>e/6*96]]),$=new Map([["cm",e=>e/72*2.54],["mm",e=>e/72*25.4],["q",e=>e/72*25.4*4],["in",e=>e/72],["pc",e=>e/72*6],["pt",e=>e],["px",e=>e/72*96]]),V=new Map([["cm",e=>e/96*2.54],["mm",e=>e/96*25.4],["q",e=>e/96*25.4*4],["in",e=>e/96],["pc",e=>e/96*6],["pt",e=>e/96*72],["px",e=>e]]),Z=new Map([["cm",e=>e/4/10],["mm",e=>e/4],["q",e=>e],["in",e=>e/4/25.4],["pc",e=>e/4/25.4*6],["pt",e=>e/4/25.4*72],["px",e=>e/4/25.4*96]]),z=new Map([["deg",e=>180*e/Math.PI],["grad",e=>180*e/Math.PI/.9],["rad",e=>e],["turn",e=>180*e/Math.PI/360]]),q=new Map([["ms",e=>1e3*e],["s",e=>e]]),G=new Map([["deg",e=>360*e],["grad",e=>360*e/.9],["rad",e=>360*e/180*Math.PI],["turn",e=>e]]),R=new Map([["cm",M],["mm",O],["q",Z],["in",k],["pc",L],["pt",$],["px",V],["ms",U],["s",q],["deg",x],["grad",T],["rad",z],["turn",G],["hz",P],["khz",W]]);function convertUnit(e,n){if(!s(e))return n;if(!s(n))return n;const t=toLowerCaseAZ(e[4].unit),r=toLowerCaseAZ(n[4].unit);if(t===r)return n;const a=R.get(r);if(!a)return n;const u=a.get(t);if(!u)return n;const o=u(n[4].value),i=[v.Dimension,"",n[2],n[3],{...n[4],signCharacter:o<0?"-":void 0,type:Number.isInteger(o)?f.Integer:f.Number,value:o}];return p(i,e[4].unit),i}function toCanonicalUnit(e){if(!s(e))return e;const n=toLowerCaseAZ(e[4].unit),t=y[n];if(n===t)return e;const r=R.get(n);if(!r)return e;const a=r.get(t);if(!a)return e;const u=a(e[4].value),o=[v.Dimension,"",e[2],e[3],{...e[4],signCharacter:u<0?"-":void 0,type:Number.isInteger(u)?f.Integer:f.Number,value:u}];return p(o,t),o}function addition(e,t){if(2!==e.length)return-1;const r=e[0].value;let a=e[1].value;if(m(r)&&m(a)){const e=r[4].value+a[4].value;return new n([v.Number,e.toString(),r[2],a[3],{value:e,type:r[4].type===f.Integer&&a[4].type===f.Integer?f.Integer:f.Number}])}if(d(r)&&d(a)){const e=r[4].value+a[4].value;return new n([v.Percentage,e.toString()+"%",r[2],a[3],{value:e}])}if(s(r)&&s(a)&&(a=convertUnit(r,a),toLowerCaseAZ(r[4].unit)===toLowerCaseAZ(a[4].unit))){const e=r[4].value+a[4].value;return new n([v.Dimension,e.toString()+r[4].unit,r[2],a[3],{value:e,type:r[4].type===f.Integer&&a[4].type===f.Integer?f.Integer:f.Number,unit:r[4].unit}])}return(m(r)&&(s(a)||d(a))||m(a)&&(s(r)||d(r)))&&t.onParseError?.(new ParseErrorWithComponentValues(S.UnexpectedAdditionOfDimensionOrPercentageWithNumber,e)),-1}function division(e){if(2!==e.length)return-1;const t=e[0].value,r=e[1].value;if(m(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Number,e.toString(),t[2],r[3],{value:e,type:Number.isInteger(e)?f.Integer:f.Number}])}if(d(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Percentage,e.toString()+"%",t[2],r[3],{value:e}])}if(s(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Dimension,e.toString()+t[4].unit,t[2],r[3],{value:e,type:Number.isInteger(e)?f.Integer:f.Number,unit:t[4].unit}])}return-1}function isCalculation(e){return!!e&&"object"==typeof e&&"inputs"in e&&Array.isArray(e.inputs)&&"operation"in e}function solve(e,n){if(-1===e)return-1;const r=[];for(let a=0;aconvertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;if(u.some(e=>Number.isNaN(e[4].value)))return resultToCalculation(e,a,Number.NaN);if(!r.rawPercentages&&d(a))return-1;const o=u.map(e=>e[4].value);if(o.some(Number.isNaN))return resultToCalculation(e,a,Number.NaN);const i=Math.hypot(...o);return resultToCalculation(e,a,i)}function solveMax(e,n,r){if(!n.every(t))return-1;const a=n[0].value;if(!C(a))return-1;const u=n.map(e=>convertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;if(u.some(e=>Number.isNaN(e[4].value)))return resultToCalculation(e,a,Number.NaN);if(!r.rawPercentages&&d(a))return-1;const o=u.map(e=>e[4].value),i=Math.max(...o);return resultToCalculation(e,a,i)}function solveMin(e,n,r){if(!n.every(t))return-1;const a=n[0].value;if(!C(a))return-1;const u=n.map(e=>convertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;if(u.some(e=>Number.isNaN(e[4].value)))return resultToCalculation(e,a,Number.NaN);if(!r.rawPercentages&&d(a))return-1;const o=u.map(e=>e[4].value),i=Math.min(...o);return resultToCalculation(e,a,i)}function solveMod(e,n,t){const r=n.value;if(!C(r))return-1;const a=convertUnit(r,t.value);if(!twoOfSameNumeric(r,a))return-1;let u;return u=Number.isNaN(r[4].value)||Number.isNaN(a[4].value)||0===a[4].value?Number.NaN:Number.isFinite(r[4].value)&&(Number.isFinite(a[4].value)||(a[4].value!==Number.POSITIVE_INFINITY||r[4].value!==Number.NEGATIVE_INFINITY&&!Object.is(0*r[4].value,-0))&&(a[4].value!==Number.NEGATIVE_INFINITY||r[4].value!==Number.POSITIVE_INFINITY&&!Object.is(0*r[4].value,0)))?Number.isFinite(a[4].value)?(r[4].value%a[4].value+a[4].value)%a[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,u)}function solvePow(e,n,t){const r=n.value,a=t.value;if(!m(r))return-1;if(!twoOfSameNumeric(r,a))return-1;if(Number.isNaN(r[4].value)||Number.isNaN(a[4].value))return numberToCalculation(e,Number.NaN);return numberToCalculation(e,Math.pow(r[4].value,a[4].value))}function solveRem(e,n,t){const r=n.value;if(!C(r))return-1;const a=convertUnit(r,t.value);if(!twoOfSameNumeric(r,a))return-1;let u;return u=Number.isNaN(r[4].value)||Number.isNaN(a[4].value)||0===a[4].value?Number.NaN:Number.isFinite(r[4].value)?Number.isFinite(a[4].value)?r[4].value%a[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,u)}function snapAsBorderWidth(e,n,t){if(!s(n))return-1;const r=t.devicePixelLength??1,a=[v.Dimension,`${r}px`,n[2],n[3],{value:r,type:f.Integer,unit:"px"}],u=convertUnit(a,n);if(!twoOfSameNumeric(u,a))return-1;if(Number.isInteger(u[4].value/r))return resultToCalculation(e,n,n[4].value);if(u[4].value>0){if(u[4].value0?1/0:Object.is(0,0*u[4].value)?0:-0;break;default:i=Object.is(0,0*u[4].value)?0:-0}else switch(n){case"down":i=(o[4].value>0?Math.floor:Math.ceil)(u[4].value/o[4].value)*o[4].value;break;case"up":i=(o[4].value>0?Math.ceil:Math.floor)(u[4].value/o[4].value)*o[4].value;break;case"to-zero":i=Math.trunc(u[4].value/o[4].value)*o[4].value;break;default:{let t=Math.floor(u[4].value/o[4].value)*o[4].value,r=Math.ceil(u[4].value/o[4].value)*o[4].value;if(t>r){const e=t;t=r,r=e}const l=Math.abs(u[4].value-t),c=Math.abs(u[4].value-r);if(i="line-width"!==n||0!==r&&0!==t?l===c?r:l!r(e));return 1===n.length&&isNone(n[0])}if(!t(e))return!1;const n=e.value;return!!N(n)&&j.test(n[4].value)}const Y=String.fromCodePoint(0);function solveRandom(e,n,t,r,a,u){if(-1===n.fixed&&!u.randomCaching)return-1;const o=t.value;if(!C(o))return-1;const i=convertUnit(o,r.value);if(!twoOfSameNumeric(o,i))return-1;let l=null;if(a&&(l=convertUnit(o,a.value),!twoOfSameNumeric(o,l)))return-1;if(Number.isNaN(o[4].value)||Number.isNaN(i[4].value))return resultToCalculation(e,o,Number.NaN);if(!Number.isFinite(o[4].value))return resultToCalculation(e,o,o[4].value);if(!Number.isFinite(i[4].value)||!Number.isFinite(i[4].value-o[4].value))return resultToCalculation(e,o,Number.NaN);if(l){if(Number.isNaN(l[4].value))return resultToCalculation(e,o,Number.NaN);if(!Number.isFinite(l[4].value))return resultToCalculation(e,o,o[4].value)}const c=-1!==n.fixed?()=>n.fixed:(u.randomCaching.propertyN||(u.randomCaching.propertyN=0),sfc32(crc32([n.dashedIdent?n.dashedIdent:"",n.elementScoped?u.randomCaching.elementID:"",n.propertyScoped||n.propertyIndexScoped?u.randomCaching.propertyName:"",n.propertyIndexScoped?u.randomCaching.propertyN:"",u.randomCaching.documentID].join(Y)))),s=o[4].value;let v=i[4].value;if(v1e10)&&(l=null),l){const n=l[4].value,t=Math.max(n/1e3,1e-9);let r=Math.floor((v-s)/n);Math.abs(s+r*n-v)>=t&&Math.abs(s+(r+1)*n-v){const a=((e|=0)+(n|=0)|0)+(r|=0)|0;return r=r+1|0,e=n^n>>>9,n=(t|=0)+(t<<3)|0,t=(t=t<<21|t>>>11)+a|0,(a>>>0)/4294967296}}function crc32(e){let n,t,r=0;r^=-1;for(let a=0,u=e.length;a>>8^n;return(-1^r)>>>0}function solveCalcMix(e,n){const t=n[0].calcSum.value;if(!C(t))return-1;const r=n.map(e=>({calcSum:convertUnit(t,e.calcSum.value),percentage:e.percentage}));if(!arrayOfSameNumeric(r.map(e=>e.calcSum)))return-1;const{items:a}=normalizeMixPercentages(r,!1),u=a.map(e=>e.calcSum[4].value*e.percentage/100);let o=0;for(let e=0;e100||u>0&&n)for(const e of a)e.percentage=e.percentage*(100/u);let o=0;return u<100&&(o=100-u),{items:a,leftover:o}}const _=new Map([["abs",function abs(e,n,t){return singleNodeSolver(e,n,t,solveAbs)}],["acos",function acos(e,n,t){return singleNodeSolver(e,n,t,solveACos)}],["asin",function asin(e,n,t){return singleNodeSolver(e,n,t,solveASin)}],["atan",function atan(e,n,t){return singleNodeSolver(e,n,t,solveATan)}],["atan2",function atan2(e,n,t){return twoCommaSeparatedNodesSolver(e,n,t,solveATan2)}],["calc",calc$1],["calc-mix",function calcMix(e,n,t){const r=variadicArgumentsCalcMix(e,e.value,n,t);if(-1===r)return-1;return solveCalcMix(e,r)}],["clamp",function clamp(a,u,i){const l=resolveGlobalsAndConstants([...a.value.filter(e=>!r(e))],u),c=[],s=[],f=[];{let e=c;for(let n=0;n!r(e)),n,t);if(-1===a)return-1;const[u,o]=a,i=variadicArguments(e,o,n,t);if(-1===i)return-1;const[l,c,s]=i;if(!l||!c)return-1;return solveRandom(e,u,l,c,s,t)}],["rem",function rem(e,n,t){return twoCommaSeparatedNodesSolver(e,n,t,solveRem)}],["round",function round(e,a,u){const o=resolveGlobalsAndConstants([...e.value.filter(e=>!r(e))],a);let i="",l=!1;const c=[],p=[];{let e=c;for(let n=0;n!r(e))],n);if(1===i.length&&t(i[0]))return{inputs:[i[0]],operation:unary};let l=0;for(;l!r(e))],n),u=solve(calc$1(calcWrapper(e,a),n,t),t);return-1===u?-1:u}function twoCommaSeparatedNodesSolver(e,n,t,r){const a=twoCommaSeparatedArguments(e,n,t);if(-1===a)return-1;const[u,o]=a;return r(e,u,o,t)}function twoCommaSeparatedArguments(e,n,a){const u=resolveGlobalsAndConstants([...e.value.filter(e=>!r(e))],n),o=[],i=[];{let e=o;for(let n=0;n!r(e))],a),i=[];{const n=[];let r=[];for(let e=0;e!r(e))],a),i=[];{const n=[];let l=[];for(let e=0;e=0;i--){if(r(n[o][i]))continue;if(l=solve(calc$1(calcWrapper(e,n[o].slice(0,i+1)),a,u),u),-1===l)continue;const s=n[o].slice(i+1).filter(e=>!r(e));if(s.length)if(1===s.length&&t(s[0])&&d(s[0].value)){if(c=s[0].value[4].value,c<0||c>100)return-1}else{const n=solve(calc$1(calcWrapper(e,s),a,u),u);if(-1===n)return-1;if(!d(n.value))return-1;c=n.value[4].value,c=Math.min(100,Math.max(0,c))}break}if(-1===l)return-1;i.push({calcSum:l,percentage:c})}}return i}const H=new Set(["nearest","line-width","up","down","to-zero"]);function parseRandomValueSharing(e,n,r,a){const u={dashedIdent:"",fixed:-1,elementScoped:!1,propertyScoped:!1,propertyIndexScoped:!1};let o=!1;const i=n[0];if(!t(i)||!N(i.value))return[u,n];for(let i=0;i1)return-1;u.fixed=Math.max(0,Math.min(l.value[4].value,1-Number.EPSILON/2));continue}if("auto"!==c){if(!c.startsWith("--"))return-1;if(-1!==u.fixed||o||u.dashedIdent)return-1;u.dashedIdent=c}else{if(-1!==u.fixed||o||u.dashedIdent||u.elementScoped||u.propertyScoped||u.propertyIndexScoped)return-1;u.elementScoped=!0,u.propertyIndexScoped=!0,o=!0}}else{if(-1!==u.fixed||o||u.propertyScoped||u.propertyIndexScoped)return-1;u.propertyIndexScoped=!0}else{if(-1!==u.fixed||o||u.propertyScoped||u.propertyIndexScoped)return-1;u.propertyScoped=!0}else{if(-1!==u.fixed||o||u.elementScoped)return-1;u.elementScoped=!0}}return-1}function calcWrapper(e,n){return new o([v.Function,"calc(",e.name[2],e.name[3],{value:"calc"}],[v.CloseParen,")",e.endToken[2],e.endToken[3],void 0],n)}function maxWrapper(t,r,a){return new o([v.Function,"max(",t.name[2],t.name[3],{value:"max"}],[v.CloseParen,")",t.endToken[2],t.endToken[3],void 0],[r,new n([v.Comma,",",...e(r),void 0]),a])}function patchNaN(e){if(-1===e)return-1;if(u(e))return e;const t=e.value;return C(t)&&Number.isNaN(t[4].value)?m(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}])]):s(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:f.Integer,unit:t[4].unit}])]):d(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Percentage,"1%",t[2],t[3],{value:1}])]):-1:e}function patchInfinity(e){if(-1===e)return-1;if(u(e))return e;const t=e.value;if(!C(t))return e;if(Number.isFinite(t[4].value)||Number.isNaN(t[4].value))return e;let r="";return Number.NEGATIVE_INFINITY===t[4].value&&(r="-"),m(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}])]):s(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:f.Integer,unit:t[4].unit}])]):new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Percentage,"1%",t[2],t[3],{value:1}])])}function patchMinusZero(e){if(-1===e)return-1;if(u(e))return e;const n=e.value;return C(n)&&Object.is(-0,n[4].value)?("-0"===n[1]||(d(n)?n[1]="-0%":s(n)?n[1]="-0"+n[4].unit:n[1]="-0"),e):e}function patchPrecision(e,n=13){if(-1===e)return-1;if(n<=0)return e;if(u(e))return e;const t=e.value;if(!C(t))return e;if(Number.isInteger(t[4].value))return e;const r=Number(t[4].value.toFixed(n)).toString();return m(t)?t[1]=r:d(t)?t[1]=r+"%":s(t)&&(t[1]=r+t[4].unit),e}function patchCanonicalUnit(e){return-1===e?-1:u(e)?e:s(e.value)?(e.value=toCanonicalUnit(e.value),e):e}function patchCalcResult(e,n){let t=e;return n?.toCanonicalUnits&&(t=patchCanonicalUnit(t)),t=patchPrecision(t,n?.precision),t=patchMinusZero(t),n?.censorIntoStandardRepresentableValues||(t=patchNaN(t),t=patchInfinity(t)),t}function tokenizeGlobals(e){const n=new Map;if(!e)return n;for(const[t,r]of e)if(b(r))n.set(t,r);else if("string"==typeof r){const e=B({css:r}),a=e.nextToken();if(e.nextToken(),!e.endOfFile())continue;if(!C(a))continue;n.set(t,a);continue}return n}function calc(e,n){return calcFromComponentValues(l(A({css:e}),{}),n).map(e=>e.map(e=>F(...e.tokens())).join("")).join(",")}function calcFromComponentValues(e,n){const t=tokenizeGlobals(n?.globals);return replaceComponentValues(e,e=>{if(!u(e))return;const r=_.get(e.getName().toLowerCase());if(!r)return;const a=patchCalcResult(solve(r(e,t,n??{}),n??{}),n);return-1!==a?n?.calcWrapper?calcWrapper(e,[a]):a:void 0})}function replaceComponentValues(n,a){for(let u=0;u{if("number"!=typeof u)return;const o=a(n.node);if(!o)return;const l=[o],c=n.parent.value[u-1];t(c)&&h(c.value)&&("-"===c.value[4].value||"+"===c.value[4].value)&&l.splice(0,0,new i([[v.Whitespace," ",...e(n.node),void 0]]));const s=n.parent.value[u+1];!s||r(s)||t(s)&&(D(s.value)||w(s.value)||E(s.value)||h(s.value)&&"-"!==s.value[4].value&&"+"!==s.value[4].value)||l.push(new i([[v.Whitespace," ",...e(n.node),void 0]])),n.parent.value.splice(u,1,...l)})}return n}const J=new Set(_.keys());export{ParseError,S as ParseErrorMessage,ParseErrorWithComponentValues,calc,calcFromComponentValues,J as mathFunctionNames}; +import{sourceIndices as e,TokenNode as n,isTokenNode as t,isWhiteSpaceOrCommentNode as r,isSimpleBlockNode as a,isFunctionNode as u,FunctionNode as o,WhitespaceNode as i,parseCommaSeparatedListOfComponentValues as l,walk as c}from"@csstools/css-parser-algorithms";import{isTokenDimension as s,TokenType as v,NumberType as f,mutateUnit as p,isTokenNumber as m,isTokenPercentage as d,isTokenIdent as N,isTokenNumeric as C,isTokenOpenParen as g,isTokenDelim as h,isTokenComma as D,isToken as b,tokenizer as B,tokenize as A,stringify as F,isTokenColon as w,isTokenSemicolon as E}from"@csstools/css-tokenizer";class ParseError extends Error{sourceStart;sourceEnd;constructor(e,n,t){super(e),this.name="ParseError",this.sourceStart=n,this.sourceEnd=t}}class ParseErrorWithComponentValues extends ParseError{componentValues;constructor(n,t){super(n,...e(t)),this.componentValues=t}}const S={UnexpectedAdditionOfDimensionOrPercentageWithNumber:"Unexpected addition of a dimension or percentage with a number.",UnexpectedSubtractionOfDimensionOrPercentageWithNumber:"Unexpected subtraction of a dimension or percentage with a number."},I=/[A-Z]/g;function toLowerCaseAZ(e){return e.replace(I,e=>String.fromCharCode(e.charCodeAt(0)+32))}const y={cm:"px",in:"px",mm:"px",pc:"px",pt:"px",px:"px",q:"px",deg:"deg",grad:"deg",rad:"deg",turn:"deg",ms:"s",s:"s",hz:"hz",khz:"hz"},M=new Map([["cm",e=>e],["mm",e=>10*e],["q",e=>40*e],["in",e=>e/2.54],["pc",e=>e/2.54*6],["pt",e=>e/2.54*72],["px",e=>e/2.54*96]]),x=new Map([["deg",e=>e],["grad",e=>e/.9],["rad",e=>e/180*Math.PI],["turn",e=>e/360]]),T=new Map([["deg",e=>.9*e],["grad",e=>e],["rad",e=>.9*e/180*Math.PI],["turn",e=>.9*e/360]]),P=new Map([["hz",e=>e],["khz",e=>e/1e3]]),k=new Map([["cm",e=>2.54*e],["mm",e=>25.4*e],["q",e=>25.4*e*4],["in",e=>e],["pc",e=>6*e],["pt",e=>72*e],["px",e=>96*e]]),W=new Map([["hz",e=>1e3*e],["khz",e=>e]]),O=new Map([["cm",e=>e/10],["mm",e=>e],["q",e=>4*e],["in",e=>e/25.4],["pc",e=>e/25.4*6],["pt",e=>e/25.4*72],["px",e=>e/25.4*96]]),U=new Map([["ms",e=>e],["s",e=>e/1e3]]),L=new Map([["cm",e=>e/6*2.54],["mm",e=>e/6*25.4],["q",e=>e/6*25.4*4],["in",e=>e/6],["pc",e=>e],["pt",e=>e/6*72],["px",e=>e/6*96]]),$=new Map([["cm",e=>e/72*2.54],["mm",e=>e/72*25.4],["q",e=>e/72*25.4*4],["in",e=>e/72],["pc",e=>e/72*6],["pt",e=>e],["px",e=>e/72*96]]),V=new Map([["cm",e=>e/96*2.54],["mm",e=>e/96*25.4],["q",e=>e/96*25.4*4],["in",e=>e/96],["pc",e=>e/96*6],["pt",e=>e/96*72],["px",e=>e]]),Z=new Map([["cm",e=>e/4/10],["mm",e=>e/4],["q",e=>e],["in",e=>e/4/25.4],["pc",e=>e/4/25.4*6],["pt",e=>e/4/25.4*72],["px",e=>e/4/25.4*96]]),z=new Map([["deg",e=>180*e/Math.PI],["grad",e=>180*e/Math.PI/.9],["rad",e=>e],["turn",e=>180*e/Math.PI/360]]),q=new Map([["ms",e=>1e3*e],["s",e=>e]]),G=new Map([["deg",e=>360*e],["grad",e=>360*e/.9],["rad",e=>360*e/180*Math.PI],["turn",e=>e]]),R=new Map([["cm",M],["mm",O],["q",Z],["in",k],["pc",L],["pt",$],["px",V],["ms",U],["s",q],["deg",x],["grad",T],["rad",z],["turn",G],["hz",P],["khz",W]]);function convertUnit(e,n){if(!s(e))return n;if(!s(n))return n;const t=toLowerCaseAZ(e[4].unit),r=toLowerCaseAZ(n[4].unit);if(t===r)return n;const a=R.get(r);if(!a)return n;const u=a.get(t);if(!u)return n;const o=u(n[4].value),i=[v.Dimension,"",n[2],n[3],{...n[4],signCharacter:o<0?"-":void 0,type:Number.isInteger(o)?f.Integer:f.Number,value:o}];return p(i,e[4].unit),i}function toCanonicalUnit(e){if(!s(e))return e;const n=toLowerCaseAZ(e[4].unit),t=y[n];if(n===t)return e;const r=R.get(n);if(!r)return e;const a=r.get(t);if(!a)return e;const u=a(e[4].value),o=[v.Dimension,"",e[2],e[3],{...e[4],signCharacter:u<0?"-":void 0,type:Number.isInteger(u)?f.Integer:f.Number,value:u}];return p(o,t),o}function addition(e,t){if(2!==e.length)return-1;const r=e[0].value;let a=e[1].value;if(m(r)&&m(a)){const e=r[4].value+a[4].value;return new n([v.Number,e.toString(),r[2],a[3],{value:e,type:r[4].type===f.Integer&&a[4].type===f.Integer?f.Integer:f.Number}])}if(d(r)&&d(a)){const e=r[4].value+a[4].value;return new n([v.Percentage,e.toString()+"%",r[2],a[3],{value:e}])}if(s(r)&&s(a)&&(a=convertUnit(r,a),toLowerCaseAZ(r[4].unit)===toLowerCaseAZ(a[4].unit))){const e=r[4].value+a[4].value;return new n([v.Dimension,e.toString()+r[4].unit,r[2],a[3],{value:e,type:r[4].type===f.Integer&&a[4].type===f.Integer?f.Integer:f.Number,unit:r[4].unit}])}return(m(r)&&(s(a)||d(a))||m(a)&&(s(r)||d(r)))&&t.onParseError?.(new ParseErrorWithComponentValues(S.UnexpectedAdditionOfDimensionOrPercentageWithNumber,e)),-1}function division(e){if(2!==e.length)return-1;const t=e[0].value,r=e[1].value;if(m(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Number,e.toString(),t[2],r[3],{value:e,type:Number.isInteger(e)?f.Integer:f.Number}])}if(d(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Percentage,e.toString()+"%",t[2],r[3],{value:e}])}if(s(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Dimension,e.toString()+t[4].unit,t[2],r[3],{value:e,type:Number.isInteger(e)?f.Integer:f.Number,unit:t[4].unit}])}return-1}function isCalculation(e){return!!e&&"object"==typeof e&&"inputs"in e&&Array.isArray(e.inputs)&&"operation"in e}function solve(e,n){if(-1===e)return-1;const r=[];for(let a=0;aconvertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;if(u.some(e=>Number.isNaN(e[4].value)))return resultToCalculation(e,a,Number.NaN);if(!r.rawPercentages&&d(a))return-1;const o=u.map(e=>e[4].value);if(o.some(Number.isNaN))return resultToCalculation(e,a,Number.NaN);const i=Math.hypot(...o);return resultToCalculation(e,a,i)}function solveMax(e,n,r){if(!n.every(t))return-1;const a=n[0].value;if(!C(a))return-1;const u=n.map(e=>convertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;if(u.some(e=>Number.isNaN(e[4].value)))return resultToCalculation(e,a,Number.NaN);if(!r.rawPercentages&&d(a))return-1;const o=u.map(e=>e[4].value),i=Math.max(...o);return resultToCalculation(e,a,i)}function solveMin(e,n,r){if(!n.every(t))return-1;const a=n[0].value;if(!C(a))return-1;const u=n.map(e=>convertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;if(u.some(e=>Number.isNaN(e[4].value)))return resultToCalculation(e,a,Number.NaN);if(!r.rawPercentages&&d(a))return-1;const o=u.map(e=>e[4].value),i=Math.min(...o);return resultToCalculation(e,a,i)}function solveMod(e,n,t){const r=n.value;if(!C(r))return-1;const a=convertUnit(r,t.value);if(!twoOfSameNumeric(r,a))return-1;let u;return u=Number.isNaN(r[4].value)||Number.isNaN(a[4].value)||0===a[4].value?Number.NaN:Number.isFinite(r[4].value)&&(Number.isFinite(a[4].value)||(a[4].value!==Number.POSITIVE_INFINITY||r[4].value!==Number.NEGATIVE_INFINITY&&!Object.is(0*r[4].value,-0))&&(a[4].value!==Number.NEGATIVE_INFINITY||r[4].value!==Number.POSITIVE_INFINITY&&!Object.is(0*r[4].value,0)))?Number.isFinite(a[4].value)?(r[4].value%a[4].value+a[4].value)%a[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,u)}function solvePow(e,n,t){const r=n.value,a=t.value;if(!m(r))return-1;if(!twoOfSameNumeric(r,a))return-1;if(Number.isNaN(r[4].value)||Number.isNaN(a[4].value))return numberToCalculation(e,Number.NaN);return numberToCalculation(e,Math.pow(r[4].value,a[4].value))}function solveRem(e,n,t){const r=n.value;if(!C(r))return-1;const a=convertUnit(r,t.value);if(!twoOfSameNumeric(r,a))return-1;let u;return u=Number.isNaN(r[4].value)||Number.isNaN(a[4].value)||0===a[4].value?Number.NaN:Number.isFinite(r[4].value)?Number.isFinite(a[4].value)?r[4].value%a[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,u)}function snapAsBorderWidth(e,n,t){if(!s(n))return-1;const r=t.devicePixelLength??1,a=[v.Dimension,`${r}px`,n[2],n[3],{value:r,type:f.Integer,unit:"px"}],u=convertUnit(a,n);if(!twoOfSameNumeric(u,a))return-1;if(Number.isInteger(u[4].value/r))return resultToCalculation(e,n,n[4].value);if(u[4].value>0){if(u[4].value0?1/0:Object.is(0,0*u[4].value)?0:-0;break;default:i=Object.is(0,0*u[4].value)?0:-0}else switch(n){case"down":i=(o[4].value>0?Math.floor:Math.ceil)(u[4].value/o[4].value)*o[4].value;break;case"up":i=(o[4].value>0?Math.ceil:Math.floor)(u[4].value/o[4].value)*o[4].value;break;case"to-zero":i=Math.trunc(u[4].value/o[4].value)*o[4].value;break;default:{let t=Math.floor(u[4].value/o[4].value)*o[4].value,r=Math.ceil(u[4].value/o[4].value)*o[4].value;if(t>r){const e=t;t=r,r=e}const l=Math.abs(u[4].value-t),c=Math.abs(u[4].value-r);if(i="line-width"!==n||0!==r&&0!==t?l===c?r:l!r(e));return 1===n.length&&isNone(n[0])}if(!t(e))return!1;const n=e.value;return!!N(n)&&j.test(n[4].value)}const Y=String.fromCodePoint(0);function solveRandom(e,n,t,r,a,u){if(-1===n.fixed&&!u.randomCaching)return-1;const o=t.value;if(!C(o))return-1;const i=convertUnit(o,r.value);if(!twoOfSameNumeric(o,i))return-1;let l=null;if(a&&(l=convertUnit(o,a.value),!twoOfSameNumeric(o,l)))return-1;if(Number.isNaN(o[4].value)||Number.isNaN(i[4].value))return resultToCalculation(e,o,Number.NaN);if(!Number.isFinite(o[4].value))return resultToCalculation(e,o,o[4].value);if(!Number.isFinite(i[4].value)||!Number.isFinite(i[4].value-o[4].value))return resultToCalculation(e,o,Number.NaN);if(l){if(Number.isNaN(l[4].value))return resultToCalculation(e,o,Number.NaN);if(!Number.isFinite(l[4].value))return resultToCalculation(e,o,o[4].value)}const c=-1!==n.fixed?()=>n.fixed:(u.randomCaching.propertyN||(u.randomCaching.propertyN=0),sfc32(crc32([n.dashedIdent?n.dashedIdent:"",n.elementScoped?u.randomCaching.elementID:"",n.propertyScoped||n.propertyIndexScoped?u.randomCaching.propertyName:"",n.propertyIndexScoped?u.randomCaching.propertyN:"",u.randomCaching.documentID].join(Y)))),s=o[4].value;let v=i[4].value;if(v1e10)&&(l=null),l){const n=l[4].value,t=Math.max(n/1e3,1e-9);let r=Math.floor((v-s)/n);Math.abs(s+r*n-v)>=t&&Math.abs(s+(r+1)*n-v){const a=((e|=0)+(n|=0)|0)+(r|=0)|0;return r=r+1|0,e=n^n>>>9,n=(t|=0)+(t<<3)|0,t=(t=t<<21|t>>>11)+a|0,(a>>>0)/4294967296}}function crc32(e){let n,t,r=0;r^=-1;for(let a=0,u=e.length;a>>8^n;return(-1^r)>>>0}function solveCalcMix(e,n){const t=n[0].calcSum.value;if(!C(t))return-1;const r=n.map(e=>({calcSum:convertUnit(t,e.calcSum.value),percentage:e.percentage}));if(!arrayOfSameNumeric(r.map(e=>e.calcSum)))return-1;const{items:a}=normalizeMixPercentages(r,!1),u=a.map(e=>e.calcSum[4].value*e.percentage/100);let o=0;for(let e=0;e100||u>0&&n)for(const e of a)e.percentage=e.percentage*(100/u);let o=0;return u<100&&(o=100-u),{items:a,leftover:o}}const _=new Map([["abs",function abs(e,n,t){return singleNodeSolver(e,n,t,solveAbs)}],["acos",function acos(e,n,t){return singleNodeSolver(e,n,t,solveACos)}],["asin",function asin(e,n,t){return singleNodeSolver(e,n,t,solveASin)}],["atan",function atan(e,n,t){return singleNodeSolver(e,n,t,solveATan)}],["atan2",function atan2(e,n,t){return twoCommaSeparatedNodesSolver(e,n,t,solveATan2)}],["calc",calc$1],["calc-mix",function calcMix(e,n,t){const r=variadicArgumentsCalcMix(e,e.value,n,t);if(-1===r)return-1;return solveCalcMix(e,r)}],["clamp",function clamp(a,u,i){const l=resolveGlobalsAndConstants([...a.value.filter(e=>!r(e))],u),c=[],s=[],f=[];{let e=c;for(let n=0;n!r(e)),n,t);if(-1===a)return-1;const[u,o]=a,i=variadicArguments(e,o,n,t);if(-1===i)return-1;const[l,c,s]=i;if(!l||!c)return-1;return solveRandom(e,u,l,c,s,t)}],["rem",function rem(e,n,t){return twoCommaSeparatedNodesSolver(e,n,t,solveRem)}],["round",function round(e,a,u){const o=resolveGlobalsAndConstants([...e.value.filter(e=>!r(e))],a);let i="",l=!1;const c=[],p=[];{let e=c;for(let n=0;n!r(e))],n);if(1===i.length&&t(i[0]))return{inputs:[i[0]],operation:unary};let l=0;for(;l!r(e))],n),u=solve(calc$1(calcWrapper(e,a),n,t),t);return-1===u?-1:u}function twoCommaSeparatedNodesSolver(e,n,t,r){const a=twoCommaSeparatedArguments(e,n,t);if(-1===a)return-1;const[u,o]=a;return r(e,u,o,t)}function twoCommaSeparatedArguments(e,n,a){const u=resolveGlobalsAndConstants([...e.value.filter(e=>!r(e))],n),o=[],i=[];{let e=o;for(let n=0;n!r(e))],a),i=[];{const n=[];let r=[];for(let e=0;e!r(e))],a),i=[];{const n=[];let l=[];for(let e=0;e=0;i--){if(r(n[o][i]))continue;if(l=solve(calc$1(calcWrapper(e,n[o].slice(0,i+1)),a,u),u),-1===l)continue;const s=n[o].slice(i+1).filter(e=>!r(e));if(s.length)if(1===s.length&&t(s[0])&&d(s[0].value)){if(c=s[0].value[4].value,c<0||c>100)return-1}else{const n=solve(calc$1(calcWrapper(e,s),a,u),u);if(-1===n)return-1;if(!d(n.value))return-1;c=n.value[4].value,c=Math.min(100,Math.max(0,c))}break}if(-1===l)return-1;i.push({calcSum:l,percentage:c})}}return i}const H=new Set(["nearest","line-width","up","down","to-zero"]);function parseRandomValueSharing(e,n,r,a){const u={dashedIdent:"",fixed:-1,elementScoped:!1,propertyScoped:!1,propertyIndexScoped:!1};let o=!1;const i=n[0];if(!t(i)||!N(i.value))return[u,n];for(let i=0;i1)return-1;u.fixed=Math.max(0,Math.min(l.value[4].value,1-Number.EPSILON/2));continue}if("auto"!==c){if(!c.startsWith("--"))return 0===i?[u,n]:-1;if(-1!==u.fixed||o||u.dashedIdent)return-1;u.dashedIdent=c}else{if(-1!==u.fixed||o||u.dashedIdent||u.elementScoped||u.propertyScoped||u.propertyIndexScoped)return-1;u.elementScoped=!0,u.propertyIndexScoped=!0,o=!0}}else{if(-1!==u.fixed||o||u.propertyScoped||u.propertyIndexScoped)return-1;u.propertyIndexScoped=!0}else{if(-1!==u.fixed||o||u.propertyScoped||u.propertyIndexScoped)return-1;u.propertyScoped=!0}else{if(-1!==u.fixed||o||u.elementScoped)return-1;u.elementScoped=!0}}return-1}function calcWrapper(e,n){return new o([v.Function,"calc(",e.name[2],e.name[3],{value:"calc"}],[v.CloseParen,")",e.endToken[2],e.endToken[3],void 0],n)}function maxWrapper(t,r,a){return new o([v.Function,"max(",t.name[2],t.name[3],{value:"max"}],[v.CloseParen,")",t.endToken[2],t.endToken[3],void 0],[r,new n([v.Comma,",",...e(r),void 0]),a])}function patchNaN(e){if(-1===e)return-1;if(u(e))return e;const t=e.value;return C(t)&&Number.isNaN(t[4].value)?m(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}])]):s(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:f.Integer,unit:t[4].unit}])]):d(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Percentage,"1%",t[2],t[3],{value:1}])]):-1:e}function patchInfinity(e){if(-1===e)return-1;if(u(e))return e;const t=e.value;if(!C(t))return e;if(Number.isFinite(t[4].value)||Number.isNaN(t[4].value))return e;let r="";return Number.NEGATIVE_INFINITY===t[4].value&&(r="-"),m(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}])]):s(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:f.Integer,unit:t[4].unit}])]):new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Percentage,"1%",t[2],t[3],{value:1}])])}function patchMinusZero(e){if(-1===e)return-1;if(u(e))return e;const n=e.value;return C(n)&&Object.is(-0,n[4].value)?("-0"===n[1]||(d(n)?n[1]="-0%":s(n)?n[1]="-0"+n[4].unit:n[1]="-0"),e):e}function patchPrecision(e,n=13){if(-1===e)return-1;if(n<=0)return e;if(u(e))return e;const t=e.value;if(!C(t))return e;if(Number.isInteger(t[4].value))return e;const r=Number(t[4].value.toFixed(n)).toString();return m(t)?t[1]=r:d(t)?t[1]=r+"%":s(t)&&(t[1]=r+t[4].unit),e}function patchCanonicalUnit(e){return-1===e?-1:u(e)?e:s(e.value)?(e.value=toCanonicalUnit(e.value),e):e}function patchCalcResult(e,n){let t=e;return n?.toCanonicalUnits&&(t=patchCanonicalUnit(t)),t=patchPrecision(t,n?.precision),t=patchMinusZero(t),n?.censorIntoStandardRepresentableValues||(t=patchNaN(t),t=patchInfinity(t)),t}function tokenizeGlobals(e){const n=new Map;if(!e)return n;for(const[t,r]of e)if(b(r))n.set(t,r);else if("string"==typeof r){const e=B({css:r}),a=e.nextToken();if(e.nextToken(),!e.endOfFile())continue;if(!C(a))continue;n.set(t,a);continue}return n}function calc(e,n){return calcFromComponentValues(l(A({css:e}),{}),n).map(e=>e.map(e=>F(...e.tokens())).join("")).join(",")}function calcFromComponentValues(e,n){const t=tokenizeGlobals(n?.globals);return replaceComponentValues(e,e=>{if(!u(e))return;const r=_.get(e.getName().toLowerCase());if(!r)return;const a=patchCalcResult(solve(r(e,t,n??{}),n??{}),n);return-1!==a?n?.calcWrapper?calcWrapper(e,[a]):a:void 0})}function replaceComponentValues(n,a){for(let u=0;u{if("number"!=typeof u)return;const o=a(n.node);if(!o)return;const l=[o],c=n.parent.value[u-1];t(c)&&h(c.value)&&("-"===c.value[4].value||"+"===c.value[4].value)&&l.splice(0,0,new i([[v.Whitespace," ",...e(n.node),void 0]]));const s=n.parent.value[u+1];!s||r(s)||t(s)&&(D(s.value)||w(s.value)||E(s.value)||h(s.value)&&"-"!==s.value[4].value&&"+"!==s.value[4].value)||l.push(new i([[v.Whitespace," ",...e(n.node),void 0]])),n.parent.value.splice(u,1,...l)})}return n}const J=new Set(_.keys());export{ParseError,S as ParseErrorMessage,ParseErrorWithComponentValues,calc,calcFromComponentValues,J as mathFunctionNames}; diff --git a/packages/css-calc/src/functions/calc.ts b/packages/css-calc/src/functions/calc.ts index af55718f14..592b4ec34f 100644 --- a/packages/css-calc/src/functions/calc.ts +++ b/packages/css-calc/src/functions/calc.ts @@ -836,6 +836,14 @@ function parseRandomValueSharing(fnNode: FunctionNode, nodes: Array (e.g. a like + // `NaN`, `infinity`, `-infinity`, `e` or `pi`). + // It is the start of the first argument. + // https://drafts.csswg.org/css-values-5/#random + return [x, nodes]; + } + return -1; } diff --git a/packages/css-calc/test/additional/random.mjs b/packages/css-calc/test/additional/random.mjs index 598ce59982..2fe6a20727 100644 --- a/packages/css-calc/test/additional/random.mjs +++ b/packages/css-calc/test/additional/random.mjs @@ -532,6 +532,23 @@ for (let i = 0; i < 100; i++) { 'calc(NaN)', ); + // A bare leading is a argument, not a . + // https://drafts.csswg.org/css-values-5/#random + assert.strictEqual( + calc('random(NaN, 100)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('random(NaN * 1px, 100px)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('random(NaN * 1%, 100%)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(NaN * 1%)', + ); + assert.strictEqual( calc('random(calc(NaN * 1px), 100px)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), 'calc(NaN * 1px)', @@ -583,3 +600,55 @@ for (let i = 0; i < 100; i++) { 'calc(NaN * 1%)', ); } + +// A bare leading (`infinity`, `-infinity`, `e`, `pi`, `NaN`) is +// the start of the first argument, not a . +// https://drafts.csswg.org/css-values-5/#random +// https://drafts.csswg.org/css-values-5/#random-infinities +{ + assert.strictEqual( + calc('random(infinity, 100)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(infinity)', + ); + + assert.strictEqual( + calc('random(-infinity, 100)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(-infinity)', + ); + + assert.strictEqual( + calc('random(-infinity, infinity)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(-infinity)', + ); + + assert.strictEqual( + calc('random(infinity, -infinity)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(infinity)', + ); + + // A finite A with an infinite B makes the range infinite, so the result is NaN. + assert.strictEqual( + calc('random(100, -infinity)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('random(100, infinity)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('random(e, 10)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + '9.90307', + ); + + assert.strictEqual( + calc('random(pi, 10)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + '9.90871', + ); + + assert.strictEqual( + calc('random(e, 10, 1)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + '9.71828', + ); +} From b10e248352e472ace8314ece8dadd57ae886e1aa Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sun, 20 Sep 2026 16:47:07 +0200 Subject: [PATCH 3/5] fix --- packages/css-calc/src/functions/hypot.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/css-calc/src/functions/hypot.ts b/packages/css-calc/src/functions/hypot.ts index 7f03c0a425..7e1b590dc7 100644 --- a/packages/css-calc/src/functions/hypot.ts +++ b/packages/css-calc/src/functions/hypot.ts @@ -24,7 +24,7 @@ export function solveHypot(hypotNode: FunctionNode, solvedNodes: Array Number.isNaN(x[4].value))) { return resultToCalculation(hypotNode, firstSolvedToken, Number.NaN); @@ -36,7 +36,7 @@ export function solveHypot(hypotNode: FunctionNode, solvedNodes: Array x[4].value); - // https://drafts.csswg.org/css-values-4/#exponent-ranges + // https://drafts.csswg.org/css-values-4/#exponent-infinities // NaN is infectious, forcing the function to return NaN if any argument calculation is NaN. if (values.some(Number.isNaN)) { return resultToCalculation(hypotNode, firstSolvedToken, Number.NaN); From 373f478240bbeb78f50c21d13015bc71bf50982f Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Thu, 24 Sep 2026 20:15:38 +0200 Subject: [PATCH 4/5] fix --- .../postcss-trigonometric-functions/test/basic.expect.css | 4 ++-- .../test/basic.preserve-true.expect.css | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/postcss-trigonometric-functions/test/basic.expect.css b/plugins/postcss-trigonometric-functions/test/basic.expect.css index 01ed72a741..bc1d579020 100644 --- a/plugins/postcss-trigonometric-functions/test/basic.expect.css +++ b/plugins/postcss-trigonometric-functions/test/basic.expect.css @@ -63,12 +63,12 @@ /* Asymptotes */ order: calc(calc(infinity)); order: calc(calc(infinity)); - order: calc(calc(infinity)); - order: calc(calc(infinity)); order: calc(calc(-infinity)); order: calc(calc(-infinity)); + order: calc(calc(infinity)); order: calc(calc(-infinity)); order: calc(calc(-infinity)); + order: calc(calc(infinity)); /* 1 + 2 -> 3 */ /* 3 * 3 -> 9 */ /* 9 * 10deg -> 90deg */ diff --git a/plugins/postcss-trigonometric-functions/test/basic.preserve-true.expect.css b/plugins/postcss-trigonometric-functions/test/basic.preserve-true.expect.css index 5a17ce6ba1..5593a96170 100644 --- a/plugins/postcss-trigonometric-functions/test/basic.preserve-true.expect.css +++ b/plugins/postcss-trigonometric-functions/test/basic.preserve-true.expect.css @@ -114,17 +114,17 @@ order: tan(90deg); order: calc(calc(infinity)); order: tan(100grad); - order: calc(calc(infinity)); + order: calc(calc(-infinity)); order: tan(270deg); - order: calc(calc(infinity)); - order: tan(300grad); order: calc(calc(-infinity)); + order: tan(300grad); + order: calc(calc(infinity)); order: tan(-270deg); order: calc(calc(-infinity)); order: tan(-100grad); order: calc(calc(-infinity)); order: tan(-450deg); - order: calc(calc(-infinity)); + order: calc(calc(infinity)); order: tan(-300grad); /* 1 + 2 -> 3 */ /* 3 * 3 -> 9 */ From 6f437d0161faaab9cc10339d6ac22ea5f33013e6 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Thu, 24 Sep 2026 20:21:54 +0200 Subject: [PATCH 5/5] more tests --- packages/css-calc/test/additional/random.mjs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/css-calc/test/additional/random.mjs b/packages/css-calc/test/additional/random.mjs index 2fe6a20727..80f60565ab 100644 --- a/packages/css-calc/test/additional/random.mjs +++ b/packages/css-calc/test/additional/random.mjs @@ -404,6 +404,16 @@ assert.strictEqual( '0.3', ); +assert.strictEqual( + calc('random(fixed 0.5, 0, 1e10, 1)'), // Number of steps is exactly the upper bound, must not be enumerated + '5000000000', +); + +assert.strictEqual( + calc('random(fixed 0.5, 0, 5e7, 1)'), // Number of steps is large but below the upper bound, must not be enumerated + '25000000', +); + for (let i = 0; i < 100; i++) { const result = Number(calc(`random(--a${i}, 0, 0.3, 0.1)`, { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }));