diff --git a/packages/css-calc/CHANGELOG.md b/packages/css-calc/CHANGELOG.md index f873ea274..de673405e 100644 --- a/packages/css-calc/CHANGELOG.md +++ b/packages/css-calc/CHANGELOG.md @@ -2,6 +2,16 @@ ### 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 `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` - 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) diff --git a/packages/css-calc/dist/index.mjs b/packages/css-calc/dist/index.mjs index 238f40f20..c417ae06b 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 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}; +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 709be65d3..592b4ec34 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 (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/src/functions/hypot.ts b/packages/css-calc/src/functions/hypot.ts index 9b04a58af..7e1b590dc 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); @@ -35,6 +35,13 @@ export function solveHypot(hypotNode: FunctionNode, solvedNodes: Array x[4].value); + + // 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); + } + 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 28070f3f6..03fd4f53a 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 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; + } - const rnd = randomValueSharing.fixed === -1 ? sfc32( - crc32( - [ - randomValueSharing.dashedIdent ? randomValueSharing.dashedIdent : "", - randomValueSharing.elementScoped ? options.randomCaching.elementID : "", - (randomValueSharing.propertyScoped || randomValueSharing.propertyIndexScoped) ? options.randomCaching.propertyName : "", - randomValueSharing.propertyIndexScoped ? options.randomCaching.propertyN : "", - options.randomCaching.documentID, - ].join(NULL_CHAR), - ), - ) : () : number => { return randomValueSharing.fixed; }; - - let min = aToken[4].value; + 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) || @@ -105,31 +114,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/round.ts b/packages/css-calc/src/functions/round.ts index 2938e67e2..493a74ede 100644 --- a/packages/css-calc/src/functions/round.ts +++ b/packages/css-calc/src/functions/round.ts @@ -80,10 +80,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; @@ -102,7 +106,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 8fa31b18b..b208f81c7 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/test/additional/random.mjs b/packages/css-calc/test/additional/random.mjs index 1dbd8cd05..80f60565a 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( @@ -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 } })); @@ -441,6 +451,84 @@ for (let i = 0; i < 100; i++) { 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)', + ); +} + // NaN is infectious: any NaN argument forces random() to return NaN. // https://drafts.csswg.org/css-values-5/#random-infinities { @@ -454,6 +542,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)', @@ -505,3 +610,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', + ); +} diff --git a/packages/css-calc/test/additional/round-line-width.mjs b/packages/css-calc/test/additional/round-line-width.mjs index b5049f5fc..f536cbed7 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 4f53924ae..2b298a933 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 502e71923..e9af4770b 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/round-function.mjs b/packages/css-calc/test/wpt/round-function.mjs index 417125e40..014c87904 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/plugins/postcss-trigonometric-functions/test/basic.expect.css b/plugins/postcss-trigonometric-functions/test/basic.expect.css index 01ed72a74..bc1d57902 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 5a17ce6ba..5593a9617 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 */