diff --git a/package-lock.json b/package-lock.json
index 40750c169..b45f8052f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"workspaces": [
"packages/css-tokenizer",
"packages/css-parser-algorithms",
+ "packages/cascade-layer-name-parser",
"packages/media-query-list-parser",
"packages/*",
"plugins/postcss-progressive-custom-properties",
@@ -1884,6 +1885,10 @@
"resolved": "packages/base-cli",
"link": true
},
+ "node_modules/@csstools/cascade-layer-name-parser": {
+ "resolved": "packages/cascade-layer-name-parser",
+ "link": true
+ },
"node_modules/@csstools/css-has-pseudo-experimental": {
"resolved": "experimental/css-has-pseudo",
"link": true
@@ -6908,6 +6913,22 @@
"postcss": "^8.4"
}
},
+ "packages/cascade-layer-name-parser": {
+ "name": "@csstools/cascade-layer-name-parser",
+ "version": "1.0.0",
+ "license": "MIT",
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^1.0.0",
+ "@csstools/css-tokenizer": "^1.0.0"
+ }
+ },
"packages/css-parser-algorithms": {
"name": "@csstools/css-parser-algorithms",
"version": "1.0.0",
@@ -7284,6 +7305,7 @@
"version": "9.0.1",
"license": "MIT",
"dependencies": {
+ "@csstools/cascade-layer-name-parser": "^1.0.0",
"@csstools/css-parser-algorithms": "^1.0.0",
"@csstools/css-tokenizer": "^1.0.0",
"@csstools/media-query-list-parser": "^1.0.0"
@@ -9062,6 +9084,10 @@
"version": "file:packages/base-cli",
"requires": {}
},
+ "@csstools/cascade-layer-name-parser": {
+ "version": "file:packages/cascade-layer-name-parser",
+ "requires": {}
+ },
"@csstools/css-has-pseudo-experimental": {
"version": "file:experimental/css-has-pseudo",
"requires": {}
@@ -11761,6 +11787,7 @@
"postcss-custom-media": {
"version": "file:plugins/postcss-custom-media",
"requires": {
+ "@csstools/cascade-layer-name-parser": "^1.0.0",
"@csstools/css-parser-algorithms": "^1.0.0",
"@csstools/css-tokenizer": "^1.0.0",
"@csstools/media-query-list-parser": "^1.0.0"
diff --git a/package.json b/package.json
index 03a1c0037..396ae0e04 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"workspaces": [
"packages/css-tokenizer",
"packages/css-parser-algorithms",
+ "packages/cascade-layer-name-parser",
"packages/media-query-list-parser",
"packages/*",
"plugins/postcss-progressive-custom-properties",
diff --git a/packages/cascade-layer-name-parser/.gitignore b/packages/cascade-layer-name-parser/.gitignore
new file mode 100644
index 000000000..8b84de034
--- /dev/null
+++ b/packages/cascade-layer-name-parser/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+package-lock.json
+yarn.lock
+*.result.css
+*.result.css.map
+*.result.json
diff --git a/packages/cascade-layer-name-parser/.nvmrc b/packages/cascade-layer-name-parser/.nvmrc
new file mode 100644
index 000000000..f0b10f153
--- /dev/null
+++ b/packages/cascade-layer-name-parser/.nvmrc
@@ -0,0 +1 @@
+v16.13.1
diff --git a/packages/cascade-layer-name-parser/CHANGELOG.md b/packages/cascade-layer-name-parser/CHANGELOG.md
new file mode 100644
index 000000000..1cceb26a8
--- /dev/null
+++ b/packages/cascade-layer-name-parser/CHANGELOG.md
@@ -0,0 +1,3 @@
+### 1.0.0 (Unreleased)
+
+- Initial version
diff --git a/packages/cascade-layer-name-parser/LICENSE.md b/packages/cascade-layer-name-parser/LICENSE.md
new file mode 100644
index 000000000..af5411fa2
--- /dev/null
+++ b/packages/cascade-layer-name-parser/LICENSE.md
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright 2022 Romain Menke, Antonio Laguna
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/cascade-layer-name-parser/README.md b/packages/cascade-layer-name-parser/README.md
new file mode 100644
index 000000000..ab897613f
--- /dev/null
+++ b/packages/cascade-layer-name-parser/README.md
@@ -0,0 +1,32 @@
+# Cascade Layer Name Parser
+
+[
][npm-url]
+[
][cli-url]
+[
][discord]
+
+## Usage
+
+Add [Cascade Layer Name Parser] to your project:
+
+```bash
+npm install postcss @csstools/cascade-layer-name-parser @csstools/css-parser-algorithms @csstools/css-tokenizer --save-dev
+```
+
+[Cascade Layer Name Parser] depends on our CSS tokenizer and parser algorithms.
+It must be used together with `@csstools/css-tokenizer` and `@csstools/css-parser-algorithms`.
+
+```ts
+import { parse } from '@csstools/cascade-layer-name-parser';
+
+const layerNames = parse('layer-name, foo.bar');
+layerNames.forEach((layerName) => {
+ console.log(layerName.name()) // "foo.bar"
+ console.log(layerName.segments()) // ["foo", "bar"]
+});
+```
+
+[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
+[discord]: https://discord.gg/bUadyRwkJS
+[npm-url]: https://www.npmjs.com/package/@csstools/cascade-layer-name-parser
+
+[Cascade Layer Name Parser]: https://github.com/csstools/postcss-plugins/tree/main/packages/cascade-layer-name-parser
diff --git a/packages/cascade-layer-name-parser/dist/index.cjs b/packages/cascade-layer-name-parser/dist/index.cjs
new file mode 100644
index 000000000..bc1f8c66a
--- /dev/null
+++ b/packages/cascade-layer-name-parser/dist/index.cjs
@@ -0,0 +1 @@
+"use strict";var e=require("@csstools/css-tokenizer"),t=require("@csstools/css-parser-algorithms");class LayerName{parts;constructor(e){this.parts=e}tokens(){return[...this.parts]}slice(t,n){const r=[];for(let t=0;tt[0]===e.TokenType.Ident||t[0]===e.TokenType.Delim)),n,...t.parts.filter((t=>t[0]===e.TokenType.Ident||t[0]===e.TokenType.Delim))])}segments(){return this.parts.filter((t=>t[0]===e.TokenType.Ident)).map((e=>e[4].value))}name(){return this.parts.filter((t=>t[0]===e.TokenType.Ident||t[0]===e.TokenType.Delim)).map((e=>e[1])).join("")}equal(e){const t=this.segments(),n=e.segments();if(t.length!==n.length)return!1;for(let e=0;e{}),genericError=e=>({message:`Invalid cascade layer name. ${e}`,start:n[0][2],end:n[n.length-1][3],state:["6.4.2. Layer Naming and Nesting","Layer name syntax"," = [ '.' ]*"]}),a=[];for(let n=0;ne.tokens()));let p,l=!1,m=!1;for(let t=0;t{const n=t.segments();e:for(let r=0;r=a&&(o=t,a=r)}-1===o?e.push(n):e.splice(o+1,0,n)}})),e},exports.parse=function parse(t,n){const r=e.tokenizer({css:t},{commentsAreTokens:!0,onParseError:null==n?void 0:n.onParseError}),s=[];for(;!r.endOfFile();)s.push(r.nextToken());return s.push(r.nextToken()),parseFromTokens(s,n)},exports.parseFromTokens=parseFromTokens;
diff --git a/packages/cascade-layer-name-parser/dist/index.d.ts b/packages/cascade-layer-name-parser/dist/index.d.ts
new file mode 100644
index 000000000..2592bc2e1
--- /dev/null
+++ b/packages/cascade-layer-name-parser/dist/index.d.ts
@@ -0,0 +1,3 @@
+export { LayerName } from './nodes/layer-name';
+export { addLayerToModel } from './util/model';
+export { parse, parseFromTokens } from './parser/parse';
diff --git a/packages/cascade-layer-name-parser/dist/index.mjs b/packages/cascade-layer-name-parser/dist/index.mjs
new file mode 100644
index 000000000..6dc6faf7e
--- /dev/null
+++ b/packages/cascade-layer-name-parser/dist/index.mjs
@@ -0,0 +1 @@
+import{TokenType as e,stringify as t,tokenizer as n}from"@csstools/css-tokenizer";import{parseCommaSeparatedListOfComponentValues as r,isTokenNode as s,isCommentNode as a,isWhitespaceNode as o}from"@csstools/css-parser-algorithms";class LayerName{parts;constructor(e){this.parts=e}tokens(){return[...this.parts]}slice(t,n){const r=[];for(let t=0;tt[0]===e.Ident||t[0]===e.Delim)),n,...t.parts.filter((t=>t[0]===e.Ident||t[0]===e.Delim))])}segments(){return this.parts.filter((t=>t[0]===e.Ident)).map((e=>e[4].value))}name(){return this.parts.filter((t=>t[0]===e.Ident||t[0]===e.Delim)).map((e=>e[1])).join("")}equal(e){const t=this.segments(),n=e.segments();if(t.length!==n.length)return!1;for(let e=0;e{const n=t.segments();e:for(let r=0;r=o&&(a=t,o=r)}-1===a?e.push(n):e.splice(a+1,0,n)}})),e}function parseFromTokens(t,n){const i=r(t,{onParseError:null==n?void 0:n.onParseError}),l=(null==n?void 0:n.onParseError)??(()=>{}),genericError=e=>({message:`Invalid cascade layer name. ${e}`,start:t[0][2],end:t[t.length-1][3],state:["6.4.2. Layer Naming and Nesting","Layer name syntax"," = [ '.' ]*"]}),m=[];for(let t=0;te.tokens()));let c,u=!1,p=!1;for(let t=0;t;
+ constructor(parts: Array);
+ tokens(): Array;
+ slice(start: number, end: number): LayerName;
+ concat(other: LayerName): LayerName;
+ segments(): Array;
+ name(): string;
+ equal(other: LayerName): boolean;
+ toString(): string;
+ toJSON(): {
+ parts: CSSToken[];
+ segments: string[];
+ name: string;
+ };
+}
diff --git a/packages/cascade-layer-name-parser/dist/parser/parse.d.ts b/packages/cascade-layer-name-parser/dist/parser/parse.d.ts
new file mode 100644
index 000000000..ed13df9f3
--- /dev/null
+++ b/packages/cascade-layer-name-parser/dist/parser/parse.d.ts
@@ -0,0 +1,8 @@
+import { ParserError } from '@csstools/css-parser-algorithms/dist/interfaces/error';
+import { CSSToken } from '@csstools/css-tokenizer';
+import { LayerName } from '../nodes/layer-name';
+export type Options = {
+ onParseError?: (error: ParserError) => void;
+};
+export declare function parseFromTokens(tokens: Array, options?: Options): LayerName[];
+export declare function parse(source: string, options?: Options): LayerName[];
diff --git a/packages/cascade-layer-name-parser/dist/util/model.d.ts b/packages/cascade-layer-name-parser/dist/util/model.d.ts
new file mode 100644
index 000000000..7347ce264
--- /dev/null
+++ b/packages/cascade-layer-name-parser/dist/util/model.d.ts
@@ -0,0 +1,2 @@
+import { LayerName } from '../nodes/layer-name';
+export declare function addLayerToModel(layers: Array, currentLayerNames: Array): LayerName[];
diff --git a/packages/cascade-layer-name-parser/package.json b/packages/cascade-layer-name-parser/package.json
new file mode 100644
index 000000000..9464189f9
--- /dev/null
+++ b/packages/cascade-layer-name-parser/package.json
@@ -0,0 +1,72 @@
+{
+ "name": "@csstools/cascade-layer-name-parser",
+ "description": "Parse CSS Cascade Layer names.",
+ "version": "1.0.0",
+ "contributors": [
+ {
+ "name": "Antonio Laguna",
+ "email": "antonio@laguna.es",
+ "url": "https://antonio.laguna.es"
+ },
+ {
+ "name": "Romain Menke",
+ "email": "romainmenke@gmail.com"
+ }
+ ],
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ },
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "main": "dist/index.cjs",
+ "module": "dist/index.mjs",
+ "types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.cjs",
+ "default": "./dist/index.mjs"
+ }
+ },
+ "files": [
+ "CHANGELOG.md",
+ "LICENSE.md",
+ "README.md",
+ "dist"
+ ],
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^1.0.0",
+ "@csstools/css-tokenizer": "^1.0.0"
+ },
+ "scripts": {
+ "prebuild": "npm run clean",
+ "build": "rollup -c ../../rollup/default.mjs",
+ "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true }); fs.mkdirSync('./dist');\"",
+ "lint": "npm run lint:eslint && npm run lint:package-json",
+ "lint:eslint": "eslint ./src --ext .js --ext .ts --ext .mjs --no-error-on-unmatched-pattern",
+ "lint:package-json": "node ../../.github/bin/format-package-json.mjs",
+ "prepublishOnly": "npm run clean && npm run build && npm run test",
+ "stryker": "stryker run --logLevel error",
+ "test": "node ./test/test.mjs",
+ "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs",
+ "test:rewrite-expects": "REWRITE_EXPECTS=true node ./test/test.mjs"
+ },
+ "homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/cascade-layer-name-parser#readme",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/csstools/postcss-plugins.git",
+ "directory": "packages/cascade-layer-name-parser"
+ },
+ "bugs": "https://github.com/csstools/postcss-plugins/issues",
+ "keywords": [
+ "cascade-layer",
+ "css",
+ "parser"
+ ],
+ "volta": {
+ "extends": "../../package.json"
+ }
+}
diff --git a/packages/cascade-layer-name-parser/src/index.ts b/packages/cascade-layer-name-parser/src/index.ts
new file mode 100644
index 000000000..2592bc2e1
--- /dev/null
+++ b/packages/cascade-layer-name-parser/src/index.ts
@@ -0,0 +1,3 @@
+export { LayerName } from './nodes/layer-name';
+export { addLayerToModel } from './util/model';
+export { parse, parseFromTokens } from './parser/parse';
diff --git a/packages/cascade-layer-name-parser/src/nodes/layer-name.ts b/packages/cascade-layer-name-parser/src/nodes/layer-name.ts
new file mode 100644
index 000000000..7878c714a
--- /dev/null
+++ b/packages/cascade-layer-name-parser/src/nodes/layer-name.ts
@@ -0,0 +1,94 @@
+import { TokenDelim } from '@csstools/css-tokenizer';
+import { CSSToken, stringify, TokenIdent, TokenType } from '@csstools/css-tokenizer';
+
+export class LayerName {
+ parts: Array;
+
+ constructor(parts: Array) {
+ this.parts = parts;
+ }
+
+ tokens(): Array {
+ return [
+ ...this.parts,
+ ];
+ }
+
+ slice(start: number, end: number): LayerName {
+ const indices = [];
+ for (let i = 0; i < this.parts.length; i++) {
+ if (this.parts[i][0] === TokenType.Ident) {
+ indices.push(i);
+ }
+ }
+
+ const slice = indices.slice(start, end);
+ return new LayerName(this.parts.slice(slice[0], slice[slice.length-1]+1));
+ }
+
+ concat(other: LayerName): LayerName {
+ const dot: TokenDelim = [
+ TokenType.Delim,
+ '.',
+ -1,
+ -1,
+ { value: '.' },
+ ];
+
+ return new LayerName([
+ ...this.parts.filter((x) => {
+ return x[0] === TokenType.Ident || x[0] === TokenType.Delim;
+ }),
+ dot,
+ ...other.parts.filter((x) => {
+ return x[0] === TokenType.Ident || x[0] === TokenType.Delim;
+ }),
+ ]);
+ }
+
+ segments(): Array {
+ return this.parts.filter((x) => {
+ return x[0] === TokenType.Ident;
+ }).map((x: TokenIdent) => {
+ return x[4].value;
+ });
+ }
+
+ name(): string {
+ return this.parts.filter((x) => {
+ return x[0] === TokenType.Ident || x[0] === TokenType.Delim;
+ }).map((x: TokenIdent) => {
+ return x[1];
+ }).join('');
+ }
+
+ equal(other: LayerName): boolean {
+ const a = this.segments();
+ const b = other.segments();
+ if (a.length !== b.length) {
+ return false;
+ }
+
+ for (let i = 0; i < a.length; i++) {
+ const aa = a[i];
+ const bb = b[i];
+ if (aa !== bb) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ toString(): string {
+ return stringify(...this.parts);
+ }
+
+ toJSON() {
+ return {
+ parts: this.parts,
+ segments: this.segments(),
+ name: this.name(),
+ };
+ }
+}
diff --git a/packages/cascade-layer-name-parser/src/parser/parse.ts b/packages/cascade-layer-name-parser/src/parser/parse.ts
new file mode 100644
index 000000000..238dc0a22
--- /dev/null
+++ b/packages/cascade-layer-name-parser/src/parser/parse.ts
@@ -0,0 +1,147 @@
+import { isCommentNode, isTokenNode, isWhitespaceNode } from '@csstools/css-parser-algorithms';
+import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms';
+import { ParserError } from '@csstools/css-parser-algorithms/dist/interfaces/error';
+import { CSSToken, tokenizer, TokenType } from '@csstools/css-tokenizer';
+import { LayerName } from '../nodes/layer-name';
+
+export type Options = {
+ onParseError?: (error: ParserError) => void
+}
+
+export function parseFromTokens(tokens: Array, options?: Options) {
+ const componentValuesLists = parseCommaSeparatedListOfComponentValues(tokens, {
+ onParseError: options?.onParseError,
+ });
+
+ const onParseError = options?.onParseError ?? (() => {
+ // noop;
+ });
+
+ // There is no error recovery when parsing layer names.
+ // They are either fully valid or fully invalid.
+
+ const genericError = (message) => {
+ return {
+ message: `Invalid cascade layer name. ${message}`,
+ start: tokens[0][2],
+ end: tokens[tokens.length - 1][3],
+ state: [
+ '6.4.2. Layer Naming and Nesting',
+ 'Layer name syntax',
+ ' = [ \'.\' ]*',
+ ],
+ };
+ };
+
+ const result: Array = [];
+
+ for (let i = 0; i < componentValuesLists.length; i++) {
+ const componentValuesList = componentValuesLists[i];
+ for (let j = 0; j < componentValuesList.length; j++) {
+ const componentValue = componentValuesList[j];
+ if (!isTokenNode(componentValue) && !isCommentNode(componentValue) && !isWhitespaceNode(componentValue)) {
+ onParseError(genericError(`Invalid layer name part "${componentValue.toString()}"`));
+ return [];
+ }
+ }
+
+ const componentValueTokens = componentValuesList.flatMap((x) => x.tokens());
+
+ let inLayerNameSequence = false;
+ let sawWhiteSpaceAfterIdent = false;
+ let lastToken: CSSToken;
+ for (let j = 0; j < componentValueTokens.length; j++) {
+ const token = componentValueTokens[j];
+ if (!(
+ token[0] === TokenType.Comment ||
+ token[0] === TokenType.Whitespace ||
+ token[0] === TokenType.Ident ||
+ (
+ token[0] === TokenType.Delim &&
+ token[4].value === '.'
+ )
+ )) {
+ onParseError(genericError(`Invalid character "${token[1]}"`));
+ return [];
+ }
+
+ if (!inLayerNameSequence) {
+ if (token[0] === TokenType.Delim) {
+ onParseError(genericError('Layer names can not start with a dot.'));
+ return [];
+ }
+ }
+
+ if (inLayerNameSequence) {
+ if (token[0] === TokenType.Whitespace) {
+ sawWhiteSpaceAfterIdent = true;
+ continue;
+ }
+
+ if (sawWhiteSpaceAfterIdent && token[0] === TokenType.Comment) {
+ continue;
+ }
+
+ if (sawWhiteSpaceAfterIdent) {
+ onParseError(genericError('Encountered unexpected whitespace between layer name parts.'));
+ return [];
+ }
+
+ if (lastToken[0] === TokenType.Ident) {
+ if (token[0] === TokenType.Ident) {
+ onParseError(genericError('Layer name parts must be separated by dots.'));
+ return [];
+ }
+ }
+
+ if (lastToken[0] === TokenType.Delim) {
+ if (token[0] === TokenType.Delim) {
+ onParseError(genericError('Layer name parts must not be empty.'));
+ return [];
+ }
+ }
+ }
+
+ if (token[0] === TokenType.Ident) {
+ inLayerNameSequence = true;
+ }
+
+ if (token[0] === TokenType.Ident || token[0] === TokenType.Delim) {
+ lastToken = token;
+ }
+ }
+
+ if (!lastToken) {
+ onParseError(genericError('Empty layer name.'));
+ return [];
+ }
+
+ if (lastToken[0] === TokenType.Delim) {
+ onParseError(genericError('Layer name must not end with a dot.'));
+ return [];
+ }
+
+ result.push(new LayerName(componentValueTokens));
+ }
+
+ return result;
+}
+
+export function parse(source: string, options?: Options) {
+ const t = tokenizer({ css: source }, {
+ commentsAreTokens: true,
+ onParseError: options?.onParseError,
+ });
+
+ const tokens = [];
+
+ {
+ while (!t.endOfFile()) {
+ tokens.push(t.nextToken());
+ }
+
+ tokens.push(t.nextToken()); // EOF-token
+ }
+
+ return parseFromTokens(tokens, options);
+}
diff --git a/packages/cascade-layer-name-parser/src/util/model.ts b/packages/cascade-layer-name-parser/src/util/model.ts
new file mode 100644
index 000000000..e9bc966f9
--- /dev/null
+++ b/packages/cascade-layer-name-parser/src/util/model.ts
@@ -0,0 +1,66 @@
+import { LayerName } from '../nodes/layer-name';
+
+// Insert new items after the most similar current item
+//
+// [["a", "b"]]
+// insert "a.first"
+// [["a", "a.first", "b"]]
+//
+// [["a", "a.first", "a.second", "b"]]
+// insert "a.first.foo"
+// [["a", "a.first", "a.first.foo", "a.second", "b"]]
+//
+// [["a", "b"]]
+// insert "c"
+
+// [["a", "b", "c"]]
+export function addLayerToModel(layers: Array, currentLayerNames: Array) {
+ currentLayerNames.forEach((layerName) => {
+ const allLayerNameParts = layerName.segments();
+
+ ALL_LAYER_NAME_PARTS_LOOP: for (let x = 0; x < allLayerNameParts.length; x++) {
+ const layerNameSlice = layerName.slice(0, x + 1);
+ const layerNameParts = layerNameSlice.segments();
+
+ let layerWithMostEqualSegments = -1;
+ let mostEqualSegments = 0;
+
+ for (let i = 0; i < layers.length; i++) {
+ const existingLayerParts = layers[i].segments();
+
+ let numberOfEqualSegments = 0;
+
+ LAYER_PARTS_LOOP: for (let j = 0; j < existingLayerParts.length; j++) {
+ const existingLayerPart = existingLayerParts[j];
+ const layerPart = layerNameParts[j];
+
+ if (layerPart === existingLayerPart && (j + 1) === layerNameParts.length) {
+ continue ALL_LAYER_NAME_PARTS_LOOP; // layer already exists in model
+ }
+
+ if (layerPart === existingLayerPart) {
+ numberOfEqualSegments++;
+ continue;
+ }
+
+ if (layerPart !== existingLayerPart) {
+ break LAYER_PARTS_LOOP;
+ }
+ }
+
+ if (numberOfEqualSegments >= mostEqualSegments) {
+ layerWithMostEqualSegments = i;
+ mostEqualSegments = numberOfEqualSegments;
+ }
+ }
+
+ if (layerWithMostEqualSegments === -1) {
+ layers.push(layerNameSlice);
+ } else {
+ layers.splice(layerWithMostEqualSegments + 1, 0, layerNameSlice);
+ }
+ }
+ });
+
+ return layers;
+}
diff --git a/packages/cascade-layer-name-parser/stryker.conf.json b/packages/cascade-layer-name-parser/stryker.conf.json
new file mode 100644
index 000000000..015ebbb73
--- /dev/null
+++ b/packages/cascade-layer-name-parser/stryker.conf.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "../../node_modules/@stryker-mutator/core/schema/stryker-schema.json",
+ "mutate": [
+ "src/**/*.ts"
+ ],
+ "buildCommand": "npm run build",
+ "testRunner": "command",
+ "coverageAnalysis": "perTest",
+ "tempDirName": "../../.stryker-tmp",
+ "commandRunner": {
+ "command": "npm run test"
+ },
+ "thresholds": {
+ "high": 100,
+ "low": 100,
+ "break": 100
+ },
+ "inPlace": true
+}
diff --git a/packages/cascade-layer-name-parser/test/_import.mjs b/packages/cascade-layer-name-parser/test/_import.mjs
new file mode 100644
index 000000000..8929e4112
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/_import.mjs
@@ -0,0 +1,3 @@
+import { parse } from '@csstools/cascade-layer-name-parser';
+
+parse('layer-name, foo.bar');
diff --git a/packages/cascade-layer-name-parser/test/_require.cjs b/packages/cascade-layer-name-parser/test/_require.cjs
new file mode 100644
index 000000000..226da4fdf
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/_require.cjs
@@ -0,0 +1,3 @@
+const { parse } = require('@csstools/cascade-layer-name-parser');
+
+parse('layer-name, foo.bar');
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0001.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0001.expect.json
new file mode 100644
index 000000000..23eb516a9
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0001.expect.json
@@ -0,0 +1,19 @@
+[
+ {
+ "parts": [
+ [
+ "ident-token",
+ "layer-name",
+ 0,
+ 9,
+ {
+ "value": "layer-name"
+ }
+ ]
+ ],
+ "segments": [
+ "layer-name"
+ ],
+ "name": "layer-name"
+ }
+]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0001.mjs b/packages/cascade-layer-name-parser/test/cases/various/0001.mjs
new file mode 100644
index 000000000..32a008cc8
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0001.mjs
@@ -0,0 +1,13 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'layer-name',
+ 'various/0001',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0002.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0002.expect.json
new file mode 100644
index 000000000..f0d4b3384
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0002.expect.json
@@ -0,0 +1,38 @@
+[
+ {
+ "parts": [
+ [
+ "ident-token",
+ "layer-name",
+ 0,
+ 9,
+ {
+ "value": "layer-name"
+ }
+ ],
+ [
+ "delim-token",
+ ".",
+ 10,
+ 10,
+ {
+ "value": "."
+ }
+ ],
+ [
+ "ident-token",
+ "sub-part",
+ 11,
+ 18,
+ {
+ "value": "sub-part"
+ }
+ ]
+ ],
+ "segments": [
+ "layer-name",
+ "sub-part"
+ ],
+ "name": "layer-name.sub-part"
+ }
+]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0002.mjs b/packages/cascade-layer-name-parser/test/cases/various/0002.mjs
new file mode 100644
index 000000000..a649e0dd6
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0002.mjs
@@ -0,0 +1,13 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'layer-name.sub-part',
+ 'various/0002',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0003.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0003.expect.json
new file mode 100644
index 000000000..5bccd10b3
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0003.expect.json
@@ -0,0 +1,93 @@
+[
+ {
+ "parts": [
+ [
+ "ident-token",
+ "abc",
+ 0,
+ 2,
+ {
+ "value": "abc"
+ }
+ ],
+ [
+ "delim-token",
+ ".",
+ 3,
+ 3,
+ {
+ "value": "."
+ }
+ ],
+ [
+ "ident-token",
+ "def",
+ 4,
+ 6,
+ {
+ "value": "def"
+ }
+ ]
+ ],
+ "segments": [
+ "abc",
+ "def"
+ ],
+ "name": "abc.def"
+ },
+ {
+ "parts": [
+ [
+ "ident-token",
+ "ghi",
+ 8,
+ 10,
+ {
+ "value": "ghi"
+ }
+ ],
+ [
+ "delim-token",
+ ".",
+ 11,
+ 11,
+ {
+ "value": "."
+ }
+ ],
+ [
+ "ident-token",
+ "jkl",
+ 12,
+ 14,
+ {
+ "value": "jkl"
+ }
+ ],
+ [
+ "delim-token",
+ ".",
+ 15,
+ 15,
+ {
+ "value": "."
+ }
+ ],
+ [
+ "ident-token",
+ "mno",
+ 16,
+ 18,
+ {
+ "value": "mno"
+ }
+ ]
+ ],
+ "segments": [
+ "ghi",
+ "jkl",
+ "mno"
+ ],
+ "name": "ghi.jkl.mno"
+ }
+]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0003.mjs b/packages/cascade-layer-name-parser/test/cases/various/0003.mjs
new file mode 100644
index 000000000..259bf1008
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0003.mjs
@@ -0,0 +1,13 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'abc.def,ghi.jkl.mno',
+ 'various/0003',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0004.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0004.expect.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0004.expect.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0004.mjs b/packages/cascade-layer-name-parser/test/cases/various/0004.mjs
new file mode 100644
index 000000000..8d01c2486
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0004.mjs
@@ -0,0 +1,14 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'ab/* a comment */c.def,ghi.jkl.mno',
+ 'various/0004',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+ false,
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0005.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0005.expect.json
new file mode 100644
index 000000000..563107eac
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0005.expect.json
@@ -0,0 +1,156 @@
+[
+ {
+ "parts": [
+ [
+ "comment",
+ "/* a comment */",
+ 0,
+ 14,
+ null
+ ],
+ [
+ "ident-token",
+ "abc",
+ 15,
+ 17,
+ {
+ "value": "abc"
+ }
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 18,
+ 32,
+ null
+ ],
+ [
+ "delim-token",
+ ".",
+ 33,
+ 33,
+ {
+ "value": "."
+ }
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 34,
+ 48,
+ null
+ ],
+ [
+ "ident-token",
+ "def",
+ 49,
+ 51,
+ {
+ "value": "def"
+ }
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 52,
+ 66,
+ null
+ ]
+ ],
+ "segments": [
+ "abc",
+ "def"
+ ],
+ "name": "abc.def"
+ },
+ {
+ "parts": [
+ [
+ "comment",
+ "/* a comment */",
+ 68,
+ 82,
+ null
+ ],
+ [
+ "ident-token",
+ "ghi",
+ 83,
+ 85,
+ {
+ "value": "ghi"
+ }
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 86,
+ 100,
+ null
+ ],
+ [
+ "delim-token",
+ ".",
+ 101,
+ 101,
+ {
+ "value": "."
+ }
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 102,
+ 116,
+ null
+ ],
+ [
+ "ident-token",
+ "jkl",
+ 117,
+ 119,
+ {
+ "value": "jkl"
+ }
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 120,
+ 134,
+ null
+ ],
+ [
+ "delim-token",
+ ".",
+ 135,
+ 135,
+ {
+ "value": "."
+ }
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 136,
+ 150,
+ null
+ ],
+ [
+ "ident-token",
+ "mno",
+ 151,
+ 153,
+ {
+ "value": "mno"
+ }
+ ]
+ ],
+ "segments": [
+ "ghi",
+ "jkl",
+ "mno"
+ ],
+ "name": "ghi.jkl.mno"
+ }
+]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0005.mjs b/packages/cascade-layer-name-parser/test/cases/various/0005.mjs
new file mode 100644
index 000000000..6e77c95da
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0005.mjs
@@ -0,0 +1,13 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ '/* a comment */abc/* a comment */./* a comment */def/* a comment */,/* a comment */ghi/* a comment */./* a comment */jkl/* a comment */./* a comment */mno',
+ 'various/0005',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0006.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0006.expect.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0006.expect.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0006.mjs b/packages/cascade-layer-name-parser/test/cases/various/0006.mjs
new file mode 100644
index 000000000..138d6056e
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0006.mjs
@@ -0,0 +1,14 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'layer..part',
+ 'various/0006',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+ false,
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0007.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0007.expect.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0007.expect.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0007.mjs b/packages/cascade-layer-name-parser/test/cases/various/0007.mjs
new file mode 100644
index 000000000..f869ff134
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0007.mjs
@@ -0,0 +1,14 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'layer . part',
+ 'various/0007',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+ false,
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0008.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0008.expect.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0008.expect.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0008.mjs b/packages/cascade-layer-name-parser/test/cases/various/0008.mjs
new file mode 100644
index 000000000..3970b75cc
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0008.mjs
@@ -0,0 +1,14 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'layer part',
+ 'various/0008',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+ false,
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0009.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0009.expect.json
new file mode 100644
index 000000000..95e50fc30
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0009.expect.json
@@ -0,0 +1,38 @@
+[
+ {
+ "parts": [
+ [
+ "ident-token",
+ "l\\61 yer",
+ 0,
+ 7,
+ {
+ "value": "layer"
+ }
+ ],
+ [
+ "delim-token",
+ ".",
+ 8,
+ 8,
+ {
+ "value": "."
+ }
+ ],
+ [
+ "ident-token",
+ "part",
+ 9,
+ 12,
+ {
+ "value": "part"
+ }
+ ]
+ ],
+ "segments": [
+ "layer",
+ "part"
+ ],
+ "name": "l\\61 yer.part"
+ }
+]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0009.mjs b/packages/cascade-layer-name-parser/test/cases/various/0009.mjs
new file mode 100644
index 000000000..586990df4
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0009.mjs
@@ -0,0 +1,13 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'l\\61 yer.part',
+ 'various/0009',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0010.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0010.expect.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0010.expect.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0010.mjs b/packages/cascade-layer-name-parser/test/cases/various/0010.mjs
new file mode 100644
index 000000000..e7d15d7fe
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0010.mjs
@@ -0,0 +1,14 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'layer, (not-a-layer), sub.layer',
+ 'various/0010',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+ false,
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0011.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0011.expect.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0011.expect.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0011.mjs b/packages/cascade-layer-name-parser/test/cases/various/0011.mjs
new file mode 100644
index 000000000..f88b0a7b9
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0011.mjs
@@ -0,0 +1,14 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'layer, calc(not-a-layer), sub.layer',
+ 'various/0011',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+ false,
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0012.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0012.expect.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0012.expect.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0012.mjs b/packages/cascade-layer-name-parser/test/cases/various/0012.mjs
new file mode 100644
index 000000000..7b3b0e00a
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0012.mjs
@@ -0,0 +1,14 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'layer, sub/layer , sub.layer',
+ 'various/0012',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+ false,
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0013.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0013.expect.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0013.expect.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0013.mjs b/packages/cascade-layer-name-parser/test/cases/various/0013.mjs
new file mode 100644
index 000000000..6097361c6
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0013.mjs
@@ -0,0 +1,14 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'layer, sub+layer , sub.layer',
+ 'various/0013',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+ false,
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0014.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0014.expect.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0014.expect.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0014.mjs b/packages/cascade-layer-name-parser/test/cases/various/0014.mjs
new file mode 100644
index 000000000..6aa53cf73
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0014.mjs
@@ -0,0 +1,14 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'layer., other-layer',
+ 'various/0014',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+ false,
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0015.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0015.expect.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0015.expect.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0015.mjs b/packages/cascade-layer-name-parser/test/cases/various/0015.mjs
new file mode 100644
index 000000000..c726c33a7
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0015.mjs
@@ -0,0 +1,14 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'layer,,other-layer',
+ 'various/0015',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+ false,
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0016.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0016.expect.json
new file mode 100644
index 000000000..5a8e9362b
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0016.expect.json
@@ -0,0 +1,88 @@
+[
+ {
+ "parts": [
+ [
+ "whitespace-token",
+ " \t ",
+ 0,
+ 2,
+ null
+ ],
+ [
+ "ident-token",
+ "layer",
+ 3,
+ 7,
+ {
+ "value": "layer"
+ }
+ ]
+ ],
+ "segments": [
+ "layer"
+ ],
+ "name": "layer"
+ },
+ {
+ "parts": [
+ [
+ "whitespace-token",
+ " \t ",
+ 9,
+ 11,
+ null
+ ],
+ [
+ "ident-token",
+ "other-layer",
+ 12,
+ 22,
+ {
+ "value": "other-layer"
+ }
+ ],
+ [
+ "whitespace-token",
+ " \t ",
+ 23,
+ 25,
+ null
+ ]
+ ],
+ "segments": [
+ "other-layer"
+ ],
+ "name": "other-layer"
+ },
+ {
+ "parts": [
+ [
+ "whitespace-token",
+ "\n\n",
+ 27,
+ 28,
+ null
+ ],
+ [
+ "ident-token",
+ "last-layer",
+ 29,
+ 38,
+ {
+ "value": "last-layer"
+ }
+ ],
+ [
+ "whitespace-token",
+ "\n\n",
+ 39,
+ 40,
+ null
+ ]
+ ],
+ "segments": [
+ "last-layer"
+ ],
+ "name": "last-layer"
+ }
+]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0016.mjs b/packages/cascade-layer-name-parser/test/cases/various/0016.mjs
new file mode 100644
index 000000000..05baab87e
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0016.mjs
@@ -0,0 +1,17 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ ` layer, other-layer ,
+
+last-layer
+
+`,
+ 'various/0016',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0017.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0017.expect.json
new file mode 100644
index 000000000..d98f090f8
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0017.expect.json
@@ -0,0 +1,55 @@
+[
+ {
+ "parts": [
+ [
+ "ident-token",
+ "lay\\,er",
+ 0,
+ 6,
+ {
+ "value": "lay,er"
+ }
+ ]
+ ],
+ "segments": [
+ "lay,er"
+ ],
+ "name": "lay\\,er"
+ },
+ {
+ "parts": [
+ [
+ "ident-token",
+ "other-la\\.yer",
+ 8,
+ 20,
+ {
+ "value": "other-la.yer"
+ }
+ ],
+ [
+ "delim-token",
+ ".",
+ 21,
+ 21,
+ {
+ "value": "."
+ }
+ ],
+ [
+ "ident-token",
+ "sub-layer",
+ 22,
+ 30,
+ {
+ "value": "sub-layer"
+ }
+ ]
+ ],
+ "segments": [
+ "other-la.yer",
+ "sub-layer"
+ ],
+ "name": "other-la\\.yer.sub-layer"
+ }
+]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0017.mjs b/packages/cascade-layer-name-parser/test/cases/various/0017.mjs
new file mode 100644
index 000000000..ecca237e8
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0017.mjs
@@ -0,0 +1,13 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'lay\\,er,other-la\\.yer.sub-layer',
+ 'various/0017',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+);
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0018.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0018.expect.json
new file mode 100644
index 000000000..69662f344
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0018.expect.json
@@ -0,0 +1,205 @@
+[
+ {
+ "parts": [
+ [
+ "whitespace-token",
+ " ",
+ 0,
+ 1,
+ null
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 2,
+ 16,
+ null
+ ],
+ [
+ "whitespace-token",
+ " ",
+ 17,
+ 18,
+ null
+ ],
+ [
+ "ident-token",
+ "abc",
+ 19,
+ 21,
+ {
+ "value": "abc"
+ }
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 22,
+ 36,
+ null
+ ],
+ [
+ "delim-token",
+ ".",
+ 37,
+ 37,
+ {
+ "value": "."
+ }
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 38,
+ 52,
+ null
+ ],
+ [
+ "ident-token",
+ "def",
+ 53,
+ 55,
+ {
+ "value": "def"
+ }
+ ],
+ [
+ "whitespace-token",
+ " ",
+ 56,
+ 57,
+ null
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 58,
+ 72,
+ null
+ ],
+ [
+ "whitespace-token",
+ " ",
+ 73,
+ 74,
+ null
+ ]
+ ],
+ "segments": [
+ "abc",
+ "def"
+ ],
+ "name": "abc.def"
+ },
+ {
+ "parts": [
+ [
+ "whitespace-token",
+ " ",
+ 76,
+ 77,
+ null
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 78,
+ 92,
+ null
+ ],
+ [
+ "whitespace-token",
+ " ",
+ 93,
+ 94,
+ null
+ ],
+ [
+ "ident-token",
+ "ghi",
+ 95,
+ 97,
+ {
+ "value": "ghi"
+ }
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 98,
+ 112,
+ null
+ ],
+ [
+ "delim-token",
+ ".",
+ 113,
+ 113,
+ {
+ "value": "."
+ }
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 114,
+ 128,
+ null
+ ],
+ [
+ "ident-token",
+ "jkl",
+ 129,
+ 131,
+ {
+ "value": "jkl"
+ }
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 132,
+ 146,
+ null
+ ],
+ [
+ "delim-token",
+ ".",
+ 147,
+ 147,
+ {
+ "value": "."
+ }
+ ],
+ [
+ "comment",
+ "/* a comment */",
+ 148,
+ 162,
+ null
+ ],
+ [
+ "ident-token",
+ "mno",
+ 163,
+ 165,
+ {
+ "value": "mno"
+ }
+ ],
+ [
+ "whitespace-token",
+ " ",
+ 166,
+ 167,
+ null
+ ]
+ ],
+ "segments": [
+ "ghi",
+ "jkl",
+ "mno"
+ ],
+ "name": "ghi.jkl.mno"
+ }
+]
\ No newline at end of file
diff --git a/packages/cascade-layer-name-parser/test/cases/various/0018.mjs b/packages/cascade-layer-name-parser/test/cases/various/0018.mjs
new file mode 100644
index 000000000..e7dc77b39
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/cases/various/0018.mjs
@@ -0,0 +1,13 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ ' /* a comment */ abc/* a comment */./* a comment */def /* a comment */ , /* a comment */ ghi/* a comment */./* a comment */jkl/* a comment */./* a comment */mno ',
+ 'various/0018',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+);
diff --git a/packages/cascade-layer-name-parser/test/concat.mjs b/packages/cascade-layer-name-parser/test/concat.mjs
new file mode 100644
index 000000000..fb25e4016
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/concat.mjs
@@ -0,0 +1,51 @@
+import { parse } from '@csstools/cascade-layer-name-parser';
+import assert from 'assert';
+
+{
+ const a = parse(' some-layer./* a comment */sub-layer ')[0];
+ const b = parse(' another-part /* trailing comment */')[0];
+
+ assert.equal(a.concat(b).toString(), 'some-layer.sub-layer.another-part');
+ assert.deepEqual(a.concat(b).toJSON(), {
+ parts: [
+ ['ident-token', 'some-layer', 1, 10, { value: 'some-layer' }],
+ ['delim-token', '.', 11, 11, { value: '.' }],
+ ['ident-token', 'sub-layer', 27, 35, { value: 'sub-layer' }],
+ ['delim-token', '.', -1, -1, { value: '.' }],
+ [
+ 'ident-token',
+ 'another-part',
+ 1,
+ 12,
+ { value: 'another-part' },
+ ],
+ ],
+ segments: ['some-layer', 'sub-layer', 'another-part'],
+ name: 'some-layer.sub-layer.another-part',
+ });
+}
+
+{
+ const a = parse(' another-part /* trailing comment */')[0];
+ const b = parse(' some-layer./* a comment */sub-layer ')[0];
+
+ assert.equal(a.concat(b).toString(), 'another-part.some-layer.sub-layer');
+ assert.deepEqual(a.concat(b).toJSON(), {
+ parts: [
+ [
+ 'ident-token',
+ 'another-part',
+ 1,
+ 12,
+ { value: 'another-part' },
+ ],
+ ['delim-token', '.', -1, -1, { value: '.' }],
+ ['ident-token', 'some-layer', 1, 10, { value: 'some-layer' }],
+ ['delim-token', '.', 11, 11, { value: '.' }],
+ ['ident-token', 'sub-layer', 27, 35, { value: 'sub-layer' }],
+ ],
+ segments: ['another-part', 'some-layer', 'sub-layer'],
+ name: 'another-part.some-layer.sub-layer',
+ });
+}
+
diff --git a/packages/cascade-layer-name-parser/test/equal.mjs b/packages/cascade-layer-name-parser/test/equal.mjs
new file mode 100644
index 000000000..c89ea8388
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/equal.mjs
@@ -0,0 +1,26 @@
+import { parse } from '@csstools/cascade-layer-name-parser';
+import assert from 'assert';
+
+{
+ const layerNames = [
+ ...parse('alpha'),
+ ...parse('beta'),
+ ...parse('alpha.one'),
+ ...parse('alpha.two.other2'),
+ ...parse('alpha.two'),
+ ...parse('alpha.one.other1'),
+ ...parse('delta'),
+ ...parse('gamma.sub-layer.foo'),
+ ];
+
+ for (let i = 0; i < layerNames.length; i++) {
+ const ii = layerNames[i];
+
+
+ for (let j = 0; j < layerNames.length; j++) {
+ const jj = layerNames[j];
+
+ assert.equal(ii.equal(jj), ii === jj);
+ }
+ }
+}
diff --git a/packages/cascade-layer-name-parser/test/model.mjs b/packages/cascade-layer-name-parser/test/model.mjs
new file mode 100644
index 000000000..2901caf3d
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/model.mjs
@@ -0,0 +1,35 @@
+import { addLayerToModel, parse } from '@csstools/cascade-layer-name-parser';
+import assert from 'assert';
+
+{
+ const model = [];
+
+ addLayerToModel(model, parse('alpha'));
+ addLayerToModel(model, parse('beta'));
+
+ addLayerToModel(model, parse('alpha.one'));
+ addLayerToModel(model, parse('alpha.two.other2'));
+ addLayerToModel(model, parse('alpha.two'));
+ addLayerToModel(model, parse('alpha.one.other1'));
+
+ addLayerToModel(model, parse('delta'));
+ addLayerToModel(model, parse('alpha'));
+
+ addLayerToModel(model, parse('gamma.sub-layer.foo'));
+
+ assert.deepEqual(
+ model.map(x => x.toString()),
+ [
+ 'alpha',
+ 'alpha.one',
+ 'alpha.one.other1',
+ 'alpha.two',
+ 'alpha.two.other2',
+ 'beta',
+ 'delta',
+ 'gamma',
+ 'gamma.sub-layer',
+ 'gamma.sub-layer.foo',
+ ],
+ );
+}
diff --git a/packages/cascade-layer-name-parser/test/slice.mjs b/packages/cascade-layer-name-parser/test/slice.mjs
new file mode 100644
index 000000000..9d6697957
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/slice.mjs
@@ -0,0 +1,19 @@
+import { parse } from '@csstools/cascade-layer-name-parser';
+import assert from 'assert';
+
+{
+ const layerName = parse('alpha./* a comment */beta.gamma.delta')[0];
+ assert.equal(layerName.toString(), 'alpha./* a comment */beta.gamma.delta');
+
+ const a = layerName.slice(1);
+ assert.equal(a.toString(), 'beta.gamma.delta');
+
+ const b = layerName.slice(-1);
+ assert.equal(b.toString(), 'delta');
+
+ const c = layerName.slice(0, -1);
+ assert.equal(c.toString(), 'alpha./* a comment */beta.gamma');
+
+ const d = layerName.slice(1, 2);
+ assert.equal(d.toString(), 'beta');
+}
diff --git a/packages/cascade-layer-name-parser/test/test.mjs b/packages/cascade-layer-name-parser/test/test.mjs
new file mode 100644
index 000000000..590f4ebe6
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/test.mjs
@@ -0,0 +1,24 @@
+import './cases/various/0001.mjs';
+import './cases/various/0002.mjs';
+import './cases/various/0003.mjs';
+import './cases/various/0004.mjs';
+import './cases/various/0005.mjs';
+import './cases/various/0006.mjs';
+import './cases/various/0007.mjs';
+import './cases/various/0008.mjs';
+import './cases/various/0009.mjs';
+import './cases/various/0010.mjs';
+import './cases/various/0011.mjs';
+import './cases/various/0012.mjs';
+import './cases/various/0013.mjs';
+import './cases/various/0014.mjs';
+import './cases/various/0015.mjs';
+import './cases/various/0016.mjs';
+import './cases/various/0017.mjs';
+import './cases/various/0018.mjs';
+
+import './concat.mjs';
+import './equal.mjs';
+import './model.mjs';
+import './slice.mjs';
+import './tokens.mjs';
diff --git a/packages/cascade-layer-name-parser/test/tokens.mjs b/packages/cascade-layer-name-parser/test/tokens.mjs
new file mode 100644
index 000000000..d99bf2273
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/tokens.mjs
@@ -0,0 +1,29 @@
+import { parse } from '@csstools/cascade-layer-name-parser';
+import assert from 'assert';
+
+// The "tokens" function exists for API parity with other parser tools.
+// It's value is the same as "parts".
+function tokensAndPartsAreEqual(layerNames) {
+ for (let i = 0; i < layerNames.length; i++) {
+ const layerName = layerNames[i];
+ assert.deepEqual(
+ layerName.tokens(),
+ layerName.parts,
+ );
+ }
+}
+
+{
+ tokensAndPartsAreEqual(parse('alpha'));
+ tokensAndPartsAreEqual(parse('beta'));
+
+ tokensAndPartsAreEqual(parse('alpha.one'));
+ tokensAndPartsAreEqual(parse('alpha.two.other2'));
+ tokensAndPartsAreEqual(parse('alpha.two'));
+ tokensAndPartsAreEqual(parse('alpha.one.other1'));
+
+ tokensAndPartsAreEqual(parse('delta'));
+ tokensAndPartsAreEqual(parse('alpha'));
+
+ tokensAndPartsAreEqual(parse('gamma.sub-layer.foo'));
+}
diff --git a/packages/cascade-layer-name-parser/test/util/run-test.mjs b/packages/cascade-layer-name-parser/test/util/run-test.mjs
new file mode 100644
index 000000000..fc45c7a3d
--- /dev/null
+++ b/packages/cascade-layer-name-parser/test/util/run-test.mjs
@@ -0,0 +1,34 @@
+import fs from 'fs';
+import path from 'path';
+import { parse } from '@csstools/cascade-layer-name-parser';
+
+export function runTest(source, testPath, assertEqual, expectSuccess = true) {
+ let err;
+ const resultAST = parse(source, {
+ onParseError: (parseError) => {
+ err = parseError;
+ },
+ });
+
+ const resultAST_JSON = JSON.stringify(resultAST, null, '\t');
+
+ if (process.env['REWRITE_EXPECTS'] === 'true') {
+ fs.writeFileSync(path.join(process.cwd(), `./test/cases/${testPath}.expect.json`), resultAST_JSON);
+ fs.writeFileSync(path.join(process.cwd(), `./test/cases/${testPath}.result.json`), resultAST_JSON);
+ } else {
+ if (expectSuccess) {
+ if (err) {
+ throw new Error(JSON.stringify(err));
+ }
+ } else {
+ assertEqual(!!err, true);
+ }
+
+ const expectData = JSON.parse(fs.readFileSync(path.join(process.cwd(), `./test/cases/${testPath}.expect.json`)).toString());
+
+ assertEqual(
+ JSON.parse(resultAST_JSON),
+ expectData,
+ );
+ }
+}
diff --git a/packages/cascade-layer-name-parser/tsconfig.json b/packages/cascade-layer-name-parser/tsconfig.json
new file mode 100644
index 000000000..e0d06239c
--- /dev/null
+++ b/packages/cascade-layer-name-parser/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "declarationDir": "."
+ },
+ "include": ["./src/**/*"],
+ "exclude": ["dist"],
+}
diff --git a/packages/css-parser-algorithms/CHANGELOG.md b/packages/css-parser-algorithms/CHANGELOG.md
index cd7fc18e7..6e25f75aa 100644
--- a/packages/css-parser-algorithms/CHANGELOG.md
+++ b/packages/css-parser-algorithms/CHANGELOG.md
@@ -1,3 +1,7 @@
+### Unreleased
+
+- Fix: Do not discard empty items in comma separated lists.
+
### 1.0.0 (November 14, 2022)
- Initial version
diff --git a/packages/css-parser-algorithms/dist/index.cjs b/packages/css-parser-algorithms/dist/index.cjs
index 91788ca8d..2ad9b2515 100644
--- a/packages/css-parser-algorithms/dist/index.cjs
+++ b/packages/css-parser-algorithms/dist/index.cjs
@@ -1 +1 @@
-"use strict";var e,n=require("@csstools/css-tokenizer");function consumeComponentValue(e,o){const t=o[0];if(t[0]===n.TokenType.OpenParen||t[0]===n.TokenType.OpenCurly||t[0]===n.TokenType.OpenSquare){const n=consumeSimpleBlock(e,o);return{advance:n.advance,node:n.node}}if(t[0]===n.TokenType.Function){const n=consumeFunction(e,o);return{advance:n.advance,node:n.node}}if(t[0]===n.TokenType.Whitespace){const n=consumeWhitespace(e,o);return{advance:n.advance,node:n.node}}if(t[0]===n.TokenType.Comment){const n=consumeComment(e,o);return{advance:n.advance,node:n.node}}return{advance:1,node:new TokenNode(t)}}exports.ComponentValueType=void 0,(e=exports.ComponentValueType||(exports.ComponentValueType={})).Function="function",e.SimpleBlock="simple-block",e.Whitespace="whitespace",e.Comment="comment",e.Token="token",e.UnclosedFunction="unclosed-function",e.UnclosedSimpleBlock="unclosed-simple-block";class FunctionNode{type=exports.ComponentValueType.Function;name;endToken;value;constructor(e,n,o){this.name=e,this.endToken=n,this.value=o}nameTokenValue(){return this.name[4].value}tokens(){return[this.name,...this.value.flatMap((e=>n.isToken(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n.isToken(e)?n.stringify(e):e.toString())).join("");return n.stringify(this.name)+e+n.stringify(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,name:this.nameTokenValue(),tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isFunctionNode(){return FunctionNode.isFunctionNode(this)}static isFunctionNode(e){return!!e&&(e instanceof FunctionNode&&e.type===exports.ComponentValueType.Function)}}function consumeFunction(e,o){const t=[];let s=1;for(;;){const i=o[s];if(!i||i[0]===n.TokenType.EOF)return e.onParseError({message:"Unexpected EOF while consuming a function.",start:o[0][2],end:o[o.length-1][3],state:["5.4.9. Consume a function","Unexpected EOF"]}),{advance:o.length,node:new UnclosedFunctionNode(o)};if(i[0]===n.TokenType.CloseParen)return{advance:s+1,node:new FunctionNode(o[0],i,t)};if(i[0]===n.TokenType.Comment||i[0]===n.TokenType.Whitespace){const n=consumeAllCommentsAndWhitespace(e,o.slice(s));s+=n.advance,t.push(...n.nodes);continue}const c=consumeComponentValue(e,o.slice(s));s+=c.advance,t.push(c.node)}}class SimpleBlockNode{type=exports.ComponentValueType.SimpleBlock;startToken;endToken;value;constructor(e,n,o){this.startToken=e,this.endToken=n,this.value=o}tokens(){return[this.startToken,...this.value.flatMap((e=>n.isToken(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n.isToken(e)?n.stringify(e):e.toString())).join("");return n.stringify(this.startToken)+e+n.stringify(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,startToken:this.startToken,tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isSimpleBlockNode(){return SimpleBlockNode.isSimpleBlockNode(this)}static isSimpleBlockNode(e){return!!e&&(e instanceof SimpleBlockNode&&e.type===exports.ComponentValueType.SimpleBlock)}}function consumeSimpleBlock(e,o){const t=n.mirrorVariantType(o[0][0]);if(!t)throw new Error("Failed to parse, a mirror variant must exist for all block open tokens.");const s=[];let i=1;for(;;){const c=o[i];if(!c||c[0]===n.TokenType.EOF)return e.onParseError({message:"Unexpected EOF while consuming a simple block.",start:o[0][2],end:o[o.length-1][3],state:["5.4.8. Consume a simple block","Unexpected EOF"]}),{advance:o.length,node:new UnclosedSimpleBlockNode(o)};if(c[0]===t)return{advance:i+1,node:new SimpleBlockNode(o[0],c,s)};if(c[0]===n.TokenType.Comment||c[0]===n.TokenType.Whitespace){const n=consumeAllCommentsAndWhitespace(e,o.slice(i));i+=n.advance,s.push(...n.nodes);continue}const r=consumeComponentValue(e,o.slice(i));i+=r.advance,s.push(r.node)}}class WhitespaceNode{type=exports.ComponentValueType.Whitespace;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return n.stringify(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isWhitespaceNode(){return WhitespaceNode.isWhitespaceNode(this)}static isWhitespaceNode(e){return!!e&&(e instanceof WhitespaceNode&&e.type===exports.ComponentValueType.Whitespace)}}function consumeWhitespace(e,o){let t=0;for(;;){if(o[t][0]!==n.TokenType.Whitespace)return{advance:t,node:new WhitespaceNode(o.slice(0,t))};t++}}class CommentNode{type=exports.ComponentValueType.Comment;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return n.stringify(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isCommentNode(){return CommentNode.isCommentNode(this)}static isCommentNode(e){return!!e&&(e instanceof CommentNode&&e.type===exports.ComponentValueType.Comment)}}function consumeComment(e,n){return{advance:1,node:new CommentNode(n[0])}}function consumeAllCommentsAndWhitespace(e,o){const t=[];let s=0;for(;;)if(o[s][0]!==n.TokenType.Whitespace){if(o[s][0]!==n.TokenType.Comment)return{advance:s,nodes:t};t.push(new CommentNode(o[s])),s++}else{const e=consumeWhitespace(0,o.slice(s));s+=e.advance,t.push(e.node)}}class TokenNode{type=exports.ComponentValueType.Token;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return n.stringify(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isTokenNode(){return TokenNode.isTokenNode(this)}static isTokenNode(e){return!!e&&(e instanceof TokenNode&&e.type===exports.ComponentValueType.Token)}}class UnclosedFunctionNode{type=exports.ComponentValueType.UnclosedFunction;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return n.stringify(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedFunctionNode(){return UnclosedFunctionNode.isUnclosedFunctionNode(this)}static isUnclosedFunctionNode(e){return!!e&&(e instanceof UnclosedFunctionNode&&e.type===exports.ComponentValueType.UnclosedFunction)}}class UnclosedSimpleBlockNode{type=exports.ComponentValueType.UnclosedSimpleBlock;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return n.stringify(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedSimpleBlockNode(){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(this)}static isUnclosedSimpleBlockNode(e){return!!e&&(e instanceof UnclosedSimpleBlockNode&&e.type===exports.ComponentValueType.UnclosedSimpleBlock)}}exports.CommentNode=CommentNode,exports.FunctionNode=FunctionNode,exports.SimpleBlockNode=SimpleBlockNode,exports.TokenNode=TokenNode,exports.UnclosedFunctionNode=UnclosedFunctionNode,exports.UnclosedSimpleBlockNode=UnclosedSimpleBlockNode,exports.WhitespaceNode=WhitespaceNode,exports.consumeAllCommentsAndWhitespace=consumeAllCommentsAndWhitespace,exports.consumeComment=consumeComment,exports.consumeComponentValue=consumeComponentValue,exports.consumeFunction=consumeFunction,exports.consumeSimpleBlock=consumeSimpleBlock,exports.consumeWhitespace=consumeWhitespace,exports.gatherNodeAncestry=function gatherNodeAncestry(e){const n=new Map;return e.walk((e=>{Array.isArray(e.node)?e.node.forEach((o=>{n.set(o,e.parent)})):n.set(e.node,e.parent)})),n},exports.isCommentNode=function isCommentNode(e){return CommentNode.isCommentNode(e)},exports.isFunctionNode=function isFunctionNode(e){return FunctionNode.isFunctionNode(e)},exports.isSimpleBlockNode=function isSimpleBlockNode(e){return SimpleBlockNode.isSimpleBlockNode(e)},exports.isTokenNode=function isTokenNode(e){return TokenNode.isTokenNode(e)},exports.isUnclosedFunctionNode=function isUnclosedFunctionNode(e){return UnclosedFunctionNode.isUnclosedFunctionNode(e)},exports.isUnclosedSimpleBlockNode=function isUnclosedSimpleBlockNode(e){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(e)},exports.isWhitespaceNode=function isWhitespaceNode(e){return WhitespaceNode.isWhitespaceNode(e)},exports.parseCommaSeparatedListOfComponentValues=function parseCommaSeparatedListOfComponentValues(e,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...e];if(0===e.length)return[];s[s.length-1][0]!==n.TokenType.EOF&&s.push([n.TokenType.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=[],r=0;for(;;){if(!s[r]||s[r][0]===n.TokenType.EOF)return c.length&&i.push(c),i;if(s[r][0]===n.TokenType.Comma){c.length&&i.push(c),c=[],r++;continue}const o=consumeComponentValue(t,e.slice(r));c.push(o.node),r+=o.advance}},exports.parseComponentValue=function parseComponentValue(e,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...e];s[s.length-1][0]!==n.TokenType.EOF&&s.push([n.TokenType.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=consumeComponentValue(t,s);if(s[Math.min(i.advance,s.length-1)][0]===n.TokenType.EOF)return i.node;t.onParseError({message:"Expected EOF after parsing a component value.",start:e[0][2],end:e[e.length-1][3],state:["5.3.9. Parse a component value","Expected EOF"]})},exports.parseListOfComponentValues=function parseListOfComponentValues(e,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...e];s[s.length-1][0]!==n.TokenType.EOF&&s.push([n.TokenType.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=0;for(;;){if(!s[c]||s[c][0]===n.TokenType.EOF)return i;const e=consumeComponentValue(t,s.slice(c));i.push(e.node),c+=e.advance}};
+"use strict";var e,n=require("@csstools/css-tokenizer");function consumeComponentValue(e,o){const t=o[0];if(t[0]===n.TokenType.OpenParen||t[0]===n.TokenType.OpenCurly||t[0]===n.TokenType.OpenSquare){const n=consumeSimpleBlock(e,o);return{advance:n.advance,node:n.node}}if(t[0]===n.TokenType.Function){const n=consumeFunction(e,o);return{advance:n.advance,node:n.node}}if(t[0]===n.TokenType.Whitespace){const n=consumeWhitespace(e,o);return{advance:n.advance,node:n.node}}if(t[0]===n.TokenType.Comment){const n=consumeComment(e,o);return{advance:n.advance,node:n.node}}return{advance:1,node:new TokenNode(t)}}exports.ComponentValueType=void 0,(e=exports.ComponentValueType||(exports.ComponentValueType={})).Function="function",e.SimpleBlock="simple-block",e.Whitespace="whitespace",e.Comment="comment",e.Token="token",e.UnclosedFunction="unclosed-function",e.UnclosedSimpleBlock="unclosed-simple-block";class FunctionNode{type=exports.ComponentValueType.Function;name;endToken;value;constructor(e,n,o){this.name=e,this.endToken=n,this.value=o}nameTokenValue(){return this.name[4].value}tokens(){return[this.name,...this.value.flatMap((e=>n.isToken(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n.isToken(e)?n.stringify(e):e.toString())).join("");return n.stringify(this.name)+e+n.stringify(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,name:this.nameTokenValue(),tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isFunctionNode(){return FunctionNode.isFunctionNode(this)}static isFunctionNode(e){return!!e&&(e instanceof FunctionNode&&e.type===exports.ComponentValueType.Function)}}function consumeFunction(e,o){const t=[];let s=1;for(;;){const i=o[s];if(!i||i[0]===n.TokenType.EOF)return e.onParseError({message:"Unexpected EOF while consuming a function.",start:o[0][2],end:o[o.length-1][3],state:["5.4.9. Consume a function","Unexpected EOF"]}),{advance:o.length,node:new UnclosedFunctionNode(o)};if(i[0]===n.TokenType.CloseParen)return{advance:s+1,node:new FunctionNode(o[0],i,t)};if(i[0]===n.TokenType.Comment||i[0]===n.TokenType.Whitespace){const n=consumeAllCommentsAndWhitespace(e,o.slice(s));s+=n.advance,t.push(...n.nodes);continue}const c=consumeComponentValue(e,o.slice(s));s+=c.advance,t.push(c.node)}}class SimpleBlockNode{type=exports.ComponentValueType.SimpleBlock;startToken;endToken;value;constructor(e,n,o){this.startToken=e,this.endToken=n,this.value=o}tokens(){return[this.startToken,...this.value.flatMap((e=>n.isToken(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n.isToken(e)?n.stringify(e):e.toString())).join("");return n.stringify(this.startToken)+e+n.stringify(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,startToken:this.startToken,tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isSimpleBlockNode(){return SimpleBlockNode.isSimpleBlockNode(this)}static isSimpleBlockNode(e){return!!e&&(e instanceof SimpleBlockNode&&e.type===exports.ComponentValueType.SimpleBlock)}}function consumeSimpleBlock(e,o){const t=n.mirrorVariantType(o[0][0]);if(!t)throw new Error("Failed to parse, a mirror variant must exist for all block open tokens.");const s=[];let i=1;for(;;){const c=o[i];if(!c||c[0]===n.TokenType.EOF)return e.onParseError({message:"Unexpected EOF while consuming a simple block.",start:o[0][2],end:o[o.length-1][3],state:["5.4.8. Consume a simple block","Unexpected EOF"]}),{advance:o.length,node:new UnclosedSimpleBlockNode(o)};if(c[0]===t)return{advance:i+1,node:new SimpleBlockNode(o[0],c,s)};if(c[0]===n.TokenType.Comment||c[0]===n.TokenType.Whitespace){const n=consumeAllCommentsAndWhitespace(e,o.slice(i));i+=n.advance,s.push(...n.nodes);continue}const r=consumeComponentValue(e,o.slice(i));i+=r.advance,s.push(r.node)}}class WhitespaceNode{type=exports.ComponentValueType.Whitespace;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return n.stringify(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isWhitespaceNode(){return WhitespaceNode.isWhitespaceNode(this)}static isWhitespaceNode(e){return!!e&&(e instanceof WhitespaceNode&&e.type===exports.ComponentValueType.Whitespace)}}function consumeWhitespace(e,o){let t=0;for(;;){if(o[t][0]!==n.TokenType.Whitespace)return{advance:t,node:new WhitespaceNode(o.slice(0,t))};t++}}class CommentNode{type=exports.ComponentValueType.Comment;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return n.stringify(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isCommentNode(){return CommentNode.isCommentNode(this)}static isCommentNode(e){return!!e&&(e instanceof CommentNode&&e.type===exports.ComponentValueType.Comment)}}function consumeComment(e,n){return{advance:1,node:new CommentNode(n[0])}}function consumeAllCommentsAndWhitespace(e,o){const t=[];let s=0;for(;;)if(o[s][0]!==n.TokenType.Whitespace){if(o[s][0]!==n.TokenType.Comment)return{advance:s,nodes:t};t.push(new CommentNode(o[s])),s++}else{const e=consumeWhitespace(0,o.slice(s));s+=e.advance,t.push(e.node)}}class TokenNode{type=exports.ComponentValueType.Token;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return n.stringify(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isTokenNode(){return TokenNode.isTokenNode(this)}static isTokenNode(e){return!!e&&(e instanceof TokenNode&&e.type===exports.ComponentValueType.Token)}}class UnclosedFunctionNode{type=exports.ComponentValueType.UnclosedFunction;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return n.stringify(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedFunctionNode(){return UnclosedFunctionNode.isUnclosedFunctionNode(this)}static isUnclosedFunctionNode(e){return!!e&&(e instanceof UnclosedFunctionNode&&e.type===exports.ComponentValueType.UnclosedFunction)}}class UnclosedSimpleBlockNode{type=exports.ComponentValueType.UnclosedSimpleBlock;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return n.stringify(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedSimpleBlockNode(){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(this)}static isUnclosedSimpleBlockNode(e){return!!e&&(e instanceof UnclosedSimpleBlockNode&&e.type===exports.ComponentValueType.UnclosedSimpleBlock)}}exports.CommentNode=CommentNode,exports.FunctionNode=FunctionNode,exports.SimpleBlockNode=SimpleBlockNode,exports.TokenNode=TokenNode,exports.UnclosedFunctionNode=UnclosedFunctionNode,exports.UnclosedSimpleBlockNode=UnclosedSimpleBlockNode,exports.WhitespaceNode=WhitespaceNode,exports.consumeAllCommentsAndWhitespace=consumeAllCommentsAndWhitespace,exports.consumeComment=consumeComment,exports.consumeComponentValue=consumeComponentValue,exports.consumeFunction=consumeFunction,exports.consumeSimpleBlock=consumeSimpleBlock,exports.consumeWhitespace=consumeWhitespace,exports.gatherNodeAncestry=function gatherNodeAncestry(e){const n=new Map;return e.walk((e=>{Array.isArray(e.node)?e.node.forEach((o=>{n.set(o,e.parent)})):n.set(e.node,e.parent)})),n},exports.isCommentNode=function isCommentNode(e){return CommentNode.isCommentNode(e)},exports.isFunctionNode=function isFunctionNode(e){return FunctionNode.isFunctionNode(e)},exports.isSimpleBlockNode=function isSimpleBlockNode(e){return SimpleBlockNode.isSimpleBlockNode(e)},exports.isTokenNode=function isTokenNode(e){return TokenNode.isTokenNode(e)},exports.isUnclosedFunctionNode=function isUnclosedFunctionNode(e){return UnclosedFunctionNode.isUnclosedFunctionNode(e)},exports.isUnclosedSimpleBlockNode=function isUnclosedSimpleBlockNode(e){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(e)},exports.isWhitespaceNode=function isWhitespaceNode(e){return WhitespaceNode.isWhitespaceNode(e)},exports.parseCommaSeparatedListOfComponentValues=function parseCommaSeparatedListOfComponentValues(e,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...e];if(0===e.length)return[];s[s.length-1][0]!==n.TokenType.EOF&&s.push([n.TokenType.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=[],r=0;for(;;){if(!s[r]||s[r][0]===n.TokenType.EOF)return c.length&&i.push(c),i;if(s[r][0]===n.TokenType.Comma){i.push(c),c=[],r++;continue}const o=consumeComponentValue(t,e.slice(r));c.push(o.node),r+=o.advance}},exports.parseComponentValue=function parseComponentValue(e,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...e];s[s.length-1][0]!==n.TokenType.EOF&&s.push([n.TokenType.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=consumeComponentValue(t,s);if(s[Math.min(i.advance,s.length-1)][0]===n.TokenType.EOF)return i.node;t.onParseError({message:"Expected EOF after parsing a component value.",start:e[0][2],end:e[e.length-1][3],state:["5.3.9. Parse a component value","Expected EOF"]})},exports.parseListOfComponentValues=function parseListOfComponentValues(e,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...e];s[s.length-1][0]!==n.TokenType.EOF&&s.push([n.TokenType.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=0;for(;;){if(!s[c]||s[c][0]===n.TokenType.EOF)return i;const e=consumeComponentValue(t,s.slice(c));i.push(e.node),c+=e.advance}};
diff --git a/packages/css-parser-algorithms/dist/index.mjs b/packages/css-parser-algorithms/dist/index.mjs
index 2864b392c..cb6b5c249 100644
--- a/packages/css-parser-algorithms/dist/index.mjs
+++ b/packages/css-parser-algorithms/dist/index.mjs
@@ -1 +1 @@
-import{TokenType as e,isToken as n,stringify as o,mirrorVariantType as t}from"@csstools/css-tokenizer";var s;function consumeComponentValue(n,o){const t=o[0];if(t[0]===e.OpenParen||t[0]===e.OpenCurly||t[0]===e.OpenSquare){const e=consumeSimpleBlock(n,o);return{advance:e.advance,node:e.node}}if(t[0]===e.Function){const e=consumeFunction(n,o);return{advance:e.advance,node:e.node}}if(t[0]===e.Whitespace){const e=consumeWhitespace(n,o);return{advance:e.advance,node:e.node}}if(t[0]===e.Comment){const e=consumeComment(n,o);return{advance:e.advance,node:e.node}}return{advance:1,node:new TokenNode(t)}}!function(e){e.Function="function",e.SimpleBlock="simple-block",e.Whitespace="whitespace",e.Comment="comment",e.Token="token",e.UnclosedFunction="unclosed-function",e.UnclosedSimpleBlock="unclosed-simple-block"}(s||(s={}));class FunctionNode{type=s.Function;name;endToken;value;constructor(e,n,o){this.name=e,this.endToken=n,this.value=o}nameTokenValue(){return this.name[4].value}tokens(){return[this.name,...this.value.flatMap((e=>n(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n(e)?o(e):e.toString())).join("");return o(this.name)+e+o(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,name:this.nameTokenValue(),tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isFunctionNode(){return FunctionNode.isFunctionNode(this)}static isFunctionNode(e){return!!e&&(e instanceof FunctionNode&&e.type===s.Function)}}function consumeFunction(n,o){const t=[];let s=1;for(;;){const i=o[s];if(!i||i[0]===e.EOF)return n.onParseError({message:"Unexpected EOF while consuming a function.",start:o[0][2],end:o[o.length-1][3],state:["5.4.9. Consume a function","Unexpected EOF"]}),{advance:o.length,node:new UnclosedFunctionNode(o)};if(i[0]===e.CloseParen)return{advance:s+1,node:new FunctionNode(o[0],i,t)};if(i[0]===e.Comment||i[0]===e.Whitespace){const e=consumeAllCommentsAndWhitespace(n,o.slice(s));s+=e.advance,t.push(...e.nodes);continue}const c=consumeComponentValue(n,o.slice(s));s+=c.advance,t.push(c.node)}}class SimpleBlockNode{type=s.SimpleBlock;startToken;endToken;value;constructor(e,n,o){this.startToken=e,this.endToken=n,this.value=o}tokens(){return[this.startToken,...this.value.flatMap((e=>n(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n(e)?o(e):e.toString())).join("");return o(this.startToken)+e+o(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,startToken:this.startToken,tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isSimpleBlockNode(){return SimpleBlockNode.isSimpleBlockNode(this)}static isSimpleBlockNode(e){return!!e&&(e instanceof SimpleBlockNode&&e.type===s.SimpleBlock)}}function consumeSimpleBlock(n,o){const s=t(o[0][0]);if(!s)throw new Error("Failed to parse, a mirror variant must exist for all block open tokens.");const i=[];let c=1;for(;;){const t=o[c];if(!t||t[0]===e.EOF)return n.onParseError({message:"Unexpected EOF while consuming a simple block.",start:o[0][2],end:o[o.length-1][3],state:["5.4.8. Consume a simple block","Unexpected EOF"]}),{advance:o.length,node:new UnclosedSimpleBlockNode(o)};if(t[0]===s)return{advance:c+1,node:new SimpleBlockNode(o[0],t,i)};if(t[0]===e.Comment||t[0]===e.Whitespace){const e=consumeAllCommentsAndWhitespace(n,o.slice(c));c+=e.advance,i.push(...e.nodes);continue}const r=consumeComponentValue(n,o.slice(c));c+=r.advance,i.push(r.node)}}class WhitespaceNode{type=s.Whitespace;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return o(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isWhitespaceNode(){return WhitespaceNode.isWhitespaceNode(this)}static isWhitespaceNode(e){return!!e&&(e instanceof WhitespaceNode&&e.type===s.Whitespace)}}function consumeWhitespace(n,o){let t=0;for(;;){if(o[t][0]!==e.Whitespace)return{advance:t,node:new WhitespaceNode(o.slice(0,t))};t++}}class CommentNode{type=s.Comment;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return o(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isCommentNode(){return CommentNode.isCommentNode(this)}static isCommentNode(e){return!!e&&(e instanceof CommentNode&&e.type===s.Comment)}}function consumeComment(e,n){return{advance:1,node:new CommentNode(n[0])}}function consumeAllCommentsAndWhitespace(n,o){const t=[];let s=0;for(;;)if(o[s][0]!==e.Whitespace){if(o[s][0]!==e.Comment)return{advance:s,nodes:t};t.push(new CommentNode(o[s])),s++}else{const e=consumeWhitespace(0,o.slice(s));s+=e.advance,t.push(e.node)}}class TokenNode{type=s.Token;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return o(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isTokenNode(){return TokenNode.isTokenNode(this)}static isTokenNode(e){return!!e&&(e instanceof TokenNode&&e.type===s.Token)}}class UnclosedFunctionNode{type=s.UnclosedFunction;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return o(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedFunctionNode(){return UnclosedFunctionNode.isUnclosedFunctionNode(this)}static isUnclosedFunctionNode(e){return!!e&&(e instanceof UnclosedFunctionNode&&e.type===s.UnclosedFunction)}}class UnclosedSimpleBlockNode{type=s.UnclosedSimpleBlock;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return o(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedSimpleBlockNode(){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(this)}static isUnclosedSimpleBlockNode(e){return!!e&&(e instanceof UnclosedSimpleBlockNode&&e.type===s.UnclosedSimpleBlock)}}function parseComponentValue(n,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...n];s[s.length-1][0]!==e.EOF&&s.push([e.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=consumeComponentValue(t,s);if(s[Math.min(i.advance,s.length-1)][0]===e.EOF)return i.node;t.onParseError({message:"Expected EOF after parsing a component value.",start:n[0][2],end:n[n.length-1][3],state:["5.3.9. Parse a component value","Expected EOF"]})}function parseListOfComponentValues(n,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...n];s[s.length-1][0]!==e.EOF&&s.push([e.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=0;for(;;){if(!s[c]||s[c][0]===e.EOF)return i;const n=consumeComponentValue(t,s.slice(c));i.push(n.node),c+=n.advance}}function parseCommaSeparatedListOfComponentValues(n,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...n];if(0===n.length)return[];s[s.length-1][0]!==e.EOF&&s.push([e.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=[],r=0;for(;;){if(!s[r]||s[r][0]===e.EOF)return c.length&&i.push(c),i;if(s[r][0]===e.Comma){c.length&&i.push(c),c=[],r++;continue}const o=consumeComponentValue(t,n.slice(r));c.push(o.node),r+=o.advance}}function gatherNodeAncestry(e){const n=new Map;return e.walk((e=>{Array.isArray(e.node)?e.node.forEach((o=>{n.set(o,e.parent)})):n.set(e.node,e.parent)})),n}function isSimpleBlockNode(e){return SimpleBlockNode.isSimpleBlockNode(e)}function isFunctionNode(e){return FunctionNode.isFunctionNode(e)}function isUnclosedSimpleBlockNode(e){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(e)}function isUnclosedFunctionNode(e){return UnclosedFunctionNode.isUnclosedFunctionNode(e)}function isWhitespaceNode(e){return WhitespaceNode.isWhitespaceNode(e)}function isCommentNode(e){return CommentNode.isCommentNode(e)}function isTokenNode(e){return TokenNode.isTokenNode(e)}export{CommentNode,s as ComponentValueType,FunctionNode,SimpleBlockNode,TokenNode,UnclosedFunctionNode,UnclosedSimpleBlockNode,WhitespaceNode,consumeAllCommentsAndWhitespace,consumeComment,consumeComponentValue,consumeFunction,consumeSimpleBlock,consumeWhitespace,gatherNodeAncestry,isCommentNode,isFunctionNode,isSimpleBlockNode,isTokenNode,isUnclosedFunctionNode,isUnclosedSimpleBlockNode,isWhitespaceNode,parseCommaSeparatedListOfComponentValues,parseComponentValue,parseListOfComponentValues};
+import{TokenType as e,isToken as n,stringify as o,mirrorVariantType as t}from"@csstools/css-tokenizer";var s;function consumeComponentValue(n,o){const t=o[0];if(t[0]===e.OpenParen||t[0]===e.OpenCurly||t[0]===e.OpenSquare){const e=consumeSimpleBlock(n,o);return{advance:e.advance,node:e.node}}if(t[0]===e.Function){const e=consumeFunction(n,o);return{advance:e.advance,node:e.node}}if(t[0]===e.Whitespace){const e=consumeWhitespace(n,o);return{advance:e.advance,node:e.node}}if(t[0]===e.Comment){const e=consumeComment(n,o);return{advance:e.advance,node:e.node}}return{advance:1,node:new TokenNode(t)}}!function(e){e.Function="function",e.SimpleBlock="simple-block",e.Whitespace="whitespace",e.Comment="comment",e.Token="token",e.UnclosedFunction="unclosed-function",e.UnclosedSimpleBlock="unclosed-simple-block"}(s||(s={}));class FunctionNode{type=s.Function;name;endToken;value;constructor(e,n,o){this.name=e,this.endToken=n,this.value=o}nameTokenValue(){return this.name[4].value}tokens(){return[this.name,...this.value.flatMap((e=>n(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n(e)?o(e):e.toString())).join("");return o(this.name)+e+o(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,name:this.nameTokenValue(),tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isFunctionNode(){return FunctionNode.isFunctionNode(this)}static isFunctionNode(e){return!!e&&(e instanceof FunctionNode&&e.type===s.Function)}}function consumeFunction(n,o){const t=[];let s=1;for(;;){const i=o[s];if(!i||i[0]===e.EOF)return n.onParseError({message:"Unexpected EOF while consuming a function.",start:o[0][2],end:o[o.length-1][3],state:["5.4.9. Consume a function","Unexpected EOF"]}),{advance:o.length,node:new UnclosedFunctionNode(o)};if(i[0]===e.CloseParen)return{advance:s+1,node:new FunctionNode(o[0],i,t)};if(i[0]===e.Comment||i[0]===e.Whitespace){const e=consumeAllCommentsAndWhitespace(n,o.slice(s));s+=e.advance,t.push(...e.nodes);continue}const c=consumeComponentValue(n,o.slice(s));s+=c.advance,t.push(c.node)}}class SimpleBlockNode{type=s.SimpleBlock;startToken;endToken;value;constructor(e,n,o){this.startToken=e,this.endToken=n,this.value=o}tokens(){return[this.startToken,...this.value.flatMap((e=>n(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n(e)?o(e):e.toString())).join("");return o(this.startToken)+e+o(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,startToken:this.startToken,tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isSimpleBlockNode(){return SimpleBlockNode.isSimpleBlockNode(this)}static isSimpleBlockNode(e){return!!e&&(e instanceof SimpleBlockNode&&e.type===s.SimpleBlock)}}function consumeSimpleBlock(n,o){const s=t(o[0][0]);if(!s)throw new Error("Failed to parse, a mirror variant must exist for all block open tokens.");const i=[];let c=1;for(;;){const t=o[c];if(!t||t[0]===e.EOF)return n.onParseError({message:"Unexpected EOF while consuming a simple block.",start:o[0][2],end:o[o.length-1][3],state:["5.4.8. Consume a simple block","Unexpected EOF"]}),{advance:o.length,node:new UnclosedSimpleBlockNode(o)};if(t[0]===s)return{advance:c+1,node:new SimpleBlockNode(o[0],t,i)};if(t[0]===e.Comment||t[0]===e.Whitespace){const e=consumeAllCommentsAndWhitespace(n,o.slice(c));c+=e.advance,i.push(...e.nodes);continue}const r=consumeComponentValue(n,o.slice(c));c+=r.advance,i.push(r.node)}}class WhitespaceNode{type=s.Whitespace;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return o(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isWhitespaceNode(){return WhitespaceNode.isWhitespaceNode(this)}static isWhitespaceNode(e){return!!e&&(e instanceof WhitespaceNode&&e.type===s.Whitespace)}}function consumeWhitespace(n,o){let t=0;for(;;){if(o[t][0]!==e.Whitespace)return{advance:t,node:new WhitespaceNode(o.slice(0,t))};t++}}class CommentNode{type=s.Comment;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return o(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isCommentNode(){return CommentNode.isCommentNode(this)}static isCommentNode(e){return!!e&&(e instanceof CommentNode&&e.type===s.Comment)}}function consumeComment(e,n){return{advance:1,node:new CommentNode(n[0])}}function consumeAllCommentsAndWhitespace(n,o){const t=[];let s=0;for(;;)if(o[s][0]!==e.Whitespace){if(o[s][0]!==e.Comment)return{advance:s,nodes:t};t.push(new CommentNode(o[s])),s++}else{const e=consumeWhitespace(0,o.slice(s));s+=e.advance,t.push(e.node)}}class TokenNode{type=s.Token;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return o(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isTokenNode(){return TokenNode.isTokenNode(this)}static isTokenNode(e){return!!e&&(e instanceof TokenNode&&e.type===s.Token)}}class UnclosedFunctionNode{type=s.UnclosedFunction;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return o(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedFunctionNode(){return UnclosedFunctionNode.isUnclosedFunctionNode(this)}static isUnclosedFunctionNode(e){return!!e&&(e instanceof UnclosedFunctionNode&&e.type===s.UnclosedFunction)}}class UnclosedSimpleBlockNode{type=s.UnclosedSimpleBlock;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return o(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedSimpleBlockNode(){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(this)}static isUnclosedSimpleBlockNode(e){return!!e&&(e instanceof UnclosedSimpleBlockNode&&e.type===s.UnclosedSimpleBlock)}}function parseComponentValue(n,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...n];s[s.length-1][0]!==e.EOF&&s.push([e.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=consumeComponentValue(t,s);if(s[Math.min(i.advance,s.length-1)][0]===e.EOF)return i.node;t.onParseError({message:"Expected EOF after parsing a component value.",start:n[0][2],end:n[n.length-1][3],state:["5.3.9. Parse a component value","Expected EOF"]})}function parseListOfComponentValues(n,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...n];s[s.length-1][0]!==e.EOF&&s.push([e.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=0;for(;;){if(!s[c]||s[c][0]===e.EOF)return i;const n=consumeComponentValue(t,s.slice(c));i.push(n.node),c+=n.advance}}function parseCommaSeparatedListOfComponentValues(n,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...n];if(0===n.length)return[];s[s.length-1][0]!==e.EOF&&s.push([e.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=[],r=0;for(;;){if(!s[r]||s[r][0]===e.EOF)return c.length&&i.push(c),i;if(s[r][0]===e.Comma){i.push(c),c=[],r++;continue}const o=consumeComponentValue(t,n.slice(r));c.push(o.node),r+=o.advance}}function gatherNodeAncestry(e){const n=new Map;return e.walk((e=>{Array.isArray(e.node)?e.node.forEach((o=>{n.set(o,e.parent)})):n.set(e.node,e.parent)})),n}function isSimpleBlockNode(e){return SimpleBlockNode.isSimpleBlockNode(e)}function isFunctionNode(e){return FunctionNode.isFunctionNode(e)}function isUnclosedSimpleBlockNode(e){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(e)}function isUnclosedFunctionNode(e){return UnclosedFunctionNode.isUnclosedFunctionNode(e)}function isWhitespaceNode(e){return WhitespaceNode.isWhitespaceNode(e)}function isCommentNode(e){return CommentNode.isCommentNode(e)}function isTokenNode(e){return TokenNode.isTokenNode(e)}export{CommentNode,s as ComponentValueType,FunctionNode,SimpleBlockNode,TokenNode,UnclosedFunctionNode,UnclosedSimpleBlockNode,WhitespaceNode,consumeAllCommentsAndWhitespace,consumeComment,consumeComponentValue,consumeFunction,consumeSimpleBlock,consumeWhitespace,gatherNodeAncestry,isCommentNode,isFunctionNode,isSimpleBlockNode,isTokenNode,isUnclosedFunctionNode,isUnclosedSimpleBlockNode,isWhitespaceNode,parseCommaSeparatedListOfComponentValues,parseComponentValue,parseListOfComponentValues};
diff --git a/packages/css-parser-algorithms/src/parse/parse-comma-separated-list-of-component-values.ts b/packages/css-parser-algorithms/src/parse/parse-comma-separated-list-of-component-values.ts
index a4136fcb8..67b5425c9 100644
--- a/packages/css-parser-algorithms/src/parse/parse-comma-separated-list-of-component-values.ts
+++ b/packages/css-parser-algorithms/src/parse/parse-comma-separated-list-of-component-values.ts
@@ -43,10 +43,7 @@ export function parseCommaSeparatedListOfComponentValues(tokens: Array
}
if (tokensCopy[i][0] === TokenType.Comma) {
- if (list.length) {
- listOfCvls.push(list);
- }
-
+ listOfCvls.push(list);
list = [];
i++;
continue;
diff --git a/packages/css-parser-algorithms/test/cases/various/0020.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0020.list-comma.expect.json
new file mode 100644
index 000000000..dc86ff5de
--- /dev/null
+++ b/packages/css-parser-algorithms/test/cases/various/0020.list-comma.expect.json
@@ -0,0 +1,35 @@
+[
+ [
+ {
+ "type": "token",
+ "tokens": [
+ [
+ "ident-token",
+ "a",
+ 0,
+ 0,
+ {
+ "value": "a"
+ }
+ ]
+ ]
+ }
+ ],
+ [],
+ [
+ {
+ "type": "token",
+ "tokens": [
+ [
+ "ident-token",
+ "c",
+ 3,
+ 3,
+ {
+ "value": "c"
+ }
+ ]
+ ]
+ }
+ ]
+]
\ No newline at end of file
diff --git a/packages/css-parser-algorithms/test/cases/various/0020.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0020.list-space.expect.json
new file mode 100644
index 000000000..afd6027d1
--- /dev/null
+++ b/packages/css-parser-algorithms/test/cases/various/0020.list-space.expect.json
@@ -0,0 +1,54 @@
+[
+ {
+ "type": "token",
+ "tokens": [
+ [
+ "ident-token",
+ "a",
+ 0,
+ 0,
+ {
+ "value": "a"
+ }
+ ]
+ ]
+ },
+ {
+ "type": "token",
+ "tokens": [
+ [
+ "comma-token",
+ ",",
+ 1,
+ 1,
+ null
+ ]
+ ]
+ },
+ {
+ "type": "token",
+ "tokens": [
+ [
+ "comma-token",
+ ",",
+ 2,
+ 2,
+ null
+ ]
+ ]
+ },
+ {
+ "type": "token",
+ "tokens": [
+ [
+ "ident-token",
+ "c",
+ 3,
+ 3,
+ {
+ "value": "c"
+ }
+ ]
+ ]
+ }
+]
\ No newline at end of file
diff --git a/packages/css-parser-algorithms/test/cases/various/0020.mjs b/packages/css-parser-algorithms/test/cases/various/0020.mjs
new file mode 100644
index 000000000..040771614
--- /dev/null
+++ b/packages/css-parser-algorithms/test/cases/various/0020.mjs
@@ -0,0 +1,13 @@
+import assert from 'assert';
+import { runTest } from '../../util/run-test.mjs';
+
+runTest(
+ 'a,,c',
+ 'various/0020',
+ (actual, expected) => {
+ assert.deepStrictEqual(
+ actual,
+ expected,
+ );
+ },
+);
diff --git a/packages/css-parser-algorithms/test/test.mjs b/packages/css-parser-algorithms/test/test.mjs
index fa184c135..2eef7a9a5 100644
--- a/packages/css-parser-algorithms/test/test.mjs
+++ b/packages/css-parser-algorithms/test/test.mjs
@@ -35,3 +35,4 @@ import './cases/various/0016.mjs';
import './cases/various/0017.mjs';
import './cases/various/0018.mjs';
import './cases/various/0019.mjs';
+import './cases/various/0020.mjs';
diff --git a/plugins/postcss-custom-media/dist/index.cjs b/plugins/postcss-custom-media/dist/index.cjs
index 38e06987f..fad0ad4ae 100644
--- a/plugins/postcss-custom-media/dist/index.cjs
+++ b/plugins/postcss-custom-media/dist/index.cjs
@@ -1 +1 @@
-"use strict";var e=require("@csstools/css-tokenizer"),r=require("@csstools/media-query-list-parser");function collectCascadeLayerOrder(e){const r=new Map,t=new Map,n=[];e.walkAtRules((e=>{if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let o=[];if(e.nodes)o.push((a=e.params,s=1,a.trim()||"csstools-anon-layer--"+s++));else{if(!e.params.trim())return;o=e.params.split(",").map((e=>e.trim()))}var a,s;{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=t.get(r);e?(o=o.map((r=>e+"."+r)),r=r.parent):r=r.parent}}if(addLayerToModel(n,o),e.nodes){const n=o[0]+".csstools-implicit-layer";r.set(e,n),t.set(e,o[0])}}));for(const e of r.values())addLayerToModel(n,[e]);const o=n.map((e=>e.join("."))),a=new WeakMap;for(const[e,t]of r)a.set(e,o.indexOf(t));return a}function addLayerToModel(e,r){return r.forEach((r=>{const t=r.split(".");e:for(let r=0;r=a&&(o=r,a=s)}-1===o?e.push(n):e.splice(o+1,0,n)}})),e}const t=new Set(["scope","container","layer"]);function isProcessableCustomMediaRule(e){if("custom-media"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes("--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!t.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function removeCyclicReferences(e,r){const t=new Set;let n=r;for(;e.size>0;)try{toposort(Array.from(e.keys()),n);break}catch(r){if(!r._graphNode)throw r;e.delete(r._graphNode),t.add(r._graphNode),n=n.filter((e=>-1===e.indexOf(r._graphNode)))}return t}function toposort(e,r){let t=e.length;const n=new Array(t),o={};let a=t;const s=makeOutgoingEdges(r),i=makeNodesHash(e);for(r.forEach((function(e){if(!i.has(e[0])||!i.has(e[1]))throw new Error("Unknown token. Make sure to provide all tokens used in aliases.")}));a--;)o[a]||visit(e[a],a,new Set);return n;function visit(e,r,a){if(a.has(e)){const r=new Error("Cyclic dependency"+JSON.stringify(e));throw r._graphNode=e,r}if(!i.has(e))throw new Error("Found unknown token. Make sure to provided all involved tokens. Unknown token: "+JSON.stringify(e));if(o[r])return;o[r]=!0;let l=s.get(e)||new Set;if(l=Array.from(l),r=l.length){a.add(e);do{const e=l[--r];visit(e,i.get(e),a)}while(r);a.delete(e)}n[--t]=e}}function makeOutgoingEdges(e){const r=new Map;for(let t=0,n=e.length;t{throw new Error(`Unable to parse media query "${r}"`)}}),n=[];for(;!t.endOfFile();)n.push(t.nextToken());return n}const n=[[e.TokenType.Ident,"max-color",0,0,{value:"max-color"}],[e.TokenType.Colon,":",0,0,void 0],[e.TokenType.Number,"2147477350",0,0,{value:2147477350,type:e.NumberType.Integer}]],o=[[e.TokenType.Ident,"color",0,0,{value:"color"}],[e.TokenType.Colon,":",0,0,void 0],[e.TokenType.Number,"2147477350",0,0,{value:2147477350,type:e.NumberType.Integer}]];function replaceTrueAndFalseTokens(r){let t,a;for(let n=0;n{throw new Error(`Unable to parse media query "${e.stringify(...s)}"`)}}),l=r.parseFromTokens(e.cloneTokens(s),{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${e.stringify(...s)}"`)}});for(let e=0;e[e,a]))}}function getCustomMedia(e,r,t){const n=new Map,o=new Map,a=[],s=collectCascadeLayerOrder(e);e.walkAtRules((e=>{if(!isProcessableCustomMediaRule(e))return;const r=parseCustomMedia(e.params);if(!r)return;if(0===r.truthy.length)return;const i=(u=s,(l=e).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?u.has(l.parent)?u.get(l.parent):-1:1/0);var l,u;if(i>=(o.get(r.name)??-1)&&(o.set(r.name,i),n.set(r.name,{truthy:r.truthy,falsy:r.falsy}),a.push(...r.dependsOn)),!t.preserve){const r=e.parent;e.remove(),removeEmptyAncestorBlocks(r)}}));const i=removeCyclicReferences(n,a);for(const t of i.values())e.warn(r,`@custom-media rules have cyclic dependencies for "${t}"`);return n}function removeEmptyAncestorBlocks(e){let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}function transformAtMediaListTokens(e,t){const n=r.parse(e,{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${e}"`)}}),o=n.map((e=>e.toString()));for(let e=0;et===e?n:{replaceWith:r}))}const s=transformComplexMediaQuery(r,t);if(s&&0!==s.length&&s[0].replaceWith!==a)return o.flatMap(((r,t)=>t===e?s:[{replaceWith:r}]))}return[]}function transformSimpleMediaQuery(e,t){if(!mediaQueryIsSimple(e))return null;let n=null;return e.walk((e=>{const o=e.node;if(!r.isMediaFeatureBoolean(o))return;const a=o.getName();if(!a.startsWith("--"))return!1;const s=t.get(a);return s?(n={replaceWith:s.truthy.map((e=>e.toString().trim())).join(",")},!1):void 0})),n}function transformComplexMediaQuery(e,t){let a=[];return e.walk((s=>{const i=s.node;if(!r.isMediaFeatureBoolean(i))return;const l=s.parent;if(!r.isMediaFeature(l))return;const u=i.getName();if(!u.startsWith("--"))return!1;const p=t.get(u);if(p){if(1===p.truthy.length&&mediaQueryIsSimple(p.truthy[0])){let t=null;if(p.truthy[0].walk((e=>{if(r.isMediaFeature(e.node))return t=e.node,!1})),t&&t.feature)return l.feature=t.feature,a=[{replaceWith:e.toString()}],!1}const t=r.newMediaFeaturePlain(n[0][4].value,n[2]);l.feature=t.feature;const s=e.toString(),i=r.newMediaFeaturePlain(o[0][4].value,o[2]);l.feature=i.feature;const u=e.toString();return a=[{replaceWith:s,encapsulateWith:p.truthy.map((e=>e.toString().trim())).join(",")},{replaceWith:u,encapsulateWith:p.falsy.map((e=>e.toString().trim())).join(",")}],!1}})),a}function mediaQueryIsSimple(e){if(r.isMediaQueryInvalid(e))return!1;if(r.isMediaQueryWithType(e))return!1;let t=!0;return e.walk((e=>{if(r.isMediaAnd(e.node)||r.isMediaOr(e.node)||r.isMediaNot(e.node)||r.isMediaConditionList(e.node)||r.isGeneralEnclosed(e.node))return t=!1,!1})),t}const creator=e=>{const r=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-media] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-media] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-media",prepare(){let e=new Map;return{Once:(t,{result:n})=>{e=getCustomMedia(t,n,{preserve:r})},AtRule:(t,{result:n})=>{if("media"!==t.name.toLowerCase())return;if(!t.params)return;if(!t.params.includes("--"))return;let o=[];try{o=transformAtMediaListTokens(t.params,e)}catch(e){return void t.warn(n,`Failed to parse @custom-media params with error message: "${e.message}"`)}if(!o||0===o.length)return;if(1===o.length){if(t.params.trim()===o[0].replaceWith.trim())return;return t.cloneBefore({params:o[0].replaceWith.trim()}),r?void 0:void t.remove()}if(!!!o.find((e=>!!e.encapsulateWith)))return t.cloneBefore({params:o.map((e=>e.replaceWith)).join(",").trim()}),void(r||t.remove());o.forEach((e=>{if(!e.encapsulateWith)return void t.cloneBefore({params:e.replaceWith.trim()});const r=t.clone({params:e.replaceWith}),n=t.clone({params:e.encapsulateWith.trim(),nodes:[]});r.parent=null,n.parent=null,n.append(r),t.before(n)})),r||t.remove()}}}}};creator.postcss=!0,module.exports=creator;
+"use strict";var e=require("@csstools/cascade-layer-name-parser"),r=require("@csstools/css-tokenizer"),t=require("@csstools/media-query-list-parser");const n=e.parse("csstools-implicit-layer")[0];function collectCascadeLayerOrder(r){const t=new Map,o=new Map,a=[];r.walkAtRules((r=>{var s;if("layer"!==r.name.toLowerCase())return;{let e=r.parent;for(;e;){if("atrule"!==e.type||"layer"!==e.name.toLowerCase()){if(e===r.root())break;return}e=e.parent}}let i;if(r.nodes)i=normalizeLayerName(r.params,1);else{if(!r.params.trim())return;i=r.params}let l=e.parse(i);if(null!=(s=l)&&s.length){{let e=r.parent;for(;e&&"atrule"===e.type&&"layer"===e.name.toLowerCase();){const r=o.get(e);r?(l=l.map((e=>r.concat(e))),e=e.parent):e=e.parent}}if(e.addLayerToModel(a,l),r.nodes){const e=l[0].concat(n);t.set(r,e),o.set(r,l[0])}}}));for(const r of t.values())e.addLayerToModel(a,[r]);const s=new WeakMap;for(const[e,r]of t)s.set(e,a.findIndex((e=>r.equal(e))));return s}function normalizeLayerName(e,r){return e.trim()?e:"csstools-anon-layer--"+r++}const o=new Set(["scope","container","layer"]);function isProcessableCustomMediaRule(e){if("custom-media"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes("--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!o.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function removeCyclicReferences(e,r){const t=new Set;let n=r;for(;e.size>0;)try{toposort(Array.from(e.keys()),n);break}catch(r){if(!r._graphNode)throw r;e.delete(r._graphNode),t.add(r._graphNode),n=n.filter((e=>-1===e.indexOf(r._graphNode)))}return t}function toposort(e,r){let t=e.length;const n=new Array(t),o={};let a=t;const s=makeOutgoingEdges(r),i=makeNodesHash(e);for(r.forEach((function(e){if(!i.has(e[0])||!i.has(e[1]))throw new Error("Unknown token. Make sure to provide all tokens used in aliases.")}));a--;)o[a]||visit(e[a],a,new Set);return n;function visit(e,r,a){if(a.has(e)){const r=new Error("Cyclic dependency"+JSON.stringify(e));throw r._graphNode=e,r}if(!i.has(e))throw new Error("Found unknown token. Make sure to provided all involved tokens. Unknown token: "+JSON.stringify(e));if(o[r])return;o[r]=!0;let l=s.get(e)||new Set;if(l=Array.from(l),r=l.length){a.add(e);do{const e=l[--r];visit(e,i.get(e),a)}while(r);a.delete(e)}n[--t]=e}}function makeOutgoingEdges(e){const r=new Map;for(let t=0,n=e.length;t{throw new Error(`Unable to parse media query "${e}"`)}}),n=[];for(;!t.endOfFile();)n.push(t.nextToken());return n}const a=[[r.TokenType.Ident,"max-color",0,0,{value:"max-color"}],[r.TokenType.Colon,":",0,0,void 0],[r.TokenType.Number,"2147477350",0,0,{value:2147477350,type:r.NumberType.Integer}]],s=[[r.TokenType.Ident,"color",0,0,{value:"color"}],[r.TokenType.Colon,":",0,0,void 0],[r.TokenType.Number,"2147477350",0,0,{value:2147477350,type:r.NumberType.Integer}]];function replaceTrueAndFalseTokens(e){let t,n;for(let o=0;o{throw new Error(`Unable to parse media query "${r.stringify(...s)}"`)}}),l=t.parseFromTokens(r.cloneTokens(s),{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${r.stringify(...s)}"`)}});for(let e=0;e[e,a]))}}function getCustomMedia(e,r,t){const n=new Map,o=new Map,a=[],s=collectCascadeLayerOrder(e);e.walkAtRules((e=>{if(!isProcessableCustomMediaRule(e))return;const r=parseCustomMedia(e.params);if(!r)return;if(0===r.truthy.length)return;const i=(u=s,(l=e).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?u.has(l.parent)?u.get(l.parent):-1:1/0);var l,u;if(i>=(o.get(r.name)??-1)&&(o.set(r.name,i),n.set(r.name,{truthy:r.truthy,falsy:r.falsy}),a.push(...r.dependsOn)),!t.preserve){const r=e.parent;e.remove(),removeEmptyAncestorBlocks(r)}}));const i=removeCyclicReferences(n,a);for(const t of i.values())e.warn(r,`@custom-media rules have cyclic dependencies for "${t}"`);return n}function removeEmptyAncestorBlocks(e){let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}function transformAtMediaListTokens(e,r){const n=t.parse(e,{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${e}"`)}}),o=n.map((e=>e.toString()));for(let e=0;et===e?n:{replaceWith:r}))}const s=transformComplexMediaQuery(t,r);if(s&&0!==s.length&&s[0].replaceWith!==a)return o.flatMap(((r,t)=>t===e?s:[{replaceWith:r}]))}return[]}function transformSimpleMediaQuery(e,r){if(!mediaQueryIsSimple(e))return null;let n=null;return e.walk((e=>{const o=e.node;if(!t.isMediaFeatureBoolean(o))return;const a=o.getName();if(!a.startsWith("--"))return!1;const s=r.get(a);return s?(n={replaceWith:s.truthy.map((e=>e.toString().trim())).join(",")},!1):void 0})),n}function transformComplexMediaQuery(e,r){let n=[];return e.walk((o=>{const i=o.node;if(!t.isMediaFeatureBoolean(i))return;const l=o.parent;if(!t.isMediaFeature(l))return;const u=i.getName();if(!u.startsWith("--"))return!1;const c=r.get(u);if(c){if(1===c.truthy.length&&mediaQueryIsSimple(c.truthy[0])){let r=null;if(c.truthy[0].walk((e=>{if(t.isMediaFeature(e.node))return r=e.node,!1})),r&&r.feature)return l.feature=r.feature,n=[{replaceWith:e.toString()}],!1}const r=t.newMediaFeaturePlain(a[0][4].value,a[2]);l.feature=r.feature;const o=e.toString(),i=t.newMediaFeaturePlain(s[0][4].value,s[2]);l.feature=i.feature;const u=e.toString();return n=[{replaceWith:o,encapsulateWith:c.truthy.map((e=>e.toString().trim())).join(",")},{replaceWith:u,encapsulateWith:c.falsy.map((e=>e.toString().trim())).join(",")}],!1}})),n}function mediaQueryIsSimple(e){if(t.isMediaQueryInvalid(e))return!1;if(t.isMediaQueryWithType(e))return!1;let r=!0;return e.walk((e=>{if(t.isMediaAnd(e.node)||t.isMediaOr(e.node)||t.isMediaNot(e.node)||t.isMediaConditionList(e.node)||t.isGeneralEnclosed(e.node))return r=!1,!1})),r}const creator=e=>{const r=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-media] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-media] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-media",prepare(){let e=new Map;return{Once:(t,{result:n})=>{e=getCustomMedia(t,n,{preserve:r})},AtRule:(t,{result:n})=>{if("media"!==t.name.toLowerCase())return;if(!t.params)return;if(!t.params.includes("--"))return;let o=[];try{o=transformAtMediaListTokens(t.params,e)}catch(e){return void t.warn(n,`Failed to parse @custom-media params with error message: "${e.message}"`)}if(!o||0===o.length)return;if(1===o.length){if(t.params.trim()===o[0].replaceWith.trim())return;return t.cloneBefore({params:o[0].replaceWith.trim()}),r?void 0:void t.remove()}if(!!!o.find((e=>!!e.encapsulateWith)))return t.cloneBefore({params:o.map((e=>e.replaceWith)).join(",").trim()}),void(r||t.remove());o.forEach((e=>{if(!e.encapsulateWith)return void t.cloneBefore({params:e.replaceWith.trim()});const r=t.clone({params:e.replaceWith}),n=t.clone({params:e.encapsulateWith.trim(),nodes:[]});r.parent=null,n.parent=null,n.append(r),t.before(n)})),r||t.remove()}}}}};creator.postcss=!0,module.exports=creator;
diff --git a/plugins/postcss-custom-media/dist/index.mjs b/plugins/postcss-custom-media/dist/index.mjs
index fee0e5809..2d96a7061 100644
--- a/plugins/postcss-custom-media/dist/index.mjs
+++ b/plugins/postcss-custom-media/dist/index.mjs
@@ -1 +1 @@
-import{tokenizer as e,TokenType as r,NumberType as t,cloneTokens as n,stringify as o}from"@csstools/css-tokenizer";import{parseFromTokens as a,parse as s,isMediaFeatureBoolean as i,isMediaFeature as l,newMediaFeaturePlain as u,isMediaQueryInvalid as c,isMediaQueryWithType as p,isMediaAnd as f,isMediaOr as m,isMediaNot as d,isMediaConditionList as h,isGeneralEnclosed as g}from"@csstools/media-query-list-parser";function collectCascadeLayerOrder(e){const r=new Map,t=new Map,n=[];e.walkAtRules((e=>{if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let o=[];if(e.nodes)o.push((a=e.params,s=1,a.trim()||"csstools-anon-layer--"+s++));else{if(!e.params.trim())return;o=e.params.split(",").map((e=>e.trim()))}var a,s;{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=t.get(r);e?(o=o.map((r=>e+"."+r)),r=r.parent):r=r.parent}}if(addLayerToModel(n,o),e.nodes){const n=o[0]+".csstools-implicit-layer";r.set(e,n),t.set(e,o[0])}}));for(const e of r.values())addLayerToModel(n,[e]);const o=n.map((e=>e.join("."))),a=new WeakMap;for(const[e,t]of r)a.set(e,o.indexOf(t));return a}function addLayerToModel(e,r){return r.forEach((r=>{const t=r.split(".");e:for(let r=0;r=a&&(o=r,a=s)}-1===o?e.push(n):e.splice(o+1,0,n)}})),e}const y=new Set(["scope","container","layer"]);function isProcessableCustomMediaRule(e){if("custom-media"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes("--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!y.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function removeCyclicReferences(e,r){const t=new Set;let n=r;for(;e.size>0;)try{toposort(Array.from(e.keys()),n);break}catch(r){if(!r._graphNode)throw r;e.delete(r._graphNode),t.add(r._graphNode),n=n.filter((e=>-1===e.indexOf(r._graphNode)))}return t}function toposort(e,r){let t=e.length;const n=new Array(t),o={};let a=t;const s=makeOutgoingEdges(r),i=makeNodesHash(e);for(r.forEach((function(e){if(!i.has(e[0])||!i.has(e[1]))throw new Error("Unknown token. Make sure to provide all tokens used in aliases.")}));a--;)o[a]||visit(e[a],a,new Set);return n;function visit(e,r,a){if(a.has(e)){const r=new Error("Cyclic dependency"+JSON.stringify(e));throw r._graphNode=e,r}if(!i.has(e))throw new Error("Found unknown token. Make sure to provided all involved tokens. Unknown token: "+JSON.stringify(e));if(o[r])return;o[r]=!0;let l=s.get(e)||new Set;if(l=Array.from(l),r=l.length){a.add(e);do{const e=l[--r];visit(e,i.get(e),a)}while(r);a.delete(e)}n[--t]=e}}function makeOutgoingEdges(e){const r=new Map;for(let t=0,n=e.length;t{throw new Error(`Unable to parse media query "${r}"`)}}),n=[];for(;!t.endOfFile();)n.push(t.nextToken());return n}const w=[[r.Ident,"max-color",0,0,{value:"max-color"}],[r.Colon,":",0,0,void 0],[r.Number,"2147477350",0,0,{value:2147477350,type:t.Integer}]],v=[[r.Ident,"color",0,0,{value:"color"}],[r.Colon,":",0,0,void 0],[r.Number,"2147477350",0,0,{value:2147477350,type:t.Integer}]];function replaceTrueAndFalseTokens(e){let t,n;for(let o=0;o{throw new Error(`Unable to parse media query "${o(...l)}"`)}}),c=a(n(l),{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${o(...l)}"`)}});for(let e=0;e[e,i]))}}function getCustomMedia(e,r,t){const n=new Map,o=new Map,a=[],s=collectCascadeLayerOrder(e);e.walkAtRules((e=>{if(!isProcessableCustomMediaRule(e))return;const r=parseCustomMedia(e.params);if(!r)return;if(0===r.truthy.length)return;const i=(u=s,(l=e).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?u.has(l.parent)?u.get(l.parent):-1:1/0);var l,u;if(i>=(o.get(r.name)??-1)&&(o.set(r.name,i),n.set(r.name,{truthy:r.truthy,falsy:r.falsy}),a.push(...r.dependsOn)),!t.preserve){const r=e.parent;e.remove(),removeEmptyAncestorBlocks(r)}}));const i=removeCyclicReferences(n,a);for(const t of i.values())e.warn(r,`@custom-media rules have cyclic dependencies for "${t}"`);return n}function removeEmptyAncestorBlocks(e){let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}function transformAtMediaListTokens(e,r){const t=s(e,{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${e}"`)}}),n=t.map((e=>e.toString()));for(let e=0;en===e?t:{replaceWith:r}))}const s=transformComplexMediaQuery(o,r);if(s&&0!==s.length&&s[0].replaceWith!==a)return n.flatMap(((r,t)=>t===e?s:[{replaceWith:r}]))}return[]}function transformSimpleMediaQuery(e,r){if(!mediaQueryIsSimple(e))return null;let t=null;return e.walk((e=>{const n=e.node;if(!i(n))return;const o=n.getName();if(!o.startsWith("--"))return!1;const a=r.get(o);return a?(t={replaceWith:a.truthy.map((e=>e.toString().trim())).join(",")},!1):void 0})),t}function transformComplexMediaQuery(e,r){let t=[];return e.walk((n=>{const o=n.node;if(!i(o))return;const a=n.parent;if(!l(a))return;const s=o.getName();if(!s.startsWith("--"))return!1;const c=r.get(s);if(c){if(1===c.truthy.length&&mediaQueryIsSimple(c.truthy[0])){let r=null;if(c.truthy[0].walk((e=>{if(l(e.node))return r=e.node,!1})),r&&r.feature)return a.feature=r.feature,t=[{replaceWith:e.toString()}],!1}const r=u(w[0][4].value,w[2]);a.feature=r.feature;const n=e.toString(),o=u(v[0][4].value,v[2]);a.feature=o.feature;const s=e.toString();return t=[{replaceWith:n,encapsulateWith:c.truthy.map((e=>e.toString().trim())).join(",")},{replaceWith:s,encapsulateWith:c.falsy.map((e=>e.toString().trim())).join(",")}],!1}})),t}function mediaQueryIsSimple(e){if(c(e))return!1;if(p(e))return!1;let r=!0;return e.walk((e=>{if(f(e.node)||m(e.node)||d(e.node)||h(e.node)||g(e.node))return r=!1,!1})),r}const creator=e=>{const r=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-media] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-media] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-media",prepare(){let e=new Map;return{Once:(t,{result:n})=>{e=getCustomMedia(t,n,{preserve:r})},AtRule:(t,{result:n})=>{if("media"!==t.name.toLowerCase())return;if(!t.params)return;if(!t.params.includes("--"))return;let o=[];try{o=transformAtMediaListTokens(t.params,e)}catch(e){return void t.warn(n,`Failed to parse @custom-media params with error message: "${e.message}"`)}if(!o||0===o.length)return;if(1===o.length){if(t.params.trim()===o[0].replaceWith.trim())return;return t.cloneBefore({params:o[0].replaceWith.trim()}),r?void 0:void t.remove()}if(!!!o.find((e=>!!e.encapsulateWith)))return t.cloneBefore({params:o.map((e=>e.replaceWith)).join(",").trim()}),void(r||t.remove());o.forEach((e=>{if(!e.encapsulateWith)return void t.cloneBefore({params:e.replaceWith.trim()});const r=t.clone({params:e.replaceWith}),n=t.clone({params:e.encapsulateWith.trim(),nodes:[]});r.parent=null,n.parent=null,n.append(r),t.before(n)})),r||t.remove()}}}}};creator.postcss=!0;export{creator as default};
+import{parse as e,addLayerToModel as r}from"@csstools/cascade-layer-name-parser";import{tokenizer as t,TokenType as n,NumberType as o,cloneTokens as a,stringify as s}from"@csstools/css-tokenizer";import{parseFromTokens as i,parse as l,isMediaFeatureBoolean as u,isMediaFeature as c,newMediaFeaturePlain as p,isMediaQueryInvalid as f,isMediaQueryWithType as m,isMediaAnd as d,isMediaOr as h,isMediaNot as y,isMediaConditionList as g,isGeneralEnclosed as w}from"@csstools/media-query-list-parser";const v=e("csstools-implicit-layer")[0];function collectCascadeLayerOrder(t){const n=new Map,o=new Map,a=[];t.walkAtRules((t=>{var s;if("layer"!==t.name.toLowerCase())return;{let e=t.parent;for(;e;){if("atrule"!==e.type||"layer"!==e.name.toLowerCase()){if(e===t.root())break;return}e=e.parent}}let i;if(t.nodes)i=normalizeLayerName(t.params,1);else{if(!t.params.trim())return;i=t.params}let l=e(i);if(null!=(s=l)&&s.length){{let e=t.parent;for(;e&&"atrule"===e.type&&"layer"===e.name.toLowerCase();){const r=o.get(e);r?(l=l.map((e=>r.concat(e))),e=e.parent):e=e.parent}}if(r(a,l),t.nodes){const e=l[0].concat(v);n.set(t,e),o.set(t,l[0])}}}));for(const e of n.values())r(a,[e]);const s=new WeakMap;for(const[e,r]of n)s.set(e,a.findIndex((e=>r.equal(e))));return s}function normalizeLayerName(e,r){return e.trim()?e:"csstools-anon-layer--"+r++}const k=new Set(["scope","container","layer"]);function isProcessableCustomMediaRule(e){if("custom-media"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes("--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!k.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function removeCyclicReferences(e,r){const t=new Set;let n=r;for(;e.size>0;)try{toposort(Array.from(e.keys()),n);break}catch(r){if(!r._graphNode)throw r;e.delete(r._graphNode),t.add(r._graphNode),n=n.filter((e=>-1===e.indexOf(r._graphNode)))}return t}function toposort(e,r){let t=e.length;const n=new Array(t),o={};let a=t;const s=makeOutgoingEdges(r),i=makeNodesHash(e);for(r.forEach((function(e){if(!i.has(e[0])||!i.has(e[1]))throw new Error("Unknown token. Make sure to provide all tokens used in aliases.")}));a--;)o[a]||visit(e[a],a,new Set);return n;function visit(e,r,a){if(a.has(e)){const r=new Error("Cyclic dependency"+JSON.stringify(e));throw r._graphNode=e,r}if(!i.has(e))throw new Error("Found unknown token. Make sure to provided all involved tokens. Unknown token: "+JSON.stringify(e));if(o[r])return;o[r]=!0;let l=s.get(e)||new Set;if(l=Array.from(l),r=l.length){a.add(e);do{const e=l[--r];visit(e,i.get(e),a)}while(r);a.delete(e)}n[--t]=e}}function makeOutgoingEdges(e){const r=new Map;for(let t=0,n=e.length;t{throw new Error(`Unable to parse media query "${e}"`)}}),n=[];for(;!r.endOfFile();)n.push(r.nextToken());return n}const C=[[n.Ident,"max-color",0,0,{value:"max-color"}],[n.Colon,":",0,0,void 0],[n.Number,"2147477350",0,0,{value:2147477350,type:o.Integer}]],M=[[n.Ident,"color",0,0,{value:"color"}],[n.Colon,":",0,0,void 0],[n.Number,"2147477350",0,0,{value:2147477350,type:o.Integer}]];function replaceTrueAndFalseTokens(e){let r,t;for(let o=0;o{throw new Error(`Unable to parse media query "${s(...l)}"`)}}),c=i(a(l),{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${s(...l)}"`)}});for(let e=0;e[e,o]))}}function getCustomMedia(e,r,t){const n=new Map,o=new Map,a=[],s=collectCascadeLayerOrder(e);e.walkAtRules((e=>{if(!isProcessableCustomMediaRule(e))return;const r=parseCustomMedia(e.params);if(!r)return;if(0===r.truthy.length)return;const i=(u=s,(l=e).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?u.has(l.parent)?u.get(l.parent):-1:1/0);var l,u;if(i>=(o.get(r.name)??-1)&&(o.set(r.name,i),n.set(r.name,{truthy:r.truthy,falsy:r.falsy}),a.push(...r.dependsOn)),!t.preserve){const r=e.parent;e.remove(),removeEmptyAncestorBlocks(r)}}));const i=removeCyclicReferences(n,a);for(const t of i.values())e.warn(r,`@custom-media rules have cyclic dependencies for "${t}"`);return n}function removeEmptyAncestorBlocks(e){let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}function transformAtMediaListTokens(e,r){const t=l(e,{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${e}"`)}}),n=t.map((e=>e.toString()));for(let e=0;en===e?t:{replaceWith:r}))}const s=transformComplexMediaQuery(o,r);if(s&&0!==s.length&&s[0].replaceWith!==a)return n.flatMap(((r,t)=>t===e?s:[{replaceWith:r}]))}return[]}function transformSimpleMediaQuery(e,r){if(!mediaQueryIsSimple(e))return null;let t=null;return e.walk((e=>{const n=e.node;if(!u(n))return;const o=n.getName();if(!o.startsWith("--"))return!1;const a=r.get(o);return a?(t={replaceWith:a.truthy.map((e=>e.toString().trim())).join(",")},!1):void 0})),t}function transformComplexMediaQuery(e,r){let t=[];return e.walk((n=>{const o=n.node;if(!u(o))return;const a=n.parent;if(!c(a))return;const s=o.getName();if(!s.startsWith("--"))return!1;const i=r.get(s);if(i){if(1===i.truthy.length&&mediaQueryIsSimple(i.truthy[0])){let r=null;if(i.truthy[0].walk((e=>{if(c(e.node))return r=e.node,!1})),r&&r.feature)return a.feature=r.feature,t=[{replaceWith:e.toString()}],!1}const r=p(C[0][4].value,C[2]);a.feature=r.feature;const n=e.toString(),o=p(M[0][4].value,M[2]);a.feature=o.feature;const s=e.toString();return t=[{replaceWith:n,encapsulateWith:i.truthy.map((e=>e.toString().trim())).join(",")},{replaceWith:s,encapsulateWith:i.falsy.map((e=>e.toString().trim())).join(",")}],!1}})),t}function mediaQueryIsSimple(e){if(f(e))return!1;if(m(e))return!1;let r=!0;return e.walk((e=>{if(d(e.node)||h(e.node)||y(e.node)||g(e.node)||w(e.node))return r=!1,!1})),r}const creator=e=>{const r=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-media] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-media] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-media",prepare(){let e=new Map;return{Once:(t,{result:n})=>{e=getCustomMedia(t,n,{preserve:r})},AtRule:(t,{result:n})=>{if("media"!==t.name.toLowerCase())return;if(!t.params)return;if(!t.params.includes("--"))return;let o=[];try{o=transformAtMediaListTokens(t.params,e)}catch(e){return void t.warn(n,`Failed to parse @custom-media params with error message: "${e.message}"`)}if(!o||0===o.length)return;if(1===o.length){if(t.params.trim()===o[0].replaceWith.trim())return;return t.cloneBefore({params:o[0].replaceWith.trim()}),r?void 0:void t.remove()}if(!!!o.find((e=>!!e.encapsulateWith)))return t.cloneBefore({params:o.map((e=>e.replaceWith)).join(",").trim()}),void(r||t.remove());o.forEach((e=>{if(!e.encapsulateWith)return void t.cloneBefore({params:e.replaceWith.trim()});const r=t.clone({params:e.replaceWith}),n=t.clone({params:e.encapsulateWith.trim(),nodes:[]});r.parent=null,n.parent=null,n.append(r),t.before(n)})),r||t.remove()}}}}};creator.postcss=!0;export{creator as default};
diff --git a/plugins/postcss-custom-media/package.json b/plugins/postcss-custom-media/package.json
index fe297c1aa..5cc7b0c50 100644
--- a/plugins/postcss-custom-media/package.json
+++ b/plugins/postcss-custom-media/package.json
@@ -45,6 +45,7 @@
"dist"
],
"dependencies": {
+ "@csstools/cascade-layer-name-parser": "^1.0.0",
"@csstools/css-parser-algorithms": "^1.0.0",
"@csstools/css-tokenizer": "^1.0.0",
"@csstools/media-query-list-parser": "^1.0.0"
diff --git a/plugins/postcss-custom-media/src/cascade-layers.ts b/plugins/postcss-custom-media/src/cascade-layers.ts
index 4d60ebb98..3b95579cb 100644
--- a/plugins/postcss-custom-media/src/cascade-layers.ts
+++ b/plugins/postcss-custom-media/src/cascade-layers.ts
@@ -1,10 +1,13 @@
import type { AtRule, Container, Document, Node, Root } from 'postcss';
+import { LayerName, parse as parseCascadeLayerNames, addLayerToModel } from '@csstools/cascade-layer-name-parser';
+
+const implicitLayerNameForCloning = parseCascadeLayerNames('csstools-implicit-layer')[0];
export function collectCascadeLayerOrder(root: Root) {
- const references: Map = new Map();
- const referencesForLayerNames: Map = new Map();
+ const references: Map = new Map();
+ const referencesForLayerNames: Map = new Map();
- const layers: Array> = [];
+ const layers: Array = [];
const anonLayerCounter = 1;
root.walkAtRules((node) => {
@@ -32,17 +35,20 @@ export function collectCascadeLayerOrder(root: Root) {
}
}
- let currentLayerNames = [];
+ let layerParams;
if (node.nodes) { // @layer { .foo {} }
- currentLayerNames.push(normalizeLayerName(node.params, anonLayerCounter));
+ layerParams = normalizeLayerName(node.params, anonLayerCounter);
} else if (node.params.trim()) { // @layer a, b;
- currentLayerNames = node.params.split(',').map((layerName) => {
- return layerName.trim();
- });
+ layerParams = node.params;
} else { // @layer;
return;
}
+ let currentLayerNames = parseCascadeLayerNames(layerParams);
+ if (!currentLayerNames?.length) {
+ return;
+ }
+
{
// Stitch the layer names of the current node together with those of ancestors.
// @layer foo { @layer bar { .any {} } }
@@ -56,7 +62,7 @@ export function collectCascadeLayerOrder(root: Root) {
}
currentLayerNames = currentLayerNames.map((layerName) => {
- return parentLayerName + '.' + layerName;
+ return parentLayerName.concat(layerName);
});
parent = parent.parent;
@@ -75,7 +81,7 @@ export function collectCascadeLayerOrder(root: Root) {
// 3. use the real layer to resolve other real layer names
// 4. use the implicit layer later
- const implicitLayerName = currentLayerNames[0] + '.' + 'csstools-implicit-layer';
+ const implicitLayerName = currentLayerNames[0].concat(implicitLayerNameForCloning);
references.set(node, implicitLayerName);
referencesForLayerNames.set(node, currentLayerNames[0]);
}
@@ -87,11 +93,11 @@ export function collectCascadeLayerOrder(root: Root) {
addLayerToModel(layers, [layerName]);
}
- const finalLayers = layers.map((x) => x.join('.'));
-
const out: WeakMap = new WeakMap();
for (const [node, layerName] of references) {
- out.set(node, finalLayers.indexOf(layerName));
+ out.set(node, layers.findIndex((x) => {
+ return layerName.equal(x);
+ }));
}
return out;
@@ -113,68 +119,9 @@ export function cascadeLayerNumberForNode(node: Node, layers: WeakMap {
- const allLayerNameParts = layerName.split('.');
-
- ALL_LAYER_NAME_PARTS_LOOP: for (let x = 0; x < allLayerNameParts.length; x++) {
- const layerNameParts = allLayerNameParts.slice(0, x + 1);
-
- let layerWithMostEqualSegments = -1;
- let mostEqualSegments = 0;
-
- for (let i = 0; i < layers.length; i++) {
- const existingLayerParts = layers[i];
-
- let numberOfEqualSegments = 0;
-
- LAYER_PARTS_LOOP: for (let j = 0; j < existingLayerParts.length; j++) {
- const existingLayerPart = existingLayerParts[j];
- const layerPart = layerNameParts[j];
-
- if (layerPart === existingLayerPart && (j + 1) === layerNameParts.length) {
- continue ALL_LAYER_NAME_PARTS_LOOP; // layer already exists in model
- }
-
- if (layerPart === existingLayerPart) {
- numberOfEqualSegments++;
- continue;
- }
-
- if (layerPart !== existingLayerPart) {
- break LAYER_PARTS_LOOP;
- }
- }
-
- if (numberOfEqualSegments >= mostEqualSegments) {
- layerWithMostEqualSegments = i;
- mostEqualSegments = numberOfEqualSegments;
- }
- }
-
- if (layerWithMostEqualSegments === -1) {
- layers.push(layerNameParts);
- } else {
- layers.splice(layerWithMostEqualSegments+1, 0, layerNameParts);
- }
- }
- });
+ if (layerName.trim()) {
+ return layerName;
+ }
- return layers;
+ return `csstools-anon-layer--${counter++}`;
}
diff --git a/plugins/postcss-custom-media/test/cascade-layers.css b/plugins/postcss-custom-media/test/cascade-layers.css
index 235fb35c4..b7b511491 100644
--- a/plugins/postcss-custom-media/test/cascade-layers.css
+++ b/plugins/postcss-custom-media/test/cascade-layers.css
@@ -1,4 +1,5 @@
@layer extensions, components;
+@layer extensions.one,,invalid-layer-names..foo;
@custom-media --desktop (min-width:1280px) and (debug-layer: unlayered);
diff --git a/plugins/postcss-custom-media/test/cascade-layers.expect.css b/plugins/postcss-custom-media/test/cascade-layers.expect.css
index 8519dc032..95f7e37c3 100644
--- a/plugins/postcss-custom-media/test/cascade-layers.expect.css
+++ b/plugins/postcss-custom-media/test/cascade-layers.expect.css
@@ -1,4 +1,5 @@
@layer extensions, components;
+@layer extensions.one,,invalid-layer-names..foo;
@layer extensions {
@layer one, two
diff --git a/plugins/postcss-custom-properties/dist/index.cjs b/plugins/postcss-custom-properties/dist/index.cjs
index 398f7670b..e3d9e55be 100644
--- a/plugins/postcss-custom-properties/dist/index.cjs
+++ b/plugins/postcss-custom-properties/dist/index.cjs
@@ -1 +1 @@
-"use strict";var e=require("postcss-value-parser");function collectCascadeLayerOrder(e){const r=new Map,t=new Map,o=[];e.walkAtRules((e=>{if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let s=[];if(e.nodes)s.push((n=e.params,a=1,n.trim()||"csstools-anon-layer--"+a++));else{if(!e.params.trim())return;s=e.params.split(",").map((e=>e.trim()))}var n,a;{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=t.get(r);e?(s=s.map((r=>e+"."+r)),r=r.parent):r=r.parent}}if(addLayerToModel(o,s),e.nodes){const o=s[0]+".csstools-implicit-layer";r.set(e,o),t.set(e,s[0])}}));for(const e of r.values())addLayerToModel(o,[e]);const s=o.map((e=>e.join("."))),n=new WeakMap;for(const[e,t]of r)n.set(e,s.indexOf(t));return n}function cascadeLayerNumberForNode(e,r){return e.parent&&"atrule"===e.parent.type&&"layer"===e.parent.name.toLowerCase()?r.has(e.parent)?r.get(e.parent):-1:1/0}function addLayerToModel(e,r){return r.forEach((r=>{const t=r.split(".");e:for(let r=0;r=n&&(s=r,n=a)}-1===s?e.push(o):e.splice(s+1,0,o)}})),e}const r=/(!\s*)?postcss-custom-properties:\s*off\b/i,t=new WeakMap;function isBlockIgnored(e){if(!e||!e.nodes)return!1;if(t.has(e))return t.get(e);const o=e.some((e=>isIgnoreComment(e,r)));return t.set(e,o),o}const o=/(!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function isDeclarationIgnored(e){return!!e&&(!!isBlockIgnored(e.parent)||isIgnoreComment(e.prev(),o))}function isIgnoreComment(e,r){return e&&"comment"===e.type&&r.test(e.text)}const s=new Set(["layer"]);function isProcessableRule(e){if(!isHtmlRule(e)&&!isRootRule(e))return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!s.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}const n=/^html$/i,a=/^:root$/i;function isHtmlRule(e){return e.selectors.some((e=>n.test(e)))&&e.nodes&&e.nodes.length}function isRootRule(e){return e.selectors.some((e=>a.test(e)))&&e.nodes&&e.nodes.length}function getCustomPropertiesFromRoot(r){const t=new Map,o=new Map,s=new Map,n=new Map,a=new Map,l=collectCascadeLayerOrder(r);r.walkRules((e=>{isProcessableRule(e)&&(isBlockIgnored(e)||(isHtmlRule(e)?e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(n.get(e.prop)??-1)&&(n.set(e.prop,r),t.set(e.prop,e.value))})):isRootRule(e)&&e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(a.get(e.prop)??-1)&&(a.set(e.prop,r),o.set(e.prop,e.value))}))))}));for(const[e,r]of t.entries())s.set(e,r);for(const[e,r]of o.entries())s.set(e,r);const i=new Map;for(const[r,t]of s.entries())i.set(r,e(t));return i}function transformValueAST(e,r){return e.nodes&&e.nodes.length&&e.nodes.slice().forEach((t=>{if(isVarFunction(t)){const[o,...s]=t.nodes.filter((e=>"div"!==e.type)),{value:n}=o,a=e.nodes.indexOf(t);if(r.has(n)){const t=r.get(n).nodes;reTransformValueAST({nodes:t},r,n),a>-1&&e.nodes.splice(a,1,...t)}else s.length&&(a>-1&&e.nodes.splice(a,1,...t.nodes.slice(t.nodes.indexOf(s[0]))),transformValueAST(e,r))}else transformValueAST(t,r)})),e.toString()}function reTransformValueAST(e,r,t){const o=new Map(r);return o.delete(t),transformValueAST(e,o)}const l=/^var$/i,isVarFunction=e=>"function"===e.type&&l.test(e.value)&&Object(e.nodes).length>0;var transformProperties=(r,t,o)=>{if(isTransformableDecl(r)&&!isDeclarationIgnored(r)){const s=r.value;let n=transformValueAST(e(s),t);const a=new Set;for(;n.includes("--")&&n.toLowerCase().includes("var(")&&!a.has(n);){a.add(n);n=transformValueAST(e(n),t)}if(n!==s){if(parentHasExactFallback(r,n))return void(o.preserve||r.remove());if(o.preserve){const e=r.cloneBefore({value:n});hasTrailingComment(e)&&(e.raws.value.value=e.value.replace(i,"$1"),e.raws.value.raw=e.raws.value.value+e.raws.value.raw.replace(i,"$2"))}else r.value=n,hasTrailingComment(r)&&(r.raws.value.value=r.value.replace(i,"$1"),r.raws.value.raw=r.raws.value.value+r.raws.value.raw.replace(i,"$2"))}}};const isTransformableDecl=e=>!e.variable&&e.value.includes("--")&&e.value.toLowerCase().includes("var("),hasTrailingComment=e=>"value"in Object(Object(e.raws).value)&&"raw"in e.raws.value&&i.test(e.raws.value.raw),i=/^([\W\w]+)(\s*\/\*[\W\w]+?\*\/)$/;function parentHasExactFallback(e,r){if(!e||!e.parent)return!1;let t=!1;const o=e.parent.index(e);return e.parent.each(((s,n)=>s!==e&&(!(n>=o)&&void("decl"===s.type&&s.prop.toLowerCase()===e.prop.toLowerCase()&&s.value===r&&(t=!0))))),t}const creator=r=>{const t=!("preserve"in Object(r))||Boolean(r.preserve);if("importFrom"in Object(r))throw new Error('[postcss-custom-properties] "importFrom" is no longer supported');if("exportTo"in Object(r))throw new Error('[postcss-custom-properties] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-properties",prepare:()=>{let r=new Map;return{Once:e=>{r=getCustomPropertiesFromRoot(e)},Declaration:o=>{let s=r;if(t&&o.parent){let t=!1;o.parent.each((n=>{o!==n&&"decl"===n.type&&n.variable&&!isDeclarationIgnored(n)&&(t||(s=new Map(r),t=!0),"initial"!==n.value.toLowerCase().trim()?s.set(n.prop,e(n.value)):s.delete(n.prop))}))}transformProperties(o,s,{preserve:t})}}}}};creator.postcss=!0,module.exports=creator;
+"use strict";var e=require("postcss-value-parser"),r=require("@csstools/cascade-layer-name-parser");const t=r.parse("csstools-implicit-layer")[0];function collectCascadeLayerOrder(e){const o=new Map,a=new Map,s=[];e.walkAtRules((e=>{var n;if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let l;if(e.nodes)l=normalizeLayerName(e.params,1);else{if(!e.params.trim())return;l=e.params}let i=r.parse(l);if(null!=(n=i)&&n.length){{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=a.get(r);e?(i=i.map((r=>e.concat(r))),r=r.parent):r=r.parent}}if(r.addLayerToModel(s,i),e.nodes){const r=i[0].concat(t);o.set(e,r),a.set(e,i[0])}}}));for(const e of o.values())r.addLayerToModel(s,[e]);const n=new WeakMap;for(const[e,r]of o)n.set(e,s.findIndex((e=>r.equal(e))));return n}function cascadeLayerNumberForNode(e,r){return e.parent&&"atrule"===e.parent.type&&"layer"===e.parent.name.toLowerCase()?r.has(e.parent)?r.get(e.parent):-1:1/0}function normalizeLayerName(e,r){return e.trim()?e:"csstools-anon-layer--"+r++}const o=/(!\s*)?postcss-custom-properties:\s*off\b/i,a=new WeakMap;function isBlockIgnored(e){if(!e||!e.nodes)return!1;if(a.has(e))return a.get(e);const r=e.some((e=>isIgnoreComment(e,o)));return a.set(e,r),r}const s=/(!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function isDeclarationIgnored(e){return!!e&&(!!isBlockIgnored(e.parent)||isIgnoreComment(e.prev(),s))}function isIgnoreComment(e,r){return e&&"comment"===e.type&&r.test(e.text)}const n=new Set(["layer"]);function isProcessableRule(e){if(!isHtmlRule(e)&&!isRootRule(e))return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!n.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}const l=/^html$/i,i=/^:root$/i;function isHtmlRule(e){return e.selectors.some((e=>l.test(e)))&&e.nodes&&e.nodes.length}function isRootRule(e){return e.selectors.some((e=>i.test(e)))&&e.nodes&&e.nodes.length}function getCustomPropertiesFromRoot(r){const t=new Map,o=new Map,a=new Map,s=new Map,n=new Map,l=collectCascadeLayerOrder(r);r.walkRules((e=>{isProcessableRule(e)&&(isBlockIgnored(e)||(isHtmlRule(e)?e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(s.get(e.prop)??-1)&&(s.set(e.prop,r),t.set(e.prop,e.value))})):isRootRule(e)&&e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(n.get(e.prop)??-1)&&(n.set(e.prop,r),o.set(e.prop,e.value))}))))}));for(const[e,r]of t.entries())a.set(e,r);for(const[e,r]of o.entries())a.set(e,r);const i=new Map;for(const[r,t]of a.entries())i.set(r,e(t));return i}function transformValueAST(e,r){return e.nodes&&e.nodes.length&&e.nodes.slice().forEach((t=>{if(isVarFunction(t)){const[o,...a]=t.nodes.filter((e=>"div"!==e.type)),{value:s}=o,n=e.nodes.indexOf(t);if(r.has(s)){const t=r.get(s).nodes;reTransformValueAST({nodes:t},r,s),n>-1&&e.nodes.splice(n,1,...t)}else a.length&&(n>-1&&e.nodes.splice(n,1,...t.nodes.slice(t.nodes.indexOf(a[0]))),transformValueAST(e,r))}else transformValueAST(t,r)})),e.toString()}function reTransformValueAST(e,r,t){const o=new Map(r);return o.delete(t),transformValueAST(e,o)}const c=/^var$/i,isVarFunction=e=>"function"===e.type&&c.test(e.value)&&Object(e.nodes).length>0;var transformProperties=(r,t,o)=>{if(isTransformableDecl(r)&&!isDeclarationIgnored(r)){const a=r.value;let s=transformValueAST(e(a),t);const n=new Set;for(;s.includes("--")&&s.toLowerCase().includes("var(")&&!n.has(s);){n.add(s);s=transformValueAST(e(s),t)}if(s!==a){if(parentHasExactFallback(r,s))return void(o.preserve||r.remove());if(o.preserve){const e=r.cloneBefore({value:s});hasTrailingComment(e)&&(e.raws.value.value=e.value.replace(u,"$1"),e.raws.value.raw=e.raws.value.value+e.raws.value.raw.replace(u,"$2"))}else r.value=s,hasTrailingComment(r)&&(r.raws.value.value=r.value.replace(u,"$1"),r.raws.value.raw=r.raws.value.value+r.raws.value.raw.replace(u,"$2"))}}};const isTransformableDecl=e=>!e.variable&&e.value.includes("--")&&e.value.toLowerCase().includes("var("),hasTrailingComment=e=>"value"in Object(Object(e.raws).value)&&"raw"in e.raws.value&&u.test(e.raws.value.raw),u=/^([\W\w]+)(\s*\/\*[\W\w]+?\*\/)$/;function parentHasExactFallback(e,r){if(!e||!e.parent)return!1;let t=!1;const o=e.parent.index(e);return e.parent.each(((a,s)=>a!==e&&(!(s>=o)&&void("decl"===a.type&&a.prop.toLowerCase()===e.prop.toLowerCase()&&a.value===r&&(t=!0))))),t}const creator=r=>{const t=!("preserve"in Object(r))||Boolean(r.preserve);if("importFrom"in Object(r))throw new Error('[postcss-custom-properties] "importFrom" is no longer supported');if("exportTo"in Object(r))throw new Error('[postcss-custom-properties] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-properties",prepare:()=>{let r=new Map;return{Once:e=>{r=getCustomPropertiesFromRoot(e)},Declaration:o=>{let a=r;if(t&&o.parent){let t=!1;o.parent.each((s=>{o!==s&&"decl"===s.type&&s.variable&&!isDeclarationIgnored(s)&&(t||(a=new Map(r),t=!0),"initial"!==s.value.toLowerCase().trim()?a.set(s.prop,e(s.value)):a.delete(s.prop))}))}transformProperties(o,a,{preserve:t})}}}}};creator.postcss=!0,module.exports=creator;
diff --git a/plugins/postcss-custom-properties/dist/index.mjs b/plugins/postcss-custom-properties/dist/index.mjs
index 43011d851..71d58176a 100644
--- a/plugins/postcss-custom-properties/dist/index.mjs
+++ b/plugins/postcss-custom-properties/dist/index.mjs
@@ -1 +1 @@
-import e from"postcss-value-parser";function collectCascadeLayerOrder(e){const r=new Map,t=new Map,o=[];e.walkAtRules((e=>{if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let s=[];if(e.nodes)s.push((n=e.params,a=1,n.trim()||"csstools-anon-layer--"+a++));else{if(!e.params.trim())return;s=e.params.split(",").map((e=>e.trim()))}var n,a;{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=t.get(r);e?(s=s.map((r=>e+"."+r)),r=r.parent):r=r.parent}}if(addLayerToModel(o,s),e.nodes){const o=s[0]+".csstools-implicit-layer";r.set(e,o),t.set(e,s[0])}}));for(const e of r.values())addLayerToModel(o,[e]);const s=o.map((e=>e.join("."))),n=new WeakMap;for(const[e,t]of r)n.set(e,s.indexOf(t));return n}function cascadeLayerNumberForNode(e,r){return e.parent&&"atrule"===e.parent.type&&"layer"===e.parent.name.toLowerCase()?r.has(e.parent)?r.get(e.parent):-1:1/0}function addLayerToModel(e,r){return r.forEach((r=>{const t=r.split(".");e:for(let r=0;r=n&&(s=r,n=a)}-1===s?e.push(o):e.splice(s+1,0,o)}})),e}const r=/(!\s*)?postcss-custom-properties:\s*off\b/i,t=new WeakMap;function isBlockIgnored(e){if(!e||!e.nodes)return!1;if(t.has(e))return t.get(e);const o=e.some((e=>isIgnoreComment(e,r)));return t.set(e,o),o}const o=/(!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function isDeclarationIgnored(e){return!!e&&(!!isBlockIgnored(e.parent)||isIgnoreComment(e.prev(),o))}function isIgnoreComment(e,r){return e&&"comment"===e.type&&r.test(e.text)}const s=new Set(["layer"]);function isProcessableRule(e){if(!isHtmlRule(e)&&!isRootRule(e))return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!s.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}const n=/^html$/i,a=/^:root$/i;function isHtmlRule(e){return e.selectors.some((e=>n.test(e)))&&e.nodes&&e.nodes.length}function isRootRule(e){return e.selectors.some((e=>a.test(e)))&&e.nodes&&e.nodes.length}function getCustomPropertiesFromRoot(r){const t=new Map,o=new Map,s=new Map,n=new Map,a=new Map,l=collectCascadeLayerOrder(r);r.walkRules((e=>{isProcessableRule(e)&&(isBlockIgnored(e)||(isHtmlRule(e)?e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(n.get(e.prop)??-1)&&(n.set(e.prop,r),t.set(e.prop,e.value))})):isRootRule(e)&&e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(a.get(e.prop)??-1)&&(a.set(e.prop,r),o.set(e.prop,e.value))}))))}));for(const[e,r]of t.entries())s.set(e,r);for(const[e,r]of o.entries())s.set(e,r);const i=new Map;for(const[r,t]of s.entries())i.set(r,e(t));return i}function transformValueAST(e,r){return e.nodes&&e.nodes.length&&e.nodes.slice().forEach((t=>{if(isVarFunction(t)){const[o,...s]=t.nodes.filter((e=>"div"!==e.type)),{value:n}=o,a=e.nodes.indexOf(t);if(r.has(n)){const t=r.get(n).nodes;reTransformValueAST({nodes:t},r,n),a>-1&&e.nodes.splice(a,1,...t)}else s.length&&(a>-1&&e.nodes.splice(a,1,...t.nodes.slice(t.nodes.indexOf(s[0]))),transformValueAST(e,r))}else transformValueAST(t,r)})),e.toString()}function reTransformValueAST(e,r,t){const o=new Map(r);return o.delete(t),transformValueAST(e,o)}const l=/^var$/i,isVarFunction=e=>"function"===e.type&&l.test(e.value)&&Object(e.nodes).length>0;var transformProperties=(r,t,o)=>{if(isTransformableDecl(r)&&!isDeclarationIgnored(r)){const s=r.value;let n=transformValueAST(e(s),t);const a=new Set;for(;n.includes("--")&&n.toLowerCase().includes("var(")&&!a.has(n);){a.add(n);n=transformValueAST(e(n),t)}if(n!==s){if(parentHasExactFallback(r,n))return void(o.preserve||r.remove());if(o.preserve){const e=r.cloneBefore({value:n});hasTrailingComment(e)&&(e.raws.value.value=e.value.replace(i,"$1"),e.raws.value.raw=e.raws.value.value+e.raws.value.raw.replace(i,"$2"))}else r.value=n,hasTrailingComment(r)&&(r.raws.value.value=r.value.replace(i,"$1"),r.raws.value.raw=r.raws.value.value+r.raws.value.raw.replace(i,"$2"))}}};const isTransformableDecl=e=>!e.variable&&e.value.includes("--")&&e.value.toLowerCase().includes("var("),hasTrailingComment=e=>"value"in Object(Object(e.raws).value)&&"raw"in e.raws.value&&i.test(e.raws.value.raw),i=/^([\W\w]+)(\s*\/\*[\W\w]+?\*\/)$/;function parentHasExactFallback(e,r){if(!e||!e.parent)return!1;let t=!1;const o=e.parent.index(e);return e.parent.each(((s,n)=>s!==e&&(!(n>=o)&&void("decl"===s.type&&s.prop.toLowerCase()===e.prop.toLowerCase()&&s.value===r&&(t=!0))))),t}const creator=r=>{const t=!("preserve"in Object(r))||Boolean(r.preserve);if("importFrom"in Object(r))throw new Error('[postcss-custom-properties] "importFrom" is no longer supported');if("exportTo"in Object(r))throw new Error('[postcss-custom-properties] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-properties",prepare:()=>{let r=new Map;return{Once:e=>{r=getCustomPropertiesFromRoot(e)},Declaration:o=>{let s=r;if(t&&o.parent){let t=!1;o.parent.each((n=>{o!==n&&"decl"===n.type&&n.variable&&!isDeclarationIgnored(n)&&(t||(s=new Map(r),t=!0),"initial"!==n.value.toLowerCase().trim()?s.set(n.prop,e(n.value)):s.delete(n.prop))}))}transformProperties(o,s,{preserve:t})}}}}};creator.postcss=!0;export{creator as default};
+import e from"postcss-value-parser";import{parse as r,addLayerToModel as t}from"@csstools/cascade-layer-name-parser";const o=r("csstools-implicit-layer")[0];function collectCascadeLayerOrder(e){const a=new Map,s=new Map,n=[];e.walkAtRules((e=>{var l;if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let i;if(e.nodes)i=normalizeLayerName(e.params,1);else{if(!e.params.trim())return;i=e.params}let c=r(i);if(null!=(l=c)&&l.length){{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=s.get(r);e?(c=c.map((r=>e.concat(r))),r=r.parent):r=r.parent}}if(t(n,c),e.nodes){const r=c[0].concat(o);a.set(e,r),s.set(e,c[0])}}}));for(const e of a.values())t(n,[e]);const l=new WeakMap;for(const[e,r]of a)l.set(e,n.findIndex((e=>r.equal(e))));return l}function cascadeLayerNumberForNode(e,r){return e.parent&&"atrule"===e.parent.type&&"layer"===e.parent.name.toLowerCase()?r.has(e.parent)?r.get(e.parent):-1:1/0}function normalizeLayerName(e,r){return e.trim()?e:"csstools-anon-layer--"+r++}const a=/(!\s*)?postcss-custom-properties:\s*off\b/i,s=new WeakMap;function isBlockIgnored(e){if(!e||!e.nodes)return!1;if(s.has(e))return s.get(e);const r=e.some((e=>isIgnoreComment(e,a)));return s.set(e,r),r}const n=/(!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function isDeclarationIgnored(e){return!!e&&(!!isBlockIgnored(e.parent)||isIgnoreComment(e.prev(),n))}function isIgnoreComment(e,r){return e&&"comment"===e.type&&r.test(e.text)}const l=new Set(["layer"]);function isProcessableRule(e){if(!isHtmlRule(e)&&!isRootRule(e))return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!l.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}const i=/^html$/i,c=/^:root$/i;function isHtmlRule(e){return e.selectors.some((e=>i.test(e)))&&e.nodes&&e.nodes.length}function isRootRule(e){return e.selectors.some((e=>c.test(e)))&&e.nodes&&e.nodes.length}function getCustomPropertiesFromRoot(r){const t=new Map,o=new Map,a=new Map,s=new Map,n=new Map,l=collectCascadeLayerOrder(r);r.walkRules((e=>{isProcessableRule(e)&&(isBlockIgnored(e)||(isHtmlRule(e)?e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(s.get(e.prop)??-1)&&(s.set(e.prop,r),t.set(e.prop,e.value))})):isRootRule(e)&&e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(n.get(e.prop)??-1)&&(n.set(e.prop,r),o.set(e.prop,e.value))}))))}));for(const[e,r]of t.entries())a.set(e,r);for(const[e,r]of o.entries())a.set(e,r);const i=new Map;for(const[r,t]of a.entries())i.set(r,e(t));return i}function transformValueAST(e,r){return e.nodes&&e.nodes.length&&e.nodes.slice().forEach((t=>{if(isVarFunction(t)){const[o,...a]=t.nodes.filter((e=>"div"!==e.type)),{value:s}=o,n=e.nodes.indexOf(t);if(r.has(s)){const t=r.get(s).nodes;reTransformValueAST({nodes:t},r,s),n>-1&&e.nodes.splice(n,1,...t)}else a.length&&(n>-1&&e.nodes.splice(n,1,...t.nodes.slice(t.nodes.indexOf(a[0]))),transformValueAST(e,r))}else transformValueAST(t,r)})),e.toString()}function reTransformValueAST(e,r,t){const o=new Map(r);return o.delete(t),transformValueAST(e,o)}const u=/^var$/i,isVarFunction=e=>"function"===e.type&&u.test(e.value)&&Object(e.nodes).length>0;var transformProperties=(r,t,o)=>{if(isTransformableDecl(r)&&!isDeclarationIgnored(r)){const a=r.value;let s=transformValueAST(e(a),t);const n=new Set;for(;s.includes("--")&&s.toLowerCase().includes("var(")&&!n.has(s);){n.add(s);s=transformValueAST(e(s),t)}if(s!==a){if(parentHasExactFallback(r,s))return void(o.preserve||r.remove());if(o.preserve){const e=r.cloneBefore({value:s});hasTrailingComment(e)&&(e.raws.value.value=e.value.replace(p,"$1"),e.raws.value.raw=e.raws.value.value+e.raws.value.raw.replace(p,"$2"))}else r.value=s,hasTrailingComment(r)&&(r.raws.value.value=r.value.replace(p,"$1"),r.raws.value.raw=r.raws.value.value+r.raws.value.raw.replace(p,"$2"))}}};const isTransformableDecl=e=>!e.variable&&e.value.includes("--")&&e.value.toLowerCase().includes("var("),hasTrailingComment=e=>"value"in Object(Object(e.raws).value)&&"raw"in e.raws.value&&p.test(e.raws.value.raw),p=/^([\W\w]+)(\s*\/\*[\W\w]+?\*\/)$/;function parentHasExactFallback(e,r){if(!e||!e.parent)return!1;let t=!1;const o=e.parent.index(e);return e.parent.each(((a,s)=>a!==e&&(!(s>=o)&&void("decl"===a.type&&a.prop.toLowerCase()===e.prop.toLowerCase()&&a.value===r&&(t=!0))))),t}const creator=r=>{const t=!("preserve"in Object(r))||Boolean(r.preserve);if("importFrom"in Object(r))throw new Error('[postcss-custom-properties] "importFrom" is no longer supported');if("exportTo"in Object(r))throw new Error('[postcss-custom-properties] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-properties",prepare:()=>{let r=new Map;return{Once:e=>{r=getCustomPropertiesFromRoot(e)},Declaration:o=>{let a=r;if(t&&o.parent){let t=!1;o.parent.each((s=>{o!==s&&"decl"===s.type&&s.variable&&!isDeclarationIgnored(s)&&(t||(a=new Map(r),t=!0),"initial"!==s.value.toLowerCase().trim()?a.set(s.prop,e(s.value)):a.delete(s.prop))}))}transformProperties(o,a,{preserve:t})}}}}};creator.postcss=!0;export{creator as default};
diff --git a/plugins/postcss-custom-properties/package.json b/plugins/postcss-custom-properties/package.json
index e68d7003b..e833c9b13 100644
--- a/plugins/postcss-custom-properties/package.json
+++ b/plugins/postcss-custom-properties/package.json
@@ -32,6 +32,9 @@
"index.d.ts"
],
"dependencies": {
+ "@csstools/cascade-layer-name-parser": "^1.0.0",
+ "@csstools/css-parser-algorithms": "^1.0.0",
+ "@csstools/css-tokenizer": "^1.0.0",
"postcss-value-parser": "^4.2.0"
},
"peerDependencies": {
diff --git a/plugins/postcss-custom-properties/src/cascade-layers.ts b/plugins/postcss-custom-properties/src/cascade-layers.ts
index 4d60ebb98..3b95579cb 100644
--- a/plugins/postcss-custom-properties/src/cascade-layers.ts
+++ b/plugins/postcss-custom-properties/src/cascade-layers.ts
@@ -1,10 +1,13 @@
import type { AtRule, Container, Document, Node, Root } from 'postcss';
+import { LayerName, parse as parseCascadeLayerNames, addLayerToModel } from '@csstools/cascade-layer-name-parser';
+
+const implicitLayerNameForCloning = parseCascadeLayerNames('csstools-implicit-layer')[0];
export function collectCascadeLayerOrder(root: Root) {
- const references: Map = new Map();
- const referencesForLayerNames: Map = new Map();
+ const references: Map = new Map();
+ const referencesForLayerNames: Map = new Map();
- const layers: Array> = [];
+ const layers: Array = [];
const anonLayerCounter = 1;
root.walkAtRules((node) => {
@@ -32,17 +35,20 @@ export function collectCascadeLayerOrder(root: Root) {
}
}
- let currentLayerNames = [];
+ let layerParams;
if (node.nodes) { // @layer { .foo {} }
- currentLayerNames.push(normalizeLayerName(node.params, anonLayerCounter));
+ layerParams = normalizeLayerName(node.params, anonLayerCounter);
} else if (node.params.trim()) { // @layer a, b;
- currentLayerNames = node.params.split(',').map((layerName) => {
- return layerName.trim();
- });
+ layerParams = node.params;
} else { // @layer;
return;
}
+ let currentLayerNames = parseCascadeLayerNames(layerParams);
+ if (!currentLayerNames?.length) {
+ return;
+ }
+
{
// Stitch the layer names of the current node together with those of ancestors.
// @layer foo { @layer bar { .any {} } }
@@ -56,7 +62,7 @@ export function collectCascadeLayerOrder(root: Root) {
}
currentLayerNames = currentLayerNames.map((layerName) => {
- return parentLayerName + '.' + layerName;
+ return parentLayerName.concat(layerName);
});
parent = parent.parent;
@@ -75,7 +81,7 @@ export function collectCascadeLayerOrder(root: Root) {
// 3. use the real layer to resolve other real layer names
// 4. use the implicit layer later
- const implicitLayerName = currentLayerNames[0] + '.' + 'csstools-implicit-layer';
+ const implicitLayerName = currentLayerNames[0].concat(implicitLayerNameForCloning);
references.set(node, implicitLayerName);
referencesForLayerNames.set(node, currentLayerNames[0]);
}
@@ -87,11 +93,11 @@ export function collectCascadeLayerOrder(root: Root) {
addLayerToModel(layers, [layerName]);
}
- const finalLayers = layers.map((x) => x.join('.'));
-
const out: WeakMap = new WeakMap();
for (const [node, layerName] of references) {
- out.set(node, finalLayers.indexOf(layerName));
+ out.set(node, layers.findIndex((x) => {
+ return layerName.equal(x);
+ }));
}
return out;
@@ -113,68 +119,9 @@ export function cascadeLayerNumberForNode(node: Node, layers: WeakMap {
- const allLayerNameParts = layerName.split('.');
-
- ALL_LAYER_NAME_PARTS_LOOP: for (let x = 0; x < allLayerNameParts.length; x++) {
- const layerNameParts = allLayerNameParts.slice(0, x + 1);
-
- let layerWithMostEqualSegments = -1;
- let mostEqualSegments = 0;
-
- for (let i = 0; i < layers.length; i++) {
- const existingLayerParts = layers[i];
-
- let numberOfEqualSegments = 0;
-
- LAYER_PARTS_LOOP: for (let j = 0; j < existingLayerParts.length; j++) {
- const existingLayerPart = existingLayerParts[j];
- const layerPart = layerNameParts[j];
-
- if (layerPart === existingLayerPart && (j + 1) === layerNameParts.length) {
- continue ALL_LAYER_NAME_PARTS_LOOP; // layer already exists in model
- }
-
- if (layerPart === existingLayerPart) {
- numberOfEqualSegments++;
- continue;
- }
-
- if (layerPart !== existingLayerPart) {
- break LAYER_PARTS_LOOP;
- }
- }
-
- if (numberOfEqualSegments >= mostEqualSegments) {
- layerWithMostEqualSegments = i;
- mostEqualSegments = numberOfEqualSegments;
- }
- }
-
- if (layerWithMostEqualSegments === -1) {
- layers.push(layerNameParts);
- } else {
- layers.splice(layerWithMostEqualSegments+1, 0, layerNameParts);
- }
- }
- });
+ if (layerName.trim()) {
+ return layerName;
+ }
- return layers;
+ return `csstools-anon-layer--${counter++}`;
}
diff --git a/plugins/postcss-custom-properties/test/cascade-layers.css b/plugins/postcss-custom-properties/test/cascade-layers.css
index f560bc574..b6cb1a95b 100644
--- a/plugins/postcss-custom-properties/test/cascade-layers.css
+++ b/plugins/postcss-custom-properties/test/cascade-layers.css
@@ -1,4 +1,5 @@
@layer extensions, components;
+@layer extensions.one,,invalid-layer-names..foo;
:root{
--desktop: unlayered;
diff --git a/plugins/postcss-custom-properties/test/cascade-layers.expect.css b/plugins/postcss-custom-properties/test/cascade-layers.expect.css
index 3b884a0c5..9595e2232 100644
--- a/plugins/postcss-custom-properties/test/cascade-layers.expect.css
+++ b/plugins/postcss-custom-properties/test/cascade-layers.expect.css
@@ -1,4 +1,5 @@
@layer extensions, components;
+@layer extensions.one,,invalid-layer-names..foo;
:root{
--desktop: unlayered;
diff --git a/plugins/postcss-custom-selectors/dist/index.cjs b/plugins/postcss-custom-selectors/dist/index.cjs
index c68729dc6..bcf35bb12 100644
--- a/plugins/postcss-custom-selectors/dist/index.cjs
+++ b/plugins/postcss-custom-selectors/dist/index.cjs
@@ -1 +1 @@
-"use strict";var e=require("postcss-selector-parser");function collectCascadeLayerOrder(e){const t=new Map,r=new Map,s=[];e.walkAtRules((e=>{if("layer"!==e.name.toLowerCase())return;{let t=e.parent;for(;t;){if("atrule"!==t.type||"layer"!==t.name.toLowerCase()){if(t===e.root())break;return}t=t.parent}}let o=[];if(e.nodes)o.push((n=e.params,a=1,n.trim()||"csstools-anon-layer--"+a++));else{if(!e.params.trim())return;o=e.params.split(",").map((e=>e.trim()))}var n,a;{let t=e.parent;for(;t&&"atrule"===t.type&&"layer"===t.name.toLowerCase();){const e=r.get(t);e?(o=o.map((t=>e+"."+t)),t=t.parent):t=t.parent}}if(addLayerToModel(s,o),e.nodes){const s=o[0]+".csstools-implicit-layer";t.set(e,s),r.set(e,o[0])}}));for(const e of t.values())addLayerToModel(s,[e]);const o=s.map((e=>e.join("."))),n=new WeakMap;for(const[e,r]of t)n.set(e,o.indexOf(r));return n}function addLayerToModel(e,t){return t.forEach((t=>{const r=t.split(".");e:for(let t=0;t=n&&(o=t,n=a)}-1===o?e.push(s):e.splice(o+1,0,s)}})),e}const t=new Set(["scope","container","layer"]);function isProcessableCustomSelectorRule(e){if("atrule"!==e.type)return!1;if("custom-selector"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes(":--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("rule"===r.type)return!1;if("atrule"===r.type&&!t.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function getCustomSelectors(t,r,s){const o=new Map,n=new Map,a=collectCascadeLayerOrder(t);return t.walkAtRules((t=>{var l,c;if(isProcessableCustomSelectorRule(t))try{var p,u,i;const r=t.params.trim(),m=e().astSync(r),f=null==m||null==(p=m.nodes)||null==(u=p[0])||null==(i=u.nodes)?void 0:i[0];if(!f||"pseudo"!==f.type||!f.value.startsWith(":--"))return;const d=f.toString(),y=(c=a,(l=t).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?c.has(l.parent)?c.get(l.parent):-1:1/0);if(y>=(n.get(d)??-1)&&(n.set(d,y),o.set(d,e().astSync(r.slice(d.length).trim()))),!s.preserve){const e=t.parent;t.remove(),removeEmptyAncestorBlocks(e)}}catch(e){t.warn(r,`Failed to parse custom selector : "${t.params}" with message: "${e.message}"`)}})),o}function removeEmptyAncestorBlocks(e){let t=e;for(;t;){if(t.nodes&&t.nodes.length>0)return;const e=t.parent;t.remove(),t=e}}var transformRule=(t,r,s,o)=>{let n=t.selector;try{n=e((t=>{t.walkPseudos((t=>{if(!s.has(t.value))return;const r=e.pseudo({value:":is",nodes:[]});s.get(t.value).each((e=>{r.append(e.clone({}))})),t.replaceWith(r)}))})).processSync(t.selector)}catch(e){return void t.warn(r,`Failed to parse selector : "${n}" with message: "${e.message}"`)}n!==t.selector&&(t.cloneBefore({selector:n}),o.preserve||t.remove())};const creator=e=>{const t=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-selectors] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-selectors] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-selectors",prepare(){let e=new Map;return{Once:(r,{result:s})=>{e=getCustomSelectors(r,s,{preserve:t})},Rule:(r,{result:s})=>{r.selector.includes(":--")&&transformRule(r,s,e,{preserve:t})}}}}};creator.postcss=!0,module.exports=creator;
+"use strict";var e=require("postcss-selector-parser"),r=require("@csstools/cascade-layer-name-parser");const t=r.parse("csstools-implicit-layer")[0];function collectCascadeLayerOrder(e){const s=new Map,o=new Map,a=[];e.walkAtRules((e=>{var n;if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let l;if(e.nodes)l=normalizeLayerName(e.params,1);else{if(!e.params.trim())return;l=e.params}let c=r.parse(l);if(null!=(n=c)&&n.length){{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=o.get(r);e?(c=c.map((r=>e.concat(r))),r=r.parent):r=r.parent}}if(r.addLayerToModel(a,c),e.nodes){const r=c[0].concat(t);s.set(e,r),o.set(e,c[0])}}}));for(const e of s.values())r.addLayerToModel(a,[e]);const n=new WeakMap;for(const[e,r]of s)n.set(e,a.findIndex((e=>r.equal(e))));return n}function normalizeLayerName(e,r){return e.trim()?e:"csstools-anon-layer--"+r++}const s=new Set(["scope","container","layer"]);function isProcessableCustomSelectorRule(e){if("atrule"!==e.type)return!1;if("custom-selector"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes(":--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("rule"===r.type)return!1;if("atrule"===r.type&&!s.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function getCustomSelectors(r,t,s){const o=new Map,a=new Map,n=collectCascadeLayerOrder(r);return r.walkAtRules((r=>{var l,c;if(isProcessableCustomSelectorRule(r))try{var p,u,i;const t=r.params.trim(),m=e().astSync(t),d=null==m||null==(p=m.nodes)||null==(u=p[0])||null==(i=u.nodes)?void 0:i[0];if(!d||"pseudo"!==d.type||!d.value.startsWith(":--"))return;const f=d.toString(),y=(c=n,(l=r).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?c.has(l.parent)?c.get(l.parent):-1:1/0);if(y>=(a.get(f)??-1)&&(a.set(f,y),o.set(f,e().astSync(t.slice(f.length).trim()))),!s.preserve){const e=r.parent;r.remove(),removeEmptyAncestorBlocks(e)}}catch(e){r.warn(t,`Failed to parse custom selector : "${r.params}" with message: "${e.message}"`)}})),o}function removeEmptyAncestorBlocks(e){let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}var transformRule=(r,t,s,o)=>{let a=r.selector;try{a=e((r=>{r.walkPseudos((r=>{if(!s.has(r.value))return;const t=e.pseudo({value:":is",nodes:[]});s.get(r.value).each((e=>{t.append(e.clone({}))})),r.replaceWith(t)}))})).processSync(r.selector)}catch(e){return void r.warn(t,`Failed to parse selector : "${a}" with message: "${e.message}"`)}a!==r.selector&&(r.cloneBefore({selector:a}),o.preserve||r.remove())};const creator=e=>{const r=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-selectors] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-selectors] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-selectors",prepare(){let e=new Map;return{Once:(t,{result:s})=>{e=getCustomSelectors(t,s,{preserve:r})},Rule:(t,{result:s})=>{t.selector.includes(":--")&&transformRule(t,s,e,{preserve:r})}}}}};creator.postcss=!0,module.exports=creator;
diff --git a/plugins/postcss-custom-selectors/dist/index.mjs b/plugins/postcss-custom-selectors/dist/index.mjs
index a8bbe2e70..0c61da99f 100644
--- a/plugins/postcss-custom-selectors/dist/index.mjs
+++ b/plugins/postcss-custom-selectors/dist/index.mjs
@@ -1 +1 @@
-import e from"postcss-selector-parser";function collectCascadeLayerOrder(e){const t=new Map,r=new Map,s=[];e.walkAtRules((e=>{if("layer"!==e.name.toLowerCase())return;{let t=e.parent;for(;t;){if("atrule"!==t.type||"layer"!==t.name.toLowerCase()){if(t===e.root())break;return}t=t.parent}}let o=[];if(e.nodes)o.push((n=e.params,a=1,n.trim()||"csstools-anon-layer--"+a++));else{if(!e.params.trim())return;o=e.params.split(",").map((e=>e.trim()))}var n,a;{let t=e.parent;for(;t&&"atrule"===t.type&&"layer"===t.name.toLowerCase();){const e=r.get(t);e?(o=o.map((t=>e+"."+t)),t=t.parent):t=t.parent}}if(addLayerToModel(s,o),e.nodes){const s=o[0]+".csstools-implicit-layer";t.set(e,s),r.set(e,o[0])}}));for(const e of t.values())addLayerToModel(s,[e]);const o=s.map((e=>e.join("."))),n=new WeakMap;for(const[e,r]of t)n.set(e,o.indexOf(r));return n}function addLayerToModel(e,t){return t.forEach((t=>{const r=t.split(".");e:for(let t=0;t=n&&(o=t,n=a)}-1===o?e.push(s):e.splice(o+1,0,s)}})),e}const t=new Set(["scope","container","layer"]);function isProcessableCustomSelectorRule(e){if("atrule"!==e.type)return!1;if("custom-selector"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes(":--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("rule"===r.type)return!1;if("atrule"===r.type&&!t.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function getCustomSelectors(t,r,s){const o=new Map,n=new Map,a=collectCascadeLayerOrder(t);return t.walkAtRules((t=>{var l,c;if(isProcessableCustomSelectorRule(t))try{var p,u,i;const r=t.params.trim(),m=e().astSync(r),f=null==m||null==(p=m.nodes)||null==(u=p[0])||null==(i=u.nodes)?void 0:i[0];if(!f||"pseudo"!==f.type||!f.value.startsWith(":--"))return;const d=f.toString(),y=(c=a,(l=t).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?c.has(l.parent)?c.get(l.parent):-1:1/0);if(y>=(n.get(d)??-1)&&(n.set(d,y),o.set(d,e().astSync(r.slice(d.length).trim()))),!s.preserve){const e=t.parent;t.remove(),removeEmptyAncestorBlocks(e)}}catch(e){t.warn(r,`Failed to parse custom selector : "${t.params}" with message: "${e.message}"`)}})),o}function removeEmptyAncestorBlocks(e){let t=e;for(;t;){if(t.nodes&&t.nodes.length>0)return;const e=t.parent;t.remove(),t=e}}var transformRule=(t,r,s,o)=>{let n=t.selector;try{n=e((t=>{t.walkPseudos((t=>{if(!s.has(t.value))return;const r=e.pseudo({value:":is",nodes:[]});s.get(t.value).each((e=>{r.append(e.clone({}))})),t.replaceWith(r)}))})).processSync(t.selector)}catch(e){return void t.warn(r,`Failed to parse selector : "${n}" with message: "${e.message}"`)}n!==t.selector&&(t.cloneBefore({selector:n}),o.preserve||t.remove())};const creator=e=>{const t=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-selectors] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-selectors] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-selectors",prepare(){let e=new Map;return{Once:(r,{result:s})=>{e=getCustomSelectors(r,s,{preserve:t})},Rule:(r,{result:s})=>{r.selector.includes(":--")&&transformRule(r,s,e,{preserve:t})}}}}};creator.postcss=!0;export{creator as default};
+import e from"postcss-selector-parser";import{parse as r,addLayerToModel as t}from"@csstools/cascade-layer-name-parser";const s=r("csstools-implicit-layer")[0];function collectCascadeLayerOrder(e){const o=new Map,n=new Map,a=[];e.walkAtRules((e=>{var l;if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let c;if(e.nodes)c=normalizeLayerName(e.params,1);else{if(!e.params.trim())return;c=e.params}let p=r(c);if(null!=(l=p)&&l.length){{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=n.get(r);e?(p=p.map((r=>e.concat(r))),r=r.parent):r=r.parent}}if(t(a,p),e.nodes){const r=p[0].concat(s);o.set(e,r),n.set(e,p[0])}}}));for(const e of o.values())t(a,[e]);const l=new WeakMap;for(const[e,r]of o)l.set(e,a.findIndex((e=>r.equal(e))));return l}function normalizeLayerName(e,r){return e.trim()?e:"csstools-anon-layer--"+r++}const o=new Set(["scope","container","layer"]);function isProcessableCustomSelectorRule(e){if("atrule"!==e.type)return!1;if("custom-selector"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes(":--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("rule"===r.type)return!1;if("atrule"===r.type&&!o.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function getCustomSelectors(r,t,s){const o=new Map,n=new Map,a=collectCascadeLayerOrder(r);return r.walkAtRules((r=>{var l,c;if(isProcessableCustomSelectorRule(r))try{var p,u,i;const t=r.params.trim(),m=e().astSync(t),f=null==m||null==(p=m.nodes)||null==(u=p[0])||null==(i=u.nodes)?void 0:i[0];if(!f||"pseudo"!==f.type||!f.value.startsWith(":--"))return;const d=f.toString(),y=(c=a,(l=r).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?c.has(l.parent)?c.get(l.parent):-1:1/0);if(y>=(n.get(d)??-1)&&(n.set(d,y),o.set(d,e().astSync(t.slice(d.length).trim()))),!s.preserve){const e=r.parent;r.remove(),removeEmptyAncestorBlocks(e)}}catch(e){r.warn(t,`Failed to parse custom selector : "${r.params}" with message: "${e.message}"`)}})),o}function removeEmptyAncestorBlocks(e){let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}var transformRule=(r,t,s,o)=>{let n=r.selector;try{n=e((r=>{r.walkPseudos((r=>{if(!s.has(r.value))return;const t=e.pseudo({value:":is",nodes:[]});s.get(r.value).each((e=>{t.append(e.clone({}))})),r.replaceWith(t)}))})).processSync(r.selector)}catch(e){return void r.warn(t,`Failed to parse selector : "${n}" with message: "${e.message}"`)}n!==r.selector&&(r.cloneBefore({selector:n}),o.preserve||r.remove())};const creator=e=>{const r=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-selectors] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-selectors] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-selectors",prepare(){let e=new Map;return{Once:(t,{result:s})=>{e=getCustomSelectors(t,s,{preserve:r})},Rule:(t,{result:s})=>{t.selector.includes(":--")&&transformRule(t,s,e,{preserve:r})}}}}};creator.postcss=!0;export{creator as default};
diff --git a/plugins/postcss-custom-selectors/package.json b/plugins/postcss-custom-selectors/package.json
index 29c6fd1c6..b31f38493 100644
--- a/plugins/postcss-custom-selectors/package.json
+++ b/plugins/postcss-custom-selectors/package.json
@@ -48,6 +48,9 @@
"dist"
],
"dependencies": {
+ "@csstools/cascade-layer-name-parser": "^1.0.0",
+ "@csstools/css-parser-algorithms": "^1.0.0",
+ "@csstools/css-tokenizer": "^1.0.0",
"postcss-selector-parser": "^6.0.4"
},
"peerDependencies": {
diff --git a/plugins/postcss-custom-selectors/src/cascade-layers.ts b/plugins/postcss-custom-selectors/src/cascade-layers.ts
index 4d60ebb98..3b95579cb 100644
--- a/plugins/postcss-custom-selectors/src/cascade-layers.ts
+++ b/plugins/postcss-custom-selectors/src/cascade-layers.ts
@@ -1,10 +1,13 @@
import type { AtRule, Container, Document, Node, Root } from 'postcss';
+import { LayerName, parse as parseCascadeLayerNames, addLayerToModel } from '@csstools/cascade-layer-name-parser';
+
+const implicitLayerNameForCloning = parseCascadeLayerNames('csstools-implicit-layer')[0];
export function collectCascadeLayerOrder(root: Root) {
- const references: Map = new Map();
- const referencesForLayerNames: Map = new Map();
+ const references: Map = new Map();
+ const referencesForLayerNames: Map = new Map();
- const layers: Array> = [];
+ const layers: Array = [];
const anonLayerCounter = 1;
root.walkAtRules((node) => {
@@ -32,17 +35,20 @@ export function collectCascadeLayerOrder(root: Root) {
}
}
- let currentLayerNames = [];
+ let layerParams;
if (node.nodes) { // @layer { .foo {} }
- currentLayerNames.push(normalizeLayerName(node.params, anonLayerCounter));
+ layerParams = normalizeLayerName(node.params, anonLayerCounter);
} else if (node.params.trim()) { // @layer a, b;
- currentLayerNames = node.params.split(',').map((layerName) => {
- return layerName.trim();
- });
+ layerParams = node.params;
} else { // @layer;
return;
}
+ let currentLayerNames = parseCascadeLayerNames(layerParams);
+ if (!currentLayerNames?.length) {
+ return;
+ }
+
{
// Stitch the layer names of the current node together with those of ancestors.
// @layer foo { @layer bar { .any {} } }
@@ -56,7 +62,7 @@ export function collectCascadeLayerOrder(root: Root) {
}
currentLayerNames = currentLayerNames.map((layerName) => {
- return parentLayerName + '.' + layerName;
+ return parentLayerName.concat(layerName);
});
parent = parent.parent;
@@ -75,7 +81,7 @@ export function collectCascadeLayerOrder(root: Root) {
// 3. use the real layer to resolve other real layer names
// 4. use the implicit layer later
- const implicitLayerName = currentLayerNames[0] + '.' + 'csstools-implicit-layer';
+ const implicitLayerName = currentLayerNames[0].concat(implicitLayerNameForCloning);
references.set(node, implicitLayerName);
referencesForLayerNames.set(node, currentLayerNames[0]);
}
@@ -87,11 +93,11 @@ export function collectCascadeLayerOrder(root: Root) {
addLayerToModel(layers, [layerName]);
}
- const finalLayers = layers.map((x) => x.join('.'));
-
const out: WeakMap = new WeakMap();
for (const [node, layerName] of references) {
- out.set(node, finalLayers.indexOf(layerName));
+ out.set(node, layers.findIndex((x) => {
+ return layerName.equal(x);
+ }));
}
return out;
@@ -113,68 +119,9 @@ export function cascadeLayerNumberForNode(node: Node, layers: WeakMap {
- const allLayerNameParts = layerName.split('.');
-
- ALL_LAYER_NAME_PARTS_LOOP: for (let x = 0; x < allLayerNameParts.length; x++) {
- const layerNameParts = allLayerNameParts.slice(0, x + 1);
-
- let layerWithMostEqualSegments = -1;
- let mostEqualSegments = 0;
-
- for (let i = 0; i < layers.length; i++) {
- const existingLayerParts = layers[i];
-
- let numberOfEqualSegments = 0;
-
- LAYER_PARTS_LOOP: for (let j = 0; j < existingLayerParts.length; j++) {
- const existingLayerPart = existingLayerParts[j];
- const layerPart = layerNameParts[j];
-
- if (layerPart === existingLayerPart && (j + 1) === layerNameParts.length) {
- continue ALL_LAYER_NAME_PARTS_LOOP; // layer already exists in model
- }
-
- if (layerPart === existingLayerPart) {
- numberOfEqualSegments++;
- continue;
- }
-
- if (layerPart !== existingLayerPart) {
- break LAYER_PARTS_LOOP;
- }
- }
-
- if (numberOfEqualSegments >= mostEqualSegments) {
- layerWithMostEqualSegments = i;
- mostEqualSegments = numberOfEqualSegments;
- }
- }
-
- if (layerWithMostEqualSegments === -1) {
- layers.push(layerNameParts);
- } else {
- layers.splice(layerWithMostEqualSegments+1, 0, layerNameParts);
- }
- }
- });
+ if (layerName.trim()) {
+ return layerName;
+ }
- return layers;
+ return `csstools-anon-layer--${counter++}`;
}
diff --git a/plugins/postcss-custom-selectors/test/cascade-layers.css b/plugins/postcss-custom-selectors/test/cascade-layers.css
index 7c5d4fbaa..ffc8afbb5 100644
--- a/plugins/postcss-custom-selectors/test/cascade-layers.css
+++ b/plugins/postcss-custom-selectors/test/cascade-layers.css
@@ -1,4 +1,5 @@
@layer extensions, components;
+@layer extensions.one,,invalid-layer-names..foo;
@custom-selector :--desktop :desktop(unlayered);
diff --git a/plugins/postcss-custom-selectors/test/cascade-layers.expect.css b/plugins/postcss-custom-selectors/test/cascade-layers.expect.css
index abb93defe..3a168eb56 100644
--- a/plugins/postcss-custom-selectors/test/cascade-layers.expect.css
+++ b/plugins/postcss-custom-selectors/test/cascade-layers.expect.css
@@ -1,4 +1,5 @@
@layer extensions, components;
+@layer extensions.one,,invalid-layer-names..foo;
@layer extensions {
@layer one, two
diff --git a/rollup/configs/externals.mjs b/rollup/configs/externals.mjs
index 65bc7a1c1..76d60711f 100644
--- a/rollup/configs/externals.mjs
+++ b/rollup/configs/externals.mjs
@@ -9,6 +9,7 @@ export const externalsForCLI = [
/^postcss\/lib\/*/,
'postcss-html',
+ '@csstools/cascade-layer-name-parser',
'@csstools/css-parser-algorithms',
'@csstools/css-tokenizer',
'@csstools/media-query-list-parser',
@@ -79,6 +80,7 @@ export const externalsForPlugin = [
/^postcss\/lib\/*/,
'postcss-html',
+ '@csstools/cascade-layer-name-parser',
'@csstools/css-parser-algorithms',
'@csstools/css-tokenizer',
'@csstools/media-query-list-parser',