diff --git a/packages/css-calc/CHANGELOG.md b/packages/css-calc/CHANGELOG.md index db1718b83..f873ea274 100644 --- a/packages/css-calc/CHANGELOG.md +++ b/packages/css-calc/CHANGELOG.md @@ -2,6 +2,7 @@ ### Unreleased (patch) +- Fix infectious `NaN` - 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 225f2f19f..238f40f20 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 N,isTokenNumeric as C,isTokenOpenParen as g,isTokenDelim as D,isTokenComma as h,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."},y=/[A-Z]/g;function toLowerCaseAZ(e){return e.replace(y,e=>String.fromCharCode(e.charCodeAt(0)+32))}const I={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"},x=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]]),M=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",x],["mm",O],["q",Z],["in",k],["pc",L],["pt",$],["px",V],["ms",U],["s",q],["deg",M],["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=I[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),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=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(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.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(!m(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(!m(a))return-1;const u=n[1];if(!u||!t(u))return-1;const o=u.value;if(!m(o))return-1;return numberToCalculation(e,Math.log(a[4].value)/Math.log(o[4].value))}return-1}const j=/^none$/i;function isNone(e){if(Array.isArray(e)){const n=e.filter(e=>!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;u.randomCaching||(u.randomCaching={propertyName:"",propertyN:0,elementID:"",documentID:""}),u.randomCaching&&!u.randomCaching.propertyN&&(u.randomCaching.propertyN=0);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,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.isNaN(l[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(Y))):()=>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(!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-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 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)&&D(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)&&(h(s.value)||w(s.value)||E(s.value)||D(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/abs.ts b/packages/css-calc/src/functions/abs.ts index 00f4db5c1..f38a339c7 100644 --- a/packages/css-calc/src/functions/abs.ts +++ b/packages/css-calc/src/functions/abs.ts @@ -10,6 +10,11 @@ export function solveAbs(absNode: FunctionNode, a: TokenNode, options: conversio return -1; } + // NaN is infectious, forcing the function to return NaN if any argument calculation is NaN. + if (Number.isNaN(aToken[4].value)) { + return resultToCalculation(absNode, aToken, Number.NaN); + } + if (!options.rawPercentages && isTokenPercentage(aToken)) { return -1; } diff --git a/packages/css-calc/src/functions/clamp.ts b/packages/css-calc/src/functions/clamp.ts index f7a1fbcef..9c4e21774 100644 --- a/packages/css-calc/src/functions/clamp.ts +++ b/packages/css-calc/src/functions/clamp.ts @@ -21,10 +21,6 @@ export function solveClamp(clampNode: FunctionNode, minimum: TokenNode | -1, cen return -1; } - if (!options.rawPercentages && isTokenPercentage(minimumToken)) { - return -1; - } - const centralToken = convertUnit(minimumToken, central.value); if (!twoOfSameNumeric(minimumToken, centralToken)) { return -1; @@ -35,6 +31,19 @@ export function solveClamp(clampNode: FunctionNode, minimum: TokenNode | -1, cen return -1; } + // NaN is infectious, forcing the function to return NaN if any argument calculation is NaN. + if ( + Number.isNaN(minimumToken[4].value) || + Number.isNaN(centralToken[4].value) || + Number.isNaN(maximumToken[4].value) + ) { + return resultToCalculation(clampNode, minimumToken, Number.NaN); + } + + if (!options.rawPercentages && isTokenPercentage(minimumToken)) { + return -1; + } + const result = Math.max(minimumToken[4].value, Math.min(centralToken[4].value, maximumToken[4].value)); return resultToCalculation(clampNode, minimumToken, result); diff --git a/packages/css-calc/src/functions/hypot.ts b/packages/css-calc/src/functions/hypot.ts index f83b6c45d..9b04a58af 100644 --- a/packages/css-calc/src/functions/hypot.ts +++ b/packages/css-calc/src/functions/hypot.ts @@ -19,12 +19,18 @@ export function solveHypot(hypotNode: FunctionNode, solvedNodes: Array convertUnit(firstSolvedToken, x.value)); + if (!arrayOfSameNumeric(tokens)) { return -1; } - const tokens = solvedNodes.map((x) => convertUnit(firstSolvedToken, x.value)); - if (!arrayOfSameNumeric(tokens)) { + // 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 (tokens.some((x) => Number.isNaN(x[4].value))) { + return resultToCalculation(hypotNode, firstSolvedToken, Number.NaN); + } + + if (!options.rawPercentages && isTokenPercentage(firstSolvedToken)) { return -1; } diff --git a/packages/css-calc/src/functions/max.ts b/packages/css-calc/src/functions/max.ts index 9893a21ac..ed146f3bc 100644 --- a/packages/css-calc/src/functions/max.ts +++ b/packages/css-calc/src/functions/max.ts @@ -19,12 +19,17 @@ export function solveMax(maxNode: FunctionNode, solvedNodes: Array convertUnit(firstSolvedToken, x.value)); + if (!arrayOfSameNumeric(tokens)) { return -1; } - const tokens = solvedNodes.map((x) => convertUnit(firstSolvedToken, x.value)); - if (!arrayOfSameNumeric(tokens)) { + // NaN is infectious, forcing the function to return NaN if any argument calculation is NaN. + if (tokens.some((x) => Number.isNaN(x[4].value))) { + return resultToCalculation(maxNode, firstSolvedToken, Number.NaN); + } + + if (!options.rawPercentages && isTokenPercentage(firstSolvedToken)) { return -1; } diff --git a/packages/css-calc/src/functions/min.ts b/packages/css-calc/src/functions/min.ts index 24f21f94b..30d646c18 100644 --- a/packages/css-calc/src/functions/min.ts +++ b/packages/css-calc/src/functions/min.ts @@ -19,12 +19,17 @@ export function solveMin(minNode: FunctionNode, solvedNodes: Array convertUnit(firstSolvedToken, x.value)); + if (!arrayOfSameNumeric(tokens)) { return -1; } - const tokens = solvedNodes.map((x) => convertUnit(firstSolvedToken, x.value)); - if (!arrayOfSameNumeric(tokens)) { + // NaN is infectious, forcing the function to return NaN if any argument calculation is NaN. + if (tokens.some((x) => Number.isNaN(x[4].value))) { + return resultToCalculation(minNode, firstSolvedToken, Number.NaN); + } + + if (!options.rawPercentages && isTokenPercentage(firstSolvedToken)) { return -1; } diff --git a/packages/css-calc/src/functions/mod.ts b/packages/css-calc/src/functions/mod.ts index 41bd51294..0f543d352 100644 --- a/packages/css-calc/src/functions/mod.ts +++ b/packages/css-calc/src/functions/mod.ts @@ -17,7 +17,10 @@ export function solveMod(modNode: 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/pow.ts b/packages/css-calc/src/functions/pow.ts index e8dbfd08d..8de054ac2 100644 --- a/packages/css-calc/src/functions/pow.ts +++ b/packages/css-calc/src/functions/pow.ts @@ -15,6 +15,13 @@ export function solvePow(powNode: FunctionNode, a: TokenNode, b: TokenNode): Cal return -1; } + // https://drafts.csswg.org/css-values-4/#exponent-infinities + // NaN is infectious, forcing the function to return NaN if any argument calculation is NaN. + // Note: `Math.pow(NaN, 0)` returns `1` in JS, but CSS requires NaN. + if (Number.isNaN(aToken[4].value) || Number.isNaN(bToken[4].value)) { + return numberToCalculation(powNode, Number.NaN); + } + const result = Math.pow(aToken[4].value, bToken[4].value); return numberToCalculation(powNode, result); diff --git a/packages/css-calc/src/functions/random.ts b/packages/css-calc/src/functions/random.ts index 10f2ba59d..ed4b87e86 100644 --- a/packages/css-calc/src/functions/random.ts +++ b/packages/css-calc/src/functions/random.ts @@ -53,6 +53,10 @@ export function solveRandom(randomNode: FunctionNode, randomValueSharing: Random } } + if (Number.isNaN(aToken[4].value) || Number.isNaN(bToken[4].value)) { + return resultToCalculation(randomNode, aToken, Number.NaN); + } + if (!Number.isFinite(aToken[4].value)) { return resultToCalculation(randomNode, aToken, Number.NaN); } @@ -65,6 +69,10 @@ export function solveRandom(randomNode: FunctionNode, randomValueSharing: Random return resultToCalculation(randomNode, aToken, Number.NaN); } + if (stepValueToken && Number.isNaN(stepValueToken[4].value)) { + return resultToCalculation(randomNode, aToken, Number.NaN); + } + if (stepValueToken && !Number.isFinite(stepValueToken[4].value)) { return resultToCalculation(randomNode, aToken, aToken[4].value); } diff --git a/packages/css-calc/src/functions/rem.ts b/packages/css-calc/src/functions/rem.ts index 4467beb4e..8228837ab 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 4c6eda48e..2938e67e2 100644 --- a/packages/css-calc/src/functions/round.ts +++ b/packages/css-calc/src/functions/round.ts @@ -17,12 +17,17 @@ export function solveRound(roundNode: FunctionNode, roundingStrategy: string, a: return -1; } - if (!options.rawPercentages && isTokenPercentage(aToken)) { + const bToken = convertUnit(aToken, b.value); + if (!twoOfSameNumeric(aToken, bToken)) { return -1; } - const bToken = convertUnit(aToken, b.value); - if (!twoOfSameNumeric(aToken, bToken)) { + // 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)) { + return resultToCalculation(roundNode, aToken, Number.NaN); + } + + if (!options.rawPercentages && isTokenPercentage(aToken)) { return -1; } diff --git a/packages/css-calc/src/functions/sign.ts b/packages/css-calc/src/functions/sign.ts index d65b08c73..46934baac 100644 --- a/packages/css-calc/src/functions/sign.ts +++ b/packages/css-calc/src/functions/sign.ts @@ -10,6 +10,11 @@ export function solveSign(signNode: FunctionNode, a: TokenNode, options: convers return -1; } + // NaN is infectious, forcing the function to return NaN if any argument calculation is NaN. + if (Number.isNaN(aToken[4].value)) { + return numberToCalculation(signNode, Number.NaN); + } + if (!options.rawPercentages && isTokenPercentage(aToken)) { return -1; } diff --git a/packages/css-calc/src/util/is-none.ts b/packages/css-calc/src/util/is-none.ts index ac74f5401..2ce00fd4f 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 b7f89f2a6..1dbd8cd05 100644 --- a/packages/css-calc/test/additional/random.mjs +++ b/packages/css-calc/test/additional/random.mjs @@ -440,3 +440,68 @@ for (let i = 0; i < 100; i++) { assert.ok(result <= 190); assert.ok(result === 100 || result === 130 || result === 160 || result === 190); } + +// NaN is infectious: any NaN argument forces random() to return NaN. +// https://drafts.csswg.org/css-values-5/#random-infinities +{ + assert.strictEqual( + calc('random(100, NaN)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('random(calc(NaN), 100)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('random(calc(NaN * 1px), 100px)', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('random(100px, calc(NaN * 1px))', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('random(0%, calc(NaN * 1%))', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(NaN * 1%)', + ); + + assert.strictEqual( + calc('random(100px, 500px, calc(NaN * 1px))', { randomCaching: { documentID: 'a', elementID: 'b', propertyName: 'c', propertyN: 0 } }), + 'calc(NaN * 1px)', + ); + + // NaN in any of the three arguments, including the optional step. + assert.strictEqual( + calc('random(fixed 0.5, NaN, 100)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('random(fixed 0.5, 10, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('random(fixed 0.5, NaN * 1px, 100px)'), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('random(fixed 0.5, 10px, NaN * 1px, 5px)'), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('random(fixed 0.5, NaN, 100, 5)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('random(fixed 0.5, NaN * 1%, 100%)'), + 'calc(NaN * 1%)', + ); +} 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 d251c4426..9e305bd16 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/index.mjs b/packages/css-calc/test/wpt/index.mjs index 5fd5ddd70..800512795 100644 --- a/packages/css-calc/test/wpt/index.mjs +++ b/packages/css-calc/test/wpt/index.mjs @@ -15,6 +15,7 @@ import './clamp-length-computed.mjs'; import './clamp-length-invalid.mjs'; import './exp-log-compute.mjs'; import './hypot-pow-sqrt-computed.mjs'; +import './infectious-nan.mjs'; import './invalid.mjs'; import './max-20-arguments.mjs'; import './minmax-angle-computed.mjs'; diff --git a/packages/css-calc/test/wpt/infectious-nan.mjs b/packages/css-calc/test/wpt/infectious-nan.mjs new file mode 100644 index 000000000..2b1f2702a --- /dev/null +++ b/packages/css-calc/test/wpt/infectious-nan.mjs @@ -0,0 +1,532 @@ +import { calc } from '@csstools/css-calc'; +import assert from 'node:assert'; + +// NaN is infectious: any operation or math function with a NaN argument produces NaN. +// https://drafts.csswg.org/css-values-4/#calc-ieee + +// calc() arithmetic +{ + assert.strictEqual( + calc('calc(NaN + 2)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('calc(2 + NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('calc(NaN - 2)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('calc(2 - NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('calc(NaN * 2)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('calc(2 * NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('calc(NaN / 2)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('calc(2 / NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('calc(NaN * 1px)'), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('calc(1px * NaN)'), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('calc(NaN * 1%)'), + 'calc(NaN * 1%)', + ); + + assert.strictEqual( + calc('calc(10.135262721212548pc - 199pt / NaN)'), + 'calc(NaN * 1pc)', + ); +} + +// min() / max() / clamp() +{ + assert.strictEqual( + calc('min(NaN, 1)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('min(1, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('max(NaN, 1)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('max(1, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('clamp(NaN, 1, 2)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('clamp(1, NaN, 2)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('clamp(1, 2, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('min(1px, NaN * 1px)'), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('max(NaN * 1px, 15px)'), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('clamp(1px, NaN * 1px, 2px)'), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('max(NaN * 1%, 50%)'), + 'calc(NaN * 1%)', + ); + + assert.strictEqual( + calc('clamp(NaN * 1%, 1%, 2%)'), + 'calc(NaN * 1%)', + ); + + assert.strictEqual( + calc('clamp(1%, NaN * 1%, 2%)'), + 'calc(NaN * 1%)', + ); + + assert.strictEqual( + calc('clamp(1%, 2%, NaN * 1%)'), + 'calc(NaN * 1%)', + ); + + // NaN is infectious even when mixed with infinities. + // https://drafts.csswg.org/css-values-4/#exponent-infinities + assert.strictEqual( + calc('max(infinity, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('max(NaN, -infinity)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('min(infinity, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('min(NaN, -infinity)'), + 'calc(NaN)', + ); + + // NaN propagates through nested math functions. + assert.strictEqual( + calc('min(max(NaN, 1), 2)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('max(min(NaN, 1), 2)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('clamp(max(NaN, 1), 2, 3)'), + 'calc(NaN)', + ); +} + +// round() / mod() / rem() +{ + assert.strictEqual( + calc('round(NaN, 5)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('round(5, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('round(up, 5, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('round(down, NaN, 5)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('round(5px, NaN * 1px)'), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('round(NaN * 1px, 5px)'), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('round(line-width, NaN * 1px)'), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('mod(NaN, 5)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('mod(5, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('rem(NaN, 5)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('rem(5, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('mod(5px, NaN * 1px)'), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('rem(5px, NaN * 1px)'), + 'calc(NaN * 1px)', + ); + + // NaN is infectious even when the arguments are percentages. + assert.strictEqual( + calc('round(NaN * 1%, 5%)'), + 'calc(NaN * 1%)', + ); + + assert.strictEqual( + calc('round(5%, NaN * 1%)'), + 'calc(NaN * 1%)', + ); + + assert.strictEqual( + calc('mod(NaN * 1%, 5%)'), + 'calc(NaN * 1%)', + ); + + assert.strictEqual( + calc('rem(5%, NaN * 1%)'), + 'calc(NaN * 1%)', + ); + + // NaN is infectious even when it would otherwise be masked by the + // "B is 0" / "both infinite" rules in https://drafts.csswg.org/css-values-4/#round-infinities + assert.strictEqual( + calc('round(NaN, 0)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('round(down, NaN, 0)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('mod(NaN, 0)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('rem(NaN, 0)'), + 'calc(NaN)', + ); +} + +// abs() / sign() +{ + assert.strictEqual( + calc('abs(NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('sign(NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('abs(NaN * 1px)'), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('sign(NaN * 1px)'), + 'calc(NaN)', + ); + + // NaN is infectious even when the argument is a percentage. + assert.strictEqual( + calc('abs(NaN * 1%)'), + 'calc(NaN * 1%)', + ); + + assert.strictEqual( + calc('sign(NaN * 1%)'), + 'calc(NaN)', + ); + + // NaN is infectious even when produced by a nested calculation. + assert.strictEqual( + calc('abs(calc(1px * NaN))'), + 'calc(NaN * 1px)', + ); + + assert.strictEqual( + calc('sign(calc(NaN + 2))'), + 'calc(NaN)', + ); +} + +// trigonometric functions +{ + assert.strictEqual( + calc('sin(NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('cos(NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('tan(NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('asin(NaN)'), + 'calc(NaN * 1rad)', + ); + + assert.strictEqual( + calc('acos(NaN)'), + 'calc(NaN * 1rad)', + ); + + assert.strictEqual( + calc('atan(NaN)'), + 'calc(NaN * 1rad)', + ); + + assert.strictEqual( + calc('atan2(NaN, 1)'), + 'calc(NaN * 1rad)', + ); + + assert.strictEqual( + calc('atan2(1, NaN)'), + 'calc(NaN * 1rad)', + ); +} + +// exponential functions +{ + assert.strictEqual( + calc('pow(NaN, 0)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('pow(0, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('sqrt(NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('exp(NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('hypot(NaN, 1)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('hypot(1, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('log(NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('log(NaN, 2)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('log(2, NaN)'), + 'calc(NaN)', + ); + + // NaN is infectious even when the arguments are percentages. + assert.strictEqual( + calc('hypot(NaN * 1%, 50%)'), + 'calc(NaN * 1%)', + ); + + assert.strictEqual( + calc('hypot(50%, NaN * 1%)'), + 'calc(NaN * 1%)', + ); + + // hypot() with any infinite input is +infinity, unless NaN is present. + // https://drafts.csswg.org/css-values-4/#exponent-infinities + assert.strictEqual( + calc('hypot(-infinity, 5)'), + 'calc(infinity)', + ); + + assert.strictEqual( + calc('hypot(5, -infinity)'), + 'calc(infinity)', + ); + + assert.strictEqual( + calc('hypot(-infinity, -infinity)'), + 'calc(infinity)', + ); + + assert.strictEqual( + calc('hypot(-infinity, NaN)'), + 'calc(NaN)', + ); + + // pow() with NaN in either argument is NaN, overriding the + // "B is 0 -> 1" / infinite base rules. + assert.strictEqual( + calc('pow(0, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('pow(infinity, NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('pow(-infinity, NaN)'), + 'calc(NaN)', + ); +} + +// calc-mix() +{ + assert.strictEqual( + calc('calc-mix(NaN)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('calc-mix(NaN 50%, 1 50%)'), + 'calc(NaN)', + ); + + assert.strictEqual( + calc('calc-mix(1 50%, NaN 50%)'), + 'calc(NaN)', + ); +} + +// Percentages that are not NaN must remain unsolved (conservative static analysis). +{ + assert.strictEqual( + calc('abs(50%)'), + 'abs(50%)', + ); + + assert.strictEqual( + calc('sign(50%)'), + 'sign(50%)', + ); + + assert.strictEqual( + calc('round(50%, 10%)'), + 'round(50%, 10%)', + ); + + assert.strictEqual( + calc('min(50%, 100%)'), + 'min(50%, 100%)', + ); + + assert.strictEqual( + calc('max(50%, 100%)'), + 'max(50%, 100%)', + ); + + assert.strictEqual( + calc('hypot(50%, 30%)'), + 'hypot(50%, 30%)', + ); + + assert.strictEqual( + calc('clamp(0%, 50%, 100%)'), + 'clamp(0%, 50%, 100%)', + ); +} 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 7420acb0b..cf4a63f2c 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)', +);