Skip to content

Commit 2a0bfcd

Browse files
authored
postcss-random-function: update to latest specification (#1596)
1 parent 34090a0 commit 2a0bfcd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+901
-405
lines changed

packages/css-calc/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changes to CSS Calc
22

3+
### Unreleased (patch)
4+
5+
- Update `random()` to better handle floating point errors.
6+
- Update `random()` to match the latest [specification](https://drafts.csswg.org/css-values-5/#randomness)
7+
38
### 2.1.2
49

510
_February 23, 2025_

packages/css-calc/dist/index.cjs

+1-1
Large diffs are not rendered by default.

packages/css-calc/dist/index.d.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,28 @@ export declare type conversionOptions = {
4040
*/
4141
rawPercentages?: boolean;
4242
/**
43-
* Seed the pseudo random number generator used in `random()`
43+
* The values used to generate random value cache keys.
4444
*/
45-
randomSeed?: number;
45+
randomCaching?: {
46+
/**
47+
* The name of the property the random function is used in.
48+
*/
49+
propertyName: string;
50+
/**
51+
* N is the index of the random function among other random functions in the same property value.
52+
*/
53+
propertyN: number;
54+
/**
55+
* An element ID identifying the element the style is being applied to.
56+
* When omitted any `random()` call will not be computed.
57+
*/
58+
elementID: string;
59+
/**
60+
* A document ID identifying the Document the styles are from.
61+
* When omitted any `random()` call will not be computed.
62+
*/
63+
documentID: string;
64+
};
4665
};
4766

4867
export declare type GlobalsWithStrings = Map<string, TokenDimension | TokenNumber | TokenPercentage | string>;

packages/css-calc/dist/index.mjs

+1-1
Large diffs are not rendered by default.

packages/css-calc/docs/css-calc.api.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@
361361
},
362362
{
363363
"kind": "Content",
364-
"text": ";\n precision?: number;\n toCanonicalUnits?: boolean;\n censorIntoStandardRepresentableValues?: boolean;\n rawPercentages?: boolean;\n randomSeed?: number;\n}"
364+
"text": ";\n precision?: number;\n toCanonicalUnits?: boolean;\n censorIntoStandardRepresentableValues?: boolean;\n rawPercentages?: boolean;\n randomCaching?: {\n propertyName: string;\n propertyN: number;\n elementID: string;\n documentID: string;\n };\n}"
365365
},
366366
{
367367
"kind": "Content",

packages/css-calc/docs/css-calc.conversionoptions.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ export type conversionOptions = {
1313
toCanonicalUnits?: boolean;
1414
censorIntoStandardRepresentableValues?: boolean;
1515
rawPercentages?: boolean;
16-
randomSeed?: number;
16+
randomCaching?: {
17+
propertyName: string;
18+
propertyN: number;
19+
elementID: string;
20+
documentID: string;
21+
};
1722
};
1823
```
1924
**References:** [GlobalsWithStrings](./css-calc.globalswithstrings.md)

packages/css-calc/src/functions/calc.ts

+106-43
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Calculation } from '../calculation';
22
import type { ComponentValue, SimpleBlockNode } from '@csstools/css-parser-algorithms';
33
import type { Globals } from '../util/globals';
4-
import { TokenType, NumberType, isTokenOpenParen, isTokenDelim, isTokenComma, isTokenIdent } from '@csstools/css-tokenizer';
4+
import { TokenType, NumberType, isTokenOpenParen, isTokenDelim, isTokenComma, isTokenIdent, isTokenNumber } from '@csstools/css-tokenizer';
55
import { addition } from '../operation/addition';
66
import { division } from '../operation/division';
77
import { isCalculation, solve } from '../calculation';
@@ -32,6 +32,7 @@ import { unary } from '../operation/unary';
3232
import { solveLog } from './log';
3333
import { isNone } from '../util/is-none';
3434
import type { conversionOptions } from '../options';
35+
import type { RandomValueSharing} from './random';
3536
import { solveRandom } from './random';
3637

3738
type mathFunction = (node: FunctionNode, globals: Globals, options: conversionOptions) => Calculation | -1
@@ -576,65 +577,127 @@ function log(logNode: FunctionNode, globals: Globals, options: conversionOptions
576577
}
577578

578579
function random(randomNode: FunctionNode, globals: Globals, options: conversionOptions): Calculation | -1 {
579-
const nodes: Array<ComponentValue> = randomNode.value.filter(x => !isWhiteSpaceOrCommentNode(x));
580+
const randomValueSharingAndNodes = parseRandomValueSharing(
581+
randomNode.value.filter(x => !isWhiteSpaceOrCommentNode(x)),
582+
globals,
583+
options,
584+
);
585+
if (randomValueSharingAndNodes === -1) {
586+
return -1;
587+
}
580588

581-
let randomCachingOptions = '';
582-
const stepValues: Array<ComponentValue> = []
583-
const values: Array<ComponentValue> = []
589+
const [randomValueSharing, nodes] = randomValueSharingAndNodes;
584590

585-
{
586-
for (let i = 0; i < nodes.length; i++) {
587-
const node = nodes[i];
588-
if (!randomCachingOptions && values.length === 0 && isTokenNode(node) && isTokenIdent(node.value)) {
589-
const token = node.value;
590-
const tokenStr = token[4].value.toLowerCase();
591-
if (tokenStr === 'per-element' || tokenStr.startsWith('--')) {
592-
randomCachingOptions = tokenStr;
591+
const randomArguments = variadicArguments(nodes, globals, options);
592+
if (randomArguments === -1) {
593+
return -1;
594+
}
593595

594-
const nextNode = nodes[i + 1];
595-
if (!isTokenNode(nextNode) || !isTokenComma(nextNode.value)) {
596-
return -1;
597-
}
596+
const [a, b, c] = randomArguments;
598597

599-
i++;
600-
continue;
601-
}
598+
if (!a || !b) {
599+
return -1
600+
}
601+
602+
return solveRandom(
603+
randomNode,
604+
randomValueSharing,
605+
a,
606+
b,
607+
c,
608+
options
609+
);
610+
}
611+
612+
function parseRandomValueSharing(nodes: Array<ComponentValue>, globals: Globals, options: conversionOptions): [RandomValueSharing, Array<ComponentValue>] | -1 {
613+
const x: RandomValueSharing = {
614+
isAuto: false,
615+
dashedIdent: "",
616+
fixed: -1,
617+
elementShared: false,
618+
};
619+
620+
const firstNode = nodes[0];
621+
if (!isTokenNode(firstNode) || !isTokenIdent(firstNode.value)) {
622+
return [x, nodes];
623+
}
624+
625+
for (let i = 0; i < nodes.length; i++) {
626+
const node = nodes[i];
627+
if (!isTokenNode(node)) {
628+
return -1;
629+
}
630+
631+
if (isTokenComma(node.value)) {
632+
return [x, nodes.slice(i+1)];
633+
}
634+
635+
if (!isTokenIdent(node.value)) {
636+
return -1;
637+
}
638+
639+
const token = node.value;
640+
const tokenStr = token[4].value.toLowerCase();
641+
642+
if (tokenStr === 'element-shared') {
643+
if (x.fixed !== -1) {
644+
return -1;
602645
}
603646

604-
if (isTokenNode(node) && isTokenComma(node.value)) {
605-
const nextNode = nodes[i + 1];
647+
x.elementShared = true;
648+
continue;
649+
}
606650

607-
if (values.length > 0 && isTokenNode(nextNode) && isTokenIdent(nextNode.value)) {
608-
const token = nextNode.value;
609-
const tokenStr = token[4].value.toLowerCase();
610-
if (tokenStr === 'by' || tokenStr.startsWith('--')) {
611-
stepValues.push(...nodes.slice(i + 2));
651+
// fixed <number [0,1]>
652+
if (tokenStr === 'fixed') {
653+
if (x.elementShared || x.dashedIdent || x.isAuto) {
654+
return -1;
655+
}
612656

613-
break;
614-
}
615-
}
657+
i++;
658+
const nextNode = nodes[i];
659+
if (!nextNode) {
660+
return -1;
661+
}
662+
663+
const fixedNumber = solve(calc(calcWrapper([nextNode]), globals, options));
664+
if (fixedNumber === -1) {
665+
return -1;
666+
}
667+
668+
if (!isTokenNumber(fixedNumber.value)) {
669+
return -1;
670+
}
671+
672+
if (fixedNumber.value[4].value < 0 || fixedNumber.value[4].value > 1) {
673+
return -1;
616674
}
617675

618-
values.push(node);
676+
x.fixed = Math.max(0, Math.min(fixedNumber.value[4].value, 1 - 0.000_000_001));
677+
678+
continue;
619679
}
620-
}
621680

622-
const solvedValues = twoCommaSeparatedArguments(values, globals, options);
623-
if (solvedValues === -1) {
624-
return -1;
625-
}
681+
if (tokenStr === 'auto') {
682+
if (x.fixed !== -1 || x.dashedIdent) {
683+
return -1;
684+
}
626685

627-
const [a, b] = solvedValues;
686+
x.isAuto = true;
687+
continue;
688+
}
628689

629-
let solvedStepValue: TokenNode | -1 | null = null;
630-
if (stepValues.length) {
631-
solvedStepValue = singleArgument(stepValues, globals, options);
632-
if (solvedStepValue === -1) {
633-
return -1;
690+
if (tokenStr.startsWith('--')) {
691+
if (x.fixed !== -1 || x.isAuto) {
692+
return -1;
693+
}
694+
695+
x.dashedIdent = tokenStr;
696+
continue;
634697
}
635698
}
636699

637-
return solveRandom(randomNode, randomCachingOptions, a, b, solvedStepValue, options);
700+
return -1;
638701
}
639702

640703
function calcWrapper(v: Array<ComponentValue>): FunctionNode {

0 commit comments

Comments
 (0)